Merge pull request #2620 from dpalou/MOBILE-3523

Mobile 3523
main
Juan Leyva 2020-11-25 12:47:39 +01:00 committed by GitHub
commit 6dac0af34d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 84 additions and 17 deletions

View File

@ -583,7 +583,13 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy {
*/
protected processAttempt(userFinish?: boolean, timeUp?: boolean, retrying?: boolean): Promise<any> {
// Get the answers to send.
return this.prepareAnswers().then((answers) => {
let promise = Promise.resolve({});
if (!this.showSummary) {
promise = this.prepareAnswers();
}
return promise.then((answers) => {
// Send the answers.
return this.quizProvider.processAttempt(this.quiz, this.attempt, answers, this.preflightData, userFinish, timeUp,
this.offline).catch((error) => {

View File

@ -18,6 +18,7 @@ import { CoreQuestionProvider } from '@core/question/providers/question';
import { CoreQuestionHandler } from '@core/question/providers/delegate';
import { CoreQuestionHelperProvider } from '@core/question/providers/helper';
import { AddonQtypeDdMarkerComponent } from '../component/ddmarker';
import { CoreWSExternalFile } from '@providers/ws';
/**
* Handler to support drag-and-drop markers question type.
@ -119,9 +120,9 @@ export class AddonQtypeDdMarkerHandler implements CoreQuestionHandler {
*
* @param question Question.
* @param usageId Usage ID.
* @return List of URLs.
* @return List of files or URLs.
*/
getAdditionalDownloadableFiles(question: any, usageId: number): string[] {
getAdditionalDownloadableFiles(question: any, usageId: number): (string | CoreWSExternalFile)[] {
this.questionHelper.extractQuestionScripts(question, usageId);
if (question.amdArgs && typeof question.amdArgs[1] == 'string') {

View File

@ -24,6 +24,7 @@ import { CoreQuestionHandler } from '@core/question/providers/delegate';
import { CoreQuestionHelperProvider } from '@core/question/providers/helper';
import { CoreQuestion } from '@core/question/providers/question';
import { AddonQtypeEssayComponent } from '../component/essay';
import { CoreWSExternalFile } from '@providers/ws';
/**
* Handler to support essay question type.
@ -67,6 +68,23 @@ export class AddonQtypeEssayHandler implements CoreQuestionHandler {
return this.questionHelper.deleteStoredQuestionFiles(question, component, componentId, siteId);
}
/**
* Get the list of files that needs to be downloaded in addition to the files embedded in the HTML.
*
* @param question Question.
* @param usageId Usage ID.
* @return List of files or URLs.
*/
getAdditionalDownloadableFiles(question: any, usageId: number): (string | CoreWSExternalFile)[] {
if (!question.responsefileareas) {
return [];
}
return question.responsefileareas.reduce((urlsList, area) => {
return urlsList.concat(area.files || []);
}, []);
}
/**
* Check whether the question allows text and/or attachments.
*
@ -165,8 +183,8 @@ export class AddonQtypeEssayHandler implements CoreQuestionHandler {
return attachments && attachments.length >= Number(question.settings.attachmentsrequired) ? 1 : 0;
}
return (hasTextAnswer || question.settings.responserequired == '0') &&
(attachments && attachments.length > Number(question.settings.attachmentsrequired)) ? 1 : 0;
return ((hasTextAnswer || question.settings.responserequired == '0') &&
(attachments && attachments.length >= Number(question.settings.attachmentsrequired))) ? 1 : 0;
}
/**

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component, ViewChild, ElementRef } from '@angular/core';
import { Component, ViewChild, ElementRef, OnDestroy } from '@angular/core';
import { IonicPage, NavController, NavParams } from 'ionic-angular';
import { TranslateService } from '@ngx-translate/core';
import { CoreAppProvider } from '@providers/app';
@ -25,6 +25,7 @@ import { CoreLoginHelperProvider } from '../../providers/helper';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { CoreConfigConstants } from '../../../../configconstants';
import { CoreCustomURLSchemes } from '@providers/urlschemes';
import { Subscription } from 'rxjs';
/**
* Page to enter the user credentials.
@ -34,7 +35,7 @@ import { CoreCustomURLSchemes } from '@providers/urlschemes';
selector: 'page-core-login-credentials',
templateUrl: 'credentials.html',
})
export class CoreLoginCredentialsPage {
export class CoreLoginCredentialsPage implements OnDestroy {
@ViewChild('credentialsForm') formElement: ElementRef;
@ -57,6 +58,7 @@ export class CoreLoginCredentialsPage {
protected viewLeft = false;
protected siteId: string;
protected urlToOpen: string;
protected valueChangeSubscription: Subscription;
constructor(private navCtrl: NavController,
navParams: NavParams,
@ -89,6 +91,28 @@ export class CoreLoginCredentialsPage {
} else {
this.showScanQR = false;
}
if (appProvider.isIOS()) {
// Make iOS auto-fill work. The field that isn't focused doesn't get updated, do it manually.
// Debounce it to prevent triggering this function too often when the user is typing.
this.valueChangeSubscription = this.credForm.valueChanges.debounceTime(1000).subscribe((changes) => {
if (!this.formElement || !this.formElement.nativeElement) {
return;
}
const usernameInput = this.formElement.nativeElement.querySelector('input[name="username"]');
const passwordInput = this.formElement.nativeElement.querySelector('input[name="password"]');
const usernameValue = usernameInput && usernameInput.value;
const passwordValue = passwordInput && passwordInput.value;
if (typeof usernameValue != 'undefined' && usernameValue != changes.username) {
this.credForm.get('username').setValue(usernameValue);
}
if (typeof passwordValue != 'undefined' && passwordValue != changes.password) {
this.credForm.get('password').setValue(passwordValue);
}
});
}
}
/**
@ -336,4 +360,11 @@ export class CoreLoginCredentialsPage {
}
}
}
/**
* Component destroyed.
*/
ngOnDestroy(): void {
this.valueChangeSubscription && this.valueChangeSubscription.unsubscribe();
}
}

View File

@ -18,6 +18,7 @@ import { CoreEventsProvider } from '@providers/events';
import { CoreSitesProvider } from '@providers/sites';
import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate';
import { CoreQuestionDefaultHandler } from './default-question-handler';
import { CoreWSExternalFile } from '@providers/ws';
/**
* Interface that all question type handlers must implement.
@ -119,9 +120,9 @@ export interface CoreQuestionHandler extends CoreDelegateHandler {
*
* @param question Question.
* @param usageId Usage ID.
* @return List of URLs.
* @return List of files or URLs.
*/
getAdditionalDownloadableFiles?(question: any, usageId: number): string[];
getAdditionalDownloadableFiles?(question: any, usageId: number): (string | CoreWSExternalFile)[];
/**
* Clear temporary data after the data has been saved.
@ -324,9 +325,9 @@ export class CoreQuestionDelegate extends CoreDelegate {
*
* @param question Question.
* @param usageId Usage ID.
* @return List of URLs.
* @return List of files or URLs.
*/
getAdditionalDownloadableFiles(question: any, usageId: number): string[] {
getAdditionalDownloadableFiles(question: any, usageId: number): (string | CoreWSExternalFile)[] {
const type = this.getTypeName(question);
return this.executeFunctionOnEnabled(type, 'getAdditionalDownloadableFiles', [question, usageId]) || [];

View File

@ -590,29 +590,39 @@ export class CoreQuestionHelperProvider {
*/
prefetchQuestionFiles(question: any, component?: string, componentId?: string | number, siteId?: string, usageId?: number)
: Promise<any> {
const urls = this.filepoolProvider.extractDownloadableFilesFromHtml(question.html);
if (!component) {
component = CoreQuestionProvider.COMPONENT;
componentId = question.number;
}
urls.push(...this.questionDelegate.getAdditionalDownloadableFiles(question, usageId));
const files = this.questionDelegate.getAdditionalDownloadableFiles(question, usageId) || [];
files.push(...this.filepoolProvider.extractDownloadableFilesFromHtml(question.html));
return this.sitesProvider.getSite(siteId).then((site) => {
const promises = [];
const treated = {};
urls.forEach((url) => {
if (!site.canDownloadFiles() && this.urlUtils.isPluginFileUrl(url)) {
files.forEach((file) => {
const fileUrl = typeof file == 'string' ? file : file.fileurl;
const timemodified = (typeof file != 'string' && file.timemodified) || 0;
if (treated[fileUrl]) {
return;
}
treated[fileUrl] = true;
if (!site.canDownloadFiles() && this.urlUtils.isPluginFileUrl(fileUrl)) {
return;
}
if (url.indexOf('theme/image.php') > -1 && url.indexOf('flagged') > -1) {
if (fileUrl.indexOf('theme/image.php') > -1 && fileUrl.indexOf('flagged') > -1) {
// Ignore flag images.
return;
}
promises.push(this.filepoolProvider.addToQueueByUrl(siteId, url, component, componentId));
promises.push(this.filepoolProvider.addToQueueByUrl(siteId, fileUrl, component, componentId, timemodified));
});
return Promise.all(promises);