Skip to content

Commit

Permalink
Merge pull request #4226 from crazyserver/MOBILE-4616
Browse files Browse the repository at this point in the history
MOBILE-4616 quiz: Improve summary and navigation info to match LMS
  • Loading branch information
albertgasset authored Oct 23, 2024
2 parents ae984d3 + 4aa75a7 commit c120c29
Show file tree
Hide file tree
Showing 9 changed files with 71 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,24 +19,24 @@ <h1>{{ 'addon.mod_quiz.quiznavigation' | translate }}</h1>
[detail]="false">

<ion-label class="ion-text-wrap">
<span *ngIf="question.type !== 'description' && question.questionnumber">
<p class="item-heading" *ngIf="question.type !== 'description' && question.questionnumber">
{{ 'core.question.questionno' | translate:{$a: question.questionnumber} }}
</span>
<span *ngIf="question.type === 'description' || !question.questionnumber">
</p>
<p class="item-heading" *ngIf="question.type === 'description' || !question.questionnumber">
{{ 'core.question.information' | translate }}
</span>
</p>
<p>{{ question.status }}</p>
</ion-label>

<ion-icon *ngIf="question.type === 'description' || !question.questionnumber" name="fas-circle-info" slot="end"
aria-hidden="true" />
<ion-icon *ngIf="question.stateClass === 'core-question-requiresgrading'" name="fas-circle-question"
[title]="question.status" slot="end" />
<ion-icon *ngIf="question.stateClass === 'core-question-correct'" [name]="correctIcon" color="success"
[title]="question.status" slot="end" />
<ion-icon *ngIf="question.stateClass === 'core-question-partiallycorrect'" [name]="partialCorrectIcon" color="warning"
[title]="question.status" slot="end" />
<ion-icon *ngIf="question.stateClass === 'core-question-incorrect' ||
question.stateClass === 'core-question-notanswered'" [name]="incorrectIcon" color="danger" [title]="question.status"
<ion-icon *ngIf="question.stateclass === 'requiresgrading'" name="fas-circle-question" aria-hidden="true" slot="end" />
<ion-icon *ngIf="question.stateclass === 'correct'" [name]="correctIcon" color="success" aria-hidden="true" slot="end" />
<ion-icon *ngIf="question.stateclass === 'partiallycorrect'" [name]="partialCorrectIcon" color="warning" aria-hidden="true"
slot="end" />
<ion-icon *ngIf="question.stateclass === 'incorrect' || question.stateclass === 'notanswered'" [name]="incorrectIcon"
color="danger" aria-hidden="true" slot="end" />
<ion-icon *ngIf="question.stateclass === 'invalidanswer'" name="fas-triangle-exclamation" color="danger" aria-hidden="true"
slot="end" />
</ion-item>
</ion-list>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
ion-item.core-question-blocked,
ion-item.core-question-complete,
ion-item.core-question-answersaved,
ion-item.core-question-requiresgrading {
--background: var(--gray-300);
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { ModalController } from '@singletons';
@Component({
selector: 'addon-mod-quiz-navigation-modal',
templateUrl: 'navigation-modal.html',
styleUrl: 'navigation-modal.scss',
standalone: true,
imports: [
CoreSharedModule,
Expand Down
24 changes: 19 additions & 5 deletions src/addons/mod/quiz/pages/player/player.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ <h1>
(click)="showConnectionError($event)" [ariaLabel]="'addon.mod_quiz.connectionerror' | translate" aria-haspopup="dialog">
<ion-icon name="fas-circle-exclamation" slot="icon-only" aria-hidden="true" />
</ion-button>
<ion-button *ngIf="navigation.length" [ariaLabel]="'addon.mod_quiz.opentoc' | translate" (click)="openNavigation()">
<ion-button *ngIf="navigation.length && !showSummary" [ariaLabel]="'addon.mod_quiz.opentoc' | translate"
(click)="openNavigation()">
<ion-icon name="fas-bookmark" slot="icon-only" aria-hidden="true" />
</ion-button>
</ion-buttons>
Expand Down Expand Up @@ -72,11 +73,24 @@ <h1>
<ng-container *ngFor="let question of summaryQuestions">
<ion-item *ngIf="question.type !== 'description' && question.questionnumber"
(click)="!isSequential && canReturn && changePage(question.page, false, question.slot)"
[detail]="!isSequential && canReturn" [button]="!isSequential && canReturn" class="ion-text-wrap">
<ion-label>
<span [attr.aria-label]="'core.question.questionno' | translate:{$a: question.questionnumber}">
{{ question.questionnumber }}.</span> {{ question.status }}
[detail]="!isSequential && canReturn" [button]="!isSequential && canReturn"
[class]="'ion-text-wrap ' + question.stateClass">
<ion-label class="ion-text-wrap">
<p class="item-heading">
{{ 'core.question.questionno' | translate:{$a: question.questionnumber} }}
</p>
<p>{{ question.status }}</p>
</ion-label>

<ion-icon *ngIf="question.stateclass === 'requiresgrading'" name="fas-circle-question" aria-hidden="true" slot="end" />
<ion-icon *ngIf="question.stateclass === 'correct'" [name]="correctIcon" color="success" aria-hidden="true"
slot="end" />
<ion-icon *ngIf="question.stateclass === 'partiallycorrect'" [name]="partialCorrectIcon" color="warning"
aria-hidden="true" slot="end" />
<ion-icon *ngIf="question.stateclass === 'incorrect' || question.stateclass === 'notanswered'" [name]="incorrectIcon"
color="danger" aria-hidden="true" slot="end" />
<ion-icon *ngIf="question.stateclass === 'invalidanswer'" name="fas-triangle-exclamation" color="danger"
aria-hidden="true" slot="end" />
</ion-item>
</ng-container>

Expand Down
7 changes: 7 additions & 0 deletions src/addons/mod/quiz/pages/player/player.scss
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,11 @@ $quiz-timer-iterations: 15 !default;
}
}
}

ion-item.core-question-blocked,
ion-item.core-question-complete,
ion-item.core-question-answersaved,
ion-item.core-question-requiresgrading {
--background: var(--gray-300);
}
}
17 changes: 15 additions & 2 deletions src/addons/mod/quiz/pages/player/player.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ import { CoreLoadings } from '@services/loadings';
@Component({
selector: 'page-addon-mod-quiz-player',
templateUrl: 'player.html',
styleUrls: ['player.scss'],
styleUrl: 'player.scss',
})
export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave {

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

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

if (!this.correctIcon) {
this.correctIcon = CoreQuestionHelper.getCorrectIcon().fullName;
this.incorrectIcon = CoreQuestionHelper.getIncorrectIcon().fullName;
this.partialCorrectIcon = CoreQuestionHelper.getPartiallyCorrectIcon().fullName;
}

this.summaryQuestions = [];

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

this.summaryQuestions.forEach((question) => {
CoreQuestionHelper.populateQuestionStateClass(question);
});

this.showSummary = true;
this.canReturn = this.attempt.state === AddonModQuizAttemptStates.IN_PROGRESS && !this.attempt.finishedOffline;
this.preventSubmitMessages = AddonModQuiz.getPreventSubmitMessages(this.summaryQuestions);
Expand Down Expand Up @@ -707,7 +720,7 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave {
});

this.navigation.forEach((question) => {
question.stateClass = CoreQuestionHelper.getQuestionStateClass(question.state || '');
CoreQuestionHelper.populateQuestionStateClass(question);
});
}

Expand Down
2 changes: 1 addition & 1 deletion src/addons/mod/quiz/pages/review/review.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ export class AddonModQuizReviewPage implements OnInit {
this.navigation = data.questions;

this.navigation.forEach((question) => {
question.stateClass = CoreQuestionHelper.getQuestionStateClass(question.state || '');
CoreQuestionHelper.populateQuestionStateClass(question);
});

const lastQuestion = data.questions[data.questions.length - 1];
Expand Down
16 changes: 10 additions & 6 deletions src/core/features/question/services/question-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { ContextLevel } from '@/core/constants';
import { CoreIonicColorNames } from '@singletons/colors';
import { CoreViewer } from '@features/viewer/services/viewer';
import { convertTextToHTMLElement } from '@/core/utils/create-html-element';
import { AddonModQuizNavigationQuestion } from '@addons/mod/quiz/components/navigation-modal/navigation-modal';

/**
* Service with some common functions to handle questions.
Expand Down Expand Up @@ -476,15 +477,18 @@ export class CoreQuestionHelperProvider {
}

/**
* Get the CSS class for a question based on its state.
* Populates the CSS class for a question based on its state.
*
* @param name Question's state name.
* @returns State class.
* @param question Question.
*/
getQuestionStateClass(name: string): string {
const state = CoreQuestion.getState(name);
populateQuestionStateClass(question: AddonModQuizNavigationQuestion): void {
if (!question.stateclass) {
const state = CoreQuestion.getState(question.state);

question.stateclass = state.stateclass;
}

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

/**
Expand Down
14 changes: 0 additions & 14 deletions src/core/features/question/services/question.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,103 +42,90 @@ const QUESTION_PREFIX_REGEX = /q\d+:(\d+)_/;
const STATES: Record<string, CoreQuestionState> = {
todo: {
name: 'todo',
class: 'core-question-notyetanswered',
status: 'notyetanswered',
stateclass: 'notyetanswered',
active: true,
finished: false,
},
invalid: {
name: 'invalid',
class: 'core-question-invalidanswer',
status: 'invalidanswer',
stateclass: 'invalidanswer',
active: true,
finished: false,
},
complete: {
name: 'complete',
class: 'core-question-answersaved',
status: 'answersaved',
stateclass: 'answersaved',
active: true,
finished: false,
},
needsgrading: {
name: 'needsgrading',
class: 'core-question-requiresgrading',
status: 'requiresgrading',
stateclass: 'requiresgrading',
active: false,
finished: true,
},
finished: {
name: 'finished',
class: 'core-question-complete',
status: 'complete',
stateclass: 'complete',
active: false,
finished: true,
},
gaveup: {
name: 'gaveup',
class: 'core-question-notanswered',
status: 'notanswered',
stateclass: 'notanswered',
active: false,
finished: true,
},
gradedwrong: {
name: 'gradedwrong',
class: 'core-question-incorrect',
status: 'incorrect',
stateclass: 'incorrect',
active: false,
finished: true,
},
gradedpartial: {
name: 'gradedpartial',
class: 'core-question-partiallycorrect',
status: 'partiallycorrect',
stateclass: 'partiallycorrect',
active: false,
finished: true,
},
gradedright: {
name: 'gradedright',
class: 'core-question-correct',
status: 'correct',
stateclass: 'correct',
active: false,
finished: true,
},
mangrwrong: {
name: 'mangrwrong',
class: 'core-question-incorrect',
status: 'incorrect',
stateclass: 'incorrect',
active: false,
finished: true,
},
mangrpartial: {
name: 'mangrpartial',
class: 'core-question-partiallycorrect',
status: 'partiallycorrect',
stateclass: 'partiallycorrect',
active: false,
finished: true,
},
mangrright: {
name: 'mangrright',
class: 'core-question-correct',
status: 'correct',
stateclass: 'correct',
active: false,
finished: true,
},
cannotdeterminestatus: { // Special state for Mobile, sometimes we won't have enough data to detemrine the state.
name: 'cannotdeterminestatus',
class: 'core-question-unknown',
status: 'cannotdeterminestatus',
stateclass: undefined,
active: true,
Expand Down Expand Up @@ -594,7 +581,6 @@ export const CoreQuestion = makeSingleton(CoreQuestionProvider);
*/
export type CoreQuestionState = {
name: string; // Name of the state.
class: string; // Class to style the state.
status: string; // The string key to translate the state.
stateclass: // A machine-readable class name for the state that this question attempt is in.
typeof QUESTION_TODO_STATE_CLASSES[number] |
Expand Down

0 comments on commit c120c29

Please sign in to comment.