diff --git a/src/addon/mod/quiz/lang/en.json b/src/addon/mod/quiz/lang/en.json index ecc2cde5b..9cb5add1e 100644 --- a/src/addon/mod/quiz/lang/en.json +++ b/src/addon/mod/quiz/lang/en.json @@ -48,6 +48,7 @@ "preview": "Preview", "previewquiznow": "Preview quiz now", "question": "Question", + "quiznavigation": "Quiz navigation", "quizpassword": "Quiz password", "reattemptquiz": "Re-attempt quiz", "requirepasswordmessage": "To attempt this quiz you need to know the quiz password", diff --git a/src/addon/mod/quiz/pages/navigation-modal/navigation-modal.html b/src/addon/mod/quiz/pages/navigation-modal/navigation-modal.html new file mode 100644 index 000000000..c618530f1 --- /dev/null +++ b/src/addon/mod/quiz/pages/navigation-modal/navigation-modal.html @@ -0,0 +1,41 @@ + + + {{ 'addon.mod_quiz.quiznavigation' | translate }} + + + + + + + + diff --git a/src/addon/mod/quiz/pages/navigation-modal/navigation-modal.module.ts b/src/addon/mod/quiz/pages/navigation-modal/navigation-modal.module.ts new file mode 100644 index 000000000..51d29607a --- /dev/null +++ b/src/addon/mod/quiz/pages/navigation-modal/navigation-modal.module.ts @@ -0,0 +1,29 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { NgModule } from '@angular/core'; +import { IonicPageModule } from 'ionic-angular'; +import { AddonModQuizNavigationModalPage } from './navigation-modal'; +import { TranslateModule } from '@ngx-translate/core'; + +@NgModule({ + declarations: [ + AddonModQuizNavigationModalPage + ], + imports: [ + IonicPageModule.forChild(AddonModQuizNavigationModalPage), + TranslateModule.forChild() + ] +}) +export class AddonModQuizNavigationModalPageModule {} diff --git a/src/addon/mod/quiz/pages/navigation-modal/navigation-modal.scss b/src/addon/mod/quiz/pages/navigation-modal/navigation-modal.scss new file mode 100644 index 000000000..5abb5c103 --- /dev/null +++ b/src/addon/mod/quiz/pages/navigation-modal/navigation-modal.scss @@ -0,0 +1,5 @@ +page-addon-mod-quiz-navigation-modal { + .addon-mod_quiz-selected, .item.addon-mod_quiz-selected { + background: $blue-light; + } +} diff --git a/src/addon/mod/quiz/pages/navigation-modal/navigation-modal.ts b/src/addon/mod/quiz/pages/navigation-modal/navigation-modal.ts new file mode 100644 index 000000000..1d09cd7ce --- /dev/null +++ b/src/addon/mod/quiz/pages/navigation-modal/navigation-modal.ts @@ -0,0 +1,66 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Component } from '@angular/core'; +import { IonicPage, ViewController, NavParams } from 'ionic-angular'; + +/** + * Modal that renders the quiz navigation. + */ +@IonicPage({ segment: 'addon-mod-quiz-navigation-modal' }) +@Component({ + selector: 'page-addon-mod-quiz-navigation-modal', + templateUrl: 'navigation-modal.html', +}) +export class AddonModQuizNavigationModalPage { + + /** + * The instance of the page that opened the modal. We use the instance instead of the needed attributes for these reasons: + * - Some attributes can change dynamically, and we don't want to create the modal everytime the user opens it. + * - The onDidDismiss function takes a while to be called, making the app seem slow. This way we can directly call + * the functions we need without having to wait for the modal to be dismissed. + * @type {any} + */ + pageInstance: any; + + constructor(params: NavParams, protected viewCtrl: ViewController) { + this.pageInstance = params.get('page'); + } + + /** + * Close modal. + */ + closeModal(): void { + this.viewCtrl.dismiss(); + } + + /** + * Load a certain page. + * + * @param {number} page The page to load. + * @param {number} [slot] Slot of the question to scroll to. + */ + loadPage(page: number, slot: number): void { + this.pageInstance.changePage && this.pageInstance.changePage(page, true, slot); + this.closeModal(); + } + + /** + * Switch mode in review. + */ + switchMode(): void { + this.pageInstance.switchMode && this.pageInstance.switchMode(); + this.closeModal(); + } +} diff --git a/src/addon/mod/quiz/pages/player/player.html b/src/addon/mod/quiz/pages/player/player.html index f37140cc8..9d84cf51e 100644 --- a/src/addon/mod/quiz/pages/player/player.html +++ b/src/addon/mod/quiz/pages/player/player.html @@ -6,7 +6,9 @@ - + diff --git a/src/addon/mod/quiz/pages/player/player.ts b/src/addon/mod/quiz/pages/player/player.ts index 3d0d02208..c28ae687a 100644 --- a/src/addon/mod/quiz/pages/player/player.ts +++ b/src/addon/mod/quiz/pages/player/player.ts @@ -13,7 +13,7 @@ // limitations under the License. import { Component, OnInit, OnDestroy, ViewChild, ElementRef, ChangeDetectorRef } from '@angular/core'; -import { IonicPage, NavParams, Content, PopoverController } from 'ionic-angular'; +import { IonicPage, NavParams, Content, PopoverController, ModalController, Modal } from 'ionic-angular'; import { TranslateService } from '@ngx-translate/core'; import { CoreEventsProvider } from '@providers/events'; import { CoreLoggerProvider } from '@providers/logger'; @@ -47,7 +47,7 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy { quizAborted: boolean; // Whether the quiz was aborted due to an error. preflightData: any = {}; // Preflight data to attempt the quiz. offline: boolean; // Whether the quiz is being attempted in offline mode. - toc: any[]; // TOC to navigate the questions. + navigation: any[]; // List of questions to navigate them. questions: any[]; // Questions of the current page. nextPage: number; // Next page. previousPage: number; // Previous page. @@ -70,13 +70,15 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy { protected timeUpCalled: boolean; // Whether the time up function has been called. protected autoSave: AddonModQuizAutoSave; // Class to auto-save answers every certain time. protected autoSaveErrorSubscription: Subscription; // To be notified when an error happens in auto-save. + protected navigationModal: Modal; // Modal to navigate through the questions. constructor(navParams: NavParams, element: ElementRef, logger: CoreLoggerProvider, protected translate: TranslateService, protected eventsProvider: CoreEventsProvider, protected sitesProvider: CoreSitesProvider, protected syncProvider: CoreSyncProvider, protected domUtils: CoreDomUtilsProvider, popoverCtrl: PopoverController, protected timeUtils: CoreTimeUtilsProvider, protected quizProvider: AddonModQuizProvider, protected quizHelper: AddonModQuizHelperProvider, protected quizSync: AddonModQuizSyncProvider, - protected questionHelper: CoreQuestionHelperProvider, protected cdr: ChangeDetectorRef) { + protected questionHelper: CoreQuestionHelperProvider, protected cdr: ChangeDetectorRef, + protected modalCtrl: ModalController) { this.quizId = navParams.get('quizId'); this.courseId = navParams.get('courseId'); @@ -88,6 +90,11 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy { // Create the auto save instance. this.autoSave = new AddonModQuizAutoSave('addon-mod_quiz-player-form', '#addon-mod_quiz-connection-error-button', logger, popoverCtrl, questionHelper, quizProvider); + + // Create the navigation modal. + this.navigationModal = this.modalCtrl.create('AddonModQuizNavigationModalPage', { + page: this + }); } /** @@ -164,10 +171,10 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy { * Change the current page. If slot is supplied, try to scroll to that question. * * @param {number} page Page to load. -1 means summary. - * @param {boolean} [fromToc] Whether the page was selected using the TOC. + * @param {boolean} [fromModal] Whether the page was selected using the navigation modal. * @param {number} [slot] Slot of the question to scroll to. */ - changePage(page: number, fromToc?: boolean, slot?: number): void { + changePage(page: number, fromModal?: boolean, slot?: number): void { if (page != -1 && (this.attempt.state == AddonModQuizProvider.ATTEMPT_OVERDUE || this.attempt.finishedOffline)) { // We can't load a page if overdue or the local attempt is finished. return; @@ -176,9 +183,9 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy { this.scrollToQuestion(slot); return; - } else if ((page == this.attempt.currentpage && !this.showSummary) || (fromToc && this.quiz.isSequential && page != -1)) { + } else if ((page == this.attempt.currentpage && !this.showSummary) || (fromModal && this.quiz.isSequential && page != -1)) { // If the user is navigating to the current page we do nothing. - // Also, in sequential quizzes we don't allow navigating using the TOC except for finishing the quiz (summary). + // Also, in sequential quizzes we don't allow navigating using the modal except for finishing the quiz (summary). return; } else if (page === -1 && this.showSummary) { // Summary already shown. @@ -417,14 +424,14 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy { } /** - * Load TOC to navigate the questions. + * Load data to navigate the questions using the navigation modal. * * @return {Promise} Promise resolved when done. */ - protected loadToc(): Promise { - // We use the attempt summary to build the TOC because it contains all the questions. + protected loadNavigation(): Promise { + // We use the attempt summary to build the navigation because it contains all the questions. return this.quizProvider.getAttemptSummary(this.attempt.id, this.preflightData, this.offline).then((questions) => { - this.toc = questions; + this.navigation = questions; }); } @@ -512,7 +519,7 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy { this.attemptAccessInfo = info; this.attempt = attempt; - return this.loadToc(); + return this.loadNavigation(); }).then(() => { if (this.attempt.state != AddonModQuizProvider.ATTEMPT_OVERDUE && !this.attempt.finishedOffline) { // Attempt not overdue and not finished in offline, load page.