MOBILE-2348 quiz: Implement attempt page

main
Dani Palou 2018-03-28 16:27:24 +02:00
parent fdb97caa6b
commit e7f0025b69
3 changed files with 254 additions and 0 deletions

View File

@ -0,0 +1,44 @@
<ion-header>
<ion-navbar>
<ion-title><core-format-text *ngIf="quiz" [text]="quiz.name"></core-format-text></ion-title>
</ion-navbar>
</ion-header>
<ion-content>
<ion-refresher [enabled]="loaded" (ionRefresh)="doRefresh($event)">
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
</ion-refresher>
<core-loading [hideUntil]="loaded">
<ion-list *ngIf="attempt">
<ion-item text-wrap>
<p class="item-heading">{{ 'addon.mod_quiz.attemptnumber' | translate }}</p>
<p *ngIf="attempt.preview">{{ 'addon.mod_quiz.preview' | translate }}</p>
<p *ngIf="!attempt.preview">{{ attempt.attempt }}</p>
</ion-item>
<ion-item text-wrap>
<p class="item-heading">{{ 'addon.mod_quiz.attemptstate' | translate }}</p>
<p *ngFor="let sentence of attempt.readableState">{{ sentence }}</p>
</ion-item>
<ion-item text-wrap *ngIf="quiz.showMarkColumn && attempt.readableMark !== ''">
<p class="item-heading">{{ 'addon.mod_quiz.marks' | translate }} / {{ quiz.sumGradesFormatted }}</p>
<p>{{ attempt.readableMark }}</p>
</ion-item>
<ion-item text-wrap *ngIf="quiz.showGradeColumn && attempt.readableGrade !== ''">
<p class="item-heading">{{ 'addon.mod_quiz.grade' | translate }} / {{ quiz.gradeFormatted }}</p>
<p>{{ attempt.readableGrade }}</p>
</ion-item>
<ion-item text-wrap *ngIf="quiz.showFeedbackColumn && attempt.feedback">
<p class="item-heading">{{ 'addon.mod_quiz.feedback' | translate }}</p>
<p><core-format-text [component]="component" [componentId]="componentId" [text]="attempt.feedback"></core-format-text></p>
</ion-item>
<ion-item *ngIf="quiz.showReviewColumn && attempt.finished">
<button ion-button block icon-start [navPush]="'AddonModQuizReviewPage'" [navParams]="{courseId: courseId, quizId: quiz.id, attemptId: attempt.id}">
<ion-icon name="search"></ion-icon>
{{ 'addon.mod_quiz.review' | translate }}
</button>
</ion-item>
<ion-item text-wrap class="core-error-item" *ngIf="!quiz.showReviewColumn">
<p>{{ 'addon.mod_quiz.noreviewattempt' | translate }}</p>
</ion-item>
</ion-list>
</core-loading>
</ion-content>

View File

@ -0,0 +1,33 @@
// (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 { TranslateModule } from '@ngx-translate/core';
import { CoreComponentsModule } from '@components/components.module';
import { CoreDirectivesModule } from '@directives/directives.module';
import { AddonModQuizAttemptPage } from './attempt';
@NgModule({
declarations: [
AddonModQuizAttemptPage,
],
imports: [
CoreComponentsModule,
CoreDirectivesModule,
IonicPageModule.forChild(AddonModQuizAttemptPage),
TranslateModule.forChild()
],
})
export class AddonModQuizAttemptPageModule {}

View File

@ -0,0 +1,177 @@
// (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, OnInit } from '@angular/core';
import { IonicPage, NavParams } from 'ionic-angular';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { AddonModQuizProvider } from '../../providers/quiz';
import { AddonModQuizHelperProvider } from '../../providers/helper';
/**
* Page that displays some summary data about an attempt.
*/
@IonicPage({ segment: 'addon-mod-quiz-attempt' })
@Component({
selector: 'page-addon-mod-quiz-attempt',
templateUrl: 'attempt.html',
})
export class AddonModQuizAttemptPage implements OnInit {
courseId: number; // The course ID the quiz belongs to.
quiz: any; // The quiz the attempt belongs to.
attempt: any; // The attempt to view.
component = AddonModQuizProvider.COMPONENT; // Component to link the files to.
componentId: number; // Component ID to use in conjunction with the component.
loaded: boolean; // Whether data has been loaded.
protected attemptId: number; // Attempt to view.
protected quizId: number; // ID of the quiz the attempt belongs to.
constructor(navParams: NavParams, protected domUtils: CoreDomUtilsProvider, protected quizProvider: AddonModQuizProvider,
protected quizHelper: AddonModQuizHelperProvider) {
this.attemptId = navParams.get('attemptId');
this.quizId = navParams.get('quizId');
this.courseId = navParams.get('courseId');
}
/**
* Component being initialized.
*/
ngOnInit(): void {
this.fetchQuizData().finally(() => {
this.loaded = true;
});
}
/**
* Refresh the data.
*
* @param {any} refresher Refresher.
*/
doRefresh(refresher: any): void {
this.refreshData().finally(() => {
refresher.complete();
});
}
/**
* Get quiz data and attempt data.
*
* @return {Promise<void>} Promise resolved when done.
*/
protected fetchQuizData(): Promise<void> {
return this.quizProvider.getQuizById(this.courseId, this.quizId).then((quizData) => {
this.quiz = quizData;
this.componentId = this.quiz.coursemodule;
return this.fetchAttempt();
}).catch((message) => {
this.domUtils.showErrorModalDefault(message, 'addon.mod_quiz.errorgetattempt', true);
});
}
/**
* Get the attempt data.
*
* @return {Promise<void>} Promise resolved when done.
*/
protected fetchAttempt(): Promise<void> {
const promises = [];
let options,
accessInfo;
// Get all the attempts and search the one we want.
promises.push(this.quizProvider.getUserAttempts(this.quizId).then((attempts) => {
for (let i = 0; i < attempts.length; i++) {
const attempt = attempts[i];
if (attempt.id == this.attemptId) {
this.attempt = attempt;
break;
}
}
if (!this.attempt) {
// Attempt not found, error.
return Promise.reject(null);
}
// Load flag to show if attempt is finished but not synced.
return this.quizProvider.loadFinishedOfflineData([this.attempt]);
}));
promises.push(this.quizProvider.getCombinedReviewOptions(this.quiz.id).then((opts) => {
options = opts;
}));
// Check if the user can review the attempt.
promises.push(this.quizProvider.getQuizAccessInformation(this.quiz.id).then((quizAccessInfo) => {
accessInfo = quizAccessInfo;
if (accessInfo.canreviewmyattempts) {
return this.quizProvider.getAttemptReview(this.attemptId, -1).catch(() => {
// Error getting the review, assume the user cannot review the attempt.
accessInfo.canreviewmyattempts = false;
});
}
}));
return Promise.all(promises).then(() => {
// Determine fields to show.
this.quizHelper.setQuizCalculatedData(this.quiz, options);
this.quiz.showReviewColumn = accessInfo.canreviewmyattempts;
// Get readable data for the attempt.
this.quizHelper.setAttemptCalculatedData(this.quiz, this.attempt, false);
// Check if the feedback should be displayed.
const grade = Number(this.attempt.rescaledGrade);
if (this.quiz.showFeedbackColumn && this.quizProvider.isAttemptFinished(this.attempt.state) &&
options.someoptions.overallfeedback && !isNaN(grade)) {
// Feedback should be displayed, get the feedback for the grade.
return this.quizProvider.getFeedbackForGrade(this.quiz.id, grade).then((response) => {
this.attempt.feedback = response.feedbacktext;
});
} else {
delete this.attempt.feedback;
}
});
}
/**
* Refresh the data.
*
* @return {Promise<void>} Promise resolved when done.
*/
protected refreshData(): Promise<void> {
const promises = [];
promises.push(this.quizProvider.invalidateQuizData(this.courseId));
promises.push(this.quizProvider.invalidateUserAttemptsForUser(this.quizId));
promises.push(this.quizProvider.invalidateQuizAccessInformation(this.quizId));
promises.push(this.quizProvider.invalidateCombinedReviewOptionsForUser(this.quizId));
promises.push(this.quizProvider.invalidateAttemptReview(this.attemptId));
if (this.attempt && typeof this.attempt.feedback != 'undefined') {
promises.push(this.quizProvider.invalidateFeedback(this.quizId));
}
return Promise.all(promises).catch(() => {
// Ignore errors.
}).then(() => {
return this.fetchQuizData();
});
}
}