Skip to content

Commit c120c29

Browse files
authored
Merge pull request #4226 from crazyserver/MOBILE-4616
MOBILE-4616 quiz: Improve summary and navigation info to match LMS
2 parents ae984d3 + 4aa75a7 commit c120c29

File tree

9 files changed

+71
-40
lines changed

9 files changed

+71
-40
lines changed

src/addons/mod/quiz/components/navigation-modal/navigation-modal.html

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,24 +19,24 @@ <h1>{{ 'addon.mod_quiz.quiznavigation' | translate }}</h1>
1919
[detail]="false">
2020

2121
<ion-label class="ion-text-wrap">
22-
<span *ngIf="question.type !== 'description' && question.questionnumber">
22+
<p class="item-heading" *ngIf="question.type !== 'description' && question.questionnumber">
2323
{{ 'core.question.questionno' | translate:{$a: question.questionnumber} }}
24-
</span>
25-
<span *ngIf="question.type === 'description' || !question.questionnumber">
24+
</p>
25+
<p class="item-heading" *ngIf="question.type === 'description' || !question.questionnumber">
2626
{{ 'core.question.information' | translate }}
27-
</span>
27+
</p>
28+
<p>{{ question.status }}</p>
2829
</ion-label>
2930

3031
<ion-icon *ngIf="question.type === 'description' || !question.questionnumber" name="fas-circle-info" slot="end"
3132
aria-hidden="true" />
32-
<ion-icon *ngIf="question.stateClass === 'core-question-requiresgrading'" name="fas-circle-question"
33-
[title]="question.status" slot="end" />
34-
<ion-icon *ngIf="question.stateClass === 'core-question-correct'" [name]="correctIcon" color="success"
35-
[title]="question.status" slot="end" />
36-
<ion-icon *ngIf="question.stateClass === 'core-question-partiallycorrect'" [name]="partialCorrectIcon" color="warning"
37-
[title]="question.status" slot="end" />
38-
<ion-icon *ngIf="question.stateClass === 'core-question-incorrect' ||
39-
question.stateClass === 'core-question-notanswered'" [name]="incorrectIcon" color="danger" [title]="question.status"
33+
<ion-icon *ngIf="question.stateclass === 'requiresgrading'" name="fas-circle-question" aria-hidden="true" slot="end" />
34+
<ion-icon *ngIf="question.stateclass === 'correct'" [name]="correctIcon" color="success" aria-hidden="true" slot="end" />
35+
<ion-icon *ngIf="question.stateclass === 'partiallycorrect'" [name]="partialCorrectIcon" color="warning" aria-hidden="true"
36+
slot="end" />
37+
<ion-icon *ngIf="question.stateclass === 'incorrect' || question.stateclass === 'notanswered'" [name]="incorrectIcon"
38+
color="danger" aria-hidden="true" slot="end" />
39+
<ion-icon *ngIf="question.stateclass === 'invalidanswer'" name="fas-triangle-exclamation" color="danger" aria-hidden="true"
4040
slot="end" />
4141
</ion-item>
4242
</ion-list>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
ion-item.core-question-blocked,
2+
ion-item.core-question-complete,
3+
ion-item.core-question-answersaved,
4+
ion-item.core-question-requiresgrading {
5+
--background: var(--gray-300);
6+
}

src/addons/mod/quiz/components/navigation-modal/navigation-modal.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { ModalController } from '@singletons';
2626
@Component({
2727
selector: 'addon-mod-quiz-navigation-modal',
2828
templateUrl: 'navigation-modal.html',
29+
styleUrl: 'navigation-modal.scss',
2930
standalone: true,
3031
imports: [
3132
CoreSharedModule,

src/addons/mod/quiz/pages/player/player.html

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ <h1>
1515
(click)="showConnectionError($event)" [ariaLabel]="'addon.mod_quiz.connectionerror' | translate" aria-haspopup="dialog">
1616
<ion-icon name="fas-circle-exclamation" slot="icon-only" aria-hidden="true" />
1717
</ion-button>
18-
<ion-button *ngIf="navigation.length" [ariaLabel]="'addon.mod_quiz.opentoc' | translate" (click)="openNavigation()">
18+
<ion-button *ngIf="navigation.length && !showSummary" [ariaLabel]="'addon.mod_quiz.opentoc' | translate"
19+
(click)="openNavigation()">
1920
<ion-icon name="fas-bookmark" slot="icon-only" aria-hidden="true" />
2021
</ion-button>
2122
</ion-buttons>
@@ -72,11 +73,24 @@ <h1>
7273
<ng-container *ngFor="let question of summaryQuestions">
7374
<ion-item *ngIf="question.type !== 'description' && question.questionnumber"
7475
(click)="!isSequential && canReturn && changePage(question.page, false, question.slot)"
75-
[detail]="!isSequential && canReturn" [button]="!isSequential && canReturn" class="ion-text-wrap">
76-
<ion-label>
77-
<span [attr.aria-label]="'core.question.questionno' | translate:{$a: question.questionnumber}">
78-
{{ question.questionnumber }}.</span> {{ question.status }}
76+
[detail]="!isSequential && canReturn" [button]="!isSequential && canReturn"
77+
[class]="'ion-text-wrap ' + question.stateClass">
78+
<ion-label class="ion-text-wrap">
79+
<p class="item-heading">
80+
{{ 'core.question.questionno' | translate:{$a: question.questionnumber} }}
81+
</p>
82+
<p>{{ question.status }}</p>
7983
</ion-label>
84+
85+
<ion-icon *ngIf="question.stateclass === 'requiresgrading'" name="fas-circle-question" aria-hidden="true" slot="end" />
86+
<ion-icon *ngIf="question.stateclass === 'correct'" [name]="correctIcon" color="success" aria-hidden="true"
87+
slot="end" />
88+
<ion-icon *ngIf="question.stateclass === 'partiallycorrect'" [name]="partialCorrectIcon" color="warning"
89+
aria-hidden="true" slot="end" />
90+
<ion-icon *ngIf="question.stateclass === 'incorrect' || question.stateclass === 'notanswered'" [name]="incorrectIcon"
91+
color="danger" aria-hidden="true" slot="end" />
92+
<ion-icon *ngIf="question.stateclass === 'invalidanswer'" name="fas-triangle-exclamation" color="danger"
93+
aria-hidden="true" slot="end" />
8094
</ion-item>
8195
</ng-container>
8296

src/addons/mod/quiz/pages/player/player.scss

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,11 @@ $quiz-timer-iterations: 15 !default;
1717
}
1818
}
1919
}
20+
21+
ion-item.core-question-blocked,
22+
ion-item.core-question-complete,
23+
ion-item.core-question-answersaved,
24+
ion-item.core-question-requiresgrading {
25+
--background: var(--gray-300);
26+
}
2027
}

src/addons/mod/quiz/pages/player/player.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ import { CoreLoadings } from '@services/loadings';
6363
@Component({
6464
selector: 'page-addon-mod-quiz-player',
6565
templateUrl: 'player.html',
66-
styleUrls: ['player.scss'],
66+
styleUrl: 'player.scss',
6767
})
6868
export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave {
6969

@@ -93,6 +93,9 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave {
9393
dueDateWarning?: string; // Warning about due date.
9494
courseId!: number; // The course ID the quiz belongs to.
9595
cmId!: number; // Course module ID.
96+
correctIcon = '';
97+
incorrectIcon = '';
98+
partialCorrectIcon = '';
9699

97100
protected preflightData: Record<string, string> = {}; // Preflight data to attempt the quiz.
98101
protected quizAccessInfo?: AddonModQuizGetQuizAccessInformationWSResponse; // Quiz access information.
@@ -672,6 +675,12 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave {
672675
return;
673676
}
674677

678+
if (!this.correctIcon) {
679+
this.correctIcon = CoreQuestionHelper.getCorrectIcon().fullName;
680+
this.incorrectIcon = CoreQuestionHelper.getIncorrectIcon().fullName;
681+
this.partialCorrectIcon = CoreQuestionHelper.getPartiallyCorrectIcon().fullName;
682+
}
683+
675684
this.summaryQuestions = [];
676685

677686
this.summaryQuestions = await AddonModQuiz.getAttemptSummary(this.attempt.id, this.preflightData, {
@@ -680,6 +689,10 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave {
680689
readingStrategy: this.offline ? CoreSitesReadingStrategy.PREFER_CACHE : CoreSitesReadingStrategy.ONLY_NETWORK,
681690
});
682691

692+
this.summaryQuestions.forEach((question) => {
693+
CoreQuestionHelper.populateQuestionStateClass(question);
694+
});
695+
683696
this.showSummary = true;
684697
this.canReturn = this.attempt.state === AddonModQuizAttemptStates.IN_PROGRESS && !this.attempt.finishedOffline;
685698
this.preventSubmitMessages = AddonModQuiz.getPreventSubmitMessages(this.summaryQuestions);
@@ -707,7 +720,7 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave {
707720
});
708721

709722
this.navigation.forEach((question) => {
710-
question.stateClass = CoreQuestionHelper.getQuestionStateClass(question.state || '');
723+
CoreQuestionHelper.populateQuestionStateClass(question);
711724
});
712725
}
713726

src/addons/mod/quiz/pages/review/review.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ export class AddonModQuizReviewPage implements OnInit {
210210
this.navigation = data.questions;
211211

212212
this.navigation.forEach((question) => {
213-
question.stateClass = CoreQuestionHelper.getQuestionStateClass(question.state || '');
213+
CoreQuestionHelper.populateQuestionStateClass(question);
214214
});
215215

216216
const lastQuestion = data.questions[data.questions.length - 1];

src/core/features/question/services/question-helper.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import { ContextLevel } from '@/core/constants';
3232
import { CoreIonicColorNames } from '@singletons/colors';
3333
import { CoreViewer } from '@features/viewer/services/viewer';
3434
import { convertTextToHTMLElement } from '@/core/utils/create-html-element';
35+
import { AddonModQuizNavigationQuestion } from '@addons/mod/quiz/components/navigation-modal/navigation-modal';
3536

3637
/**
3738
* Service with some common functions to handle questions.
@@ -476,15 +477,18 @@ export class CoreQuestionHelperProvider {
476477
}
477478

478479
/**
479-
* Get the CSS class for a question based on its state.
480+
* Populates the CSS class for a question based on its state.
480481
*
481-
* @param name Question's state name.
482-
* @returns State class.
482+
* @param question Question.
483483
*/
484-
getQuestionStateClass(name: string): string {
485-
const state = CoreQuestion.getState(name);
484+
populateQuestionStateClass(question: AddonModQuizNavigationQuestion): void {
485+
if (!question.stateclass) {
486+
const state = CoreQuestion.getState(question.state);
487+
488+
question.stateclass = state.stateclass;
489+
}
486490

487-
return state ? state.class : '';
491+
question.stateClass = 'core-question-' + (question.stateclass ?? 'unknown');
488492
}
489493

490494
/**

src/core/features/question/services/question.ts

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -42,103 +42,90 @@ const QUESTION_PREFIX_REGEX = /q\d+:(\d+)_/;
4242
const STATES: Record<string, CoreQuestionState> = {
4343
todo: {
4444
name: 'todo',
45-
class: 'core-question-notyetanswered',
4645
status: 'notyetanswered',
4746
stateclass: 'notyetanswered',
4847
active: true,
4948
finished: false,
5049
},
5150
invalid: {
5251
name: 'invalid',
53-
class: 'core-question-invalidanswer',
5452
status: 'invalidanswer',
5553
stateclass: 'invalidanswer',
5654
active: true,
5755
finished: false,
5856
},
5957
complete: {
6058
name: 'complete',
61-
class: 'core-question-answersaved',
6259
status: 'answersaved',
6360
stateclass: 'answersaved',
6461
active: true,
6562
finished: false,
6663
},
6764
needsgrading: {
6865
name: 'needsgrading',
69-
class: 'core-question-requiresgrading',
7066
status: 'requiresgrading',
7167
stateclass: 'requiresgrading',
7268
active: false,
7369
finished: true,
7470
},
7571
finished: {
7672
name: 'finished',
77-
class: 'core-question-complete',
7873
status: 'complete',
7974
stateclass: 'complete',
8075
active: false,
8176
finished: true,
8277
},
8378
gaveup: {
8479
name: 'gaveup',
85-
class: 'core-question-notanswered',
8680
status: 'notanswered',
8781
stateclass: 'notanswered',
8882
active: false,
8983
finished: true,
9084
},
9185
gradedwrong: {
9286
name: 'gradedwrong',
93-
class: 'core-question-incorrect',
9487
status: 'incorrect',
9588
stateclass: 'incorrect',
9689
active: false,
9790
finished: true,
9891
},
9992
gradedpartial: {
10093
name: 'gradedpartial',
101-
class: 'core-question-partiallycorrect',
10294
status: 'partiallycorrect',
10395
stateclass: 'partiallycorrect',
10496
active: false,
10597
finished: true,
10698
},
10799
gradedright: {
108100
name: 'gradedright',
109-
class: 'core-question-correct',
110101
status: 'correct',
111102
stateclass: 'correct',
112103
active: false,
113104
finished: true,
114105
},
115106
mangrwrong: {
116107
name: 'mangrwrong',
117-
class: 'core-question-incorrect',
118108
status: 'incorrect',
119109
stateclass: 'incorrect',
120110
active: false,
121111
finished: true,
122112
},
123113
mangrpartial: {
124114
name: 'mangrpartial',
125-
class: 'core-question-partiallycorrect',
126115
status: 'partiallycorrect',
127116
stateclass: 'partiallycorrect',
128117
active: false,
129118
finished: true,
130119
},
131120
mangrright: {
132121
name: 'mangrright',
133-
class: 'core-question-correct',
134122
status: 'correct',
135123
stateclass: 'correct',
136124
active: false,
137125
finished: true,
138126
},
139127
cannotdeterminestatus: { // Special state for Mobile, sometimes we won't have enough data to detemrine the state.
140128
name: 'cannotdeterminestatus',
141-
class: 'core-question-unknown',
142129
status: 'cannotdeterminestatus',
143130
stateclass: undefined,
144131
active: true,
@@ -594,7 +581,6 @@ export const CoreQuestion = makeSingleton(CoreQuestionProvider);
594581
*/
595582
export type CoreQuestionState = {
596583
name: string; // Name of the state.
597-
class: string; // Class to style the state.
598584
status: string; // The string key to translate the state.
599585
stateclass: // A machine-readable class name for the state that this question attempt is in.
600586
typeof QUESTION_TODO_STATE_CLASSES[number] |

0 commit comments

Comments
 (0)