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> { protected processAttempt(userFinish?: boolean, timeUp?: boolean, retrying?: boolean): Promise<any> {
// Get the answers to send. // 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. // Send the answers.
return this.quizProvider.processAttempt(this.quiz, this.attempt, answers, this.preflightData, userFinish, timeUp, return this.quizProvider.processAttempt(this.quiz, this.attempt, answers, this.preflightData, userFinish, timeUp,
this.offline).catch((error) => { 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 { CoreQuestionHandler } from '@core/question/providers/delegate';
import { CoreQuestionHelperProvider } from '@core/question/providers/helper'; import { CoreQuestionHelperProvider } from '@core/question/providers/helper';
import { AddonQtypeDdMarkerComponent } from '../component/ddmarker'; import { AddonQtypeDdMarkerComponent } from '../component/ddmarker';
import { CoreWSExternalFile } from '@providers/ws';
/** /**
* Handler to support drag-and-drop markers question type. * Handler to support drag-and-drop markers question type.
@ -119,9 +120,9 @@ export class AddonQtypeDdMarkerHandler implements CoreQuestionHandler {
* *
* @param question Question. * @param question Question.
* @param usageId Usage ID. * @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); this.questionHelper.extractQuestionScripts(question, usageId);
if (question.amdArgs && typeof question.amdArgs[1] == 'string') { 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 { CoreQuestionHelperProvider } from '@core/question/providers/helper';
import { CoreQuestion } from '@core/question/providers/question'; import { CoreQuestion } from '@core/question/providers/question';
import { AddonQtypeEssayComponent } from '../component/essay'; import { AddonQtypeEssayComponent } from '../component/essay';
import { CoreWSExternalFile } from '@providers/ws';
/** /**
* Handler to support essay question type. * Handler to support essay question type.
@ -67,6 +68,23 @@ export class AddonQtypeEssayHandler implements CoreQuestionHandler {
return this.questionHelper.deleteStoredQuestionFiles(question, component, componentId, siteId); 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. * 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 attachments && attachments.length >= Number(question.settings.attachmentsrequired) ? 1 : 0;
} }
return (hasTextAnswer || question.settings.responserequired == '0') && return ((hasTextAnswer || question.settings.responserequired == '0') &&
(attachments && attachments.length > Number(question.settings.attachmentsrequired)) ? 1 : 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 // See the License for the specific language governing permissions and
// limitations under the License. // 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 { IonicPage, NavController, NavParams } from 'ionic-angular';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { CoreAppProvider } from '@providers/app'; import { CoreAppProvider } from '@providers/app';
@ -25,6 +25,7 @@ import { CoreLoginHelperProvider } from '../../providers/helper';
import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { CoreConfigConstants } from '../../../../configconstants'; import { CoreConfigConstants } from '../../../../configconstants';
import { CoreCustomURLSchemes } from '@providers/urlschemes'; import { CoreCustomURLSchemes } from '@providers/urlschemes';
import { Subscription } from 'rxjs';
/** /**
* Page to enter the user credentials. * Page to enter the user credentials.
@ -34,7 +35,7 @@ import { CoreCustomURLSchemes } from '@providers/urlschemes';
selector: 'page-core-login-credentials', selector: 'page-core-login-credentials',
templateUrl: 'credentials.html', templateUrl: 'credentials.html',
}) })
export class CoreLoginCredentialsPage { export class CoreLoginCredentialsPage implements OnDestroy {
@ViewChild('credentialsForm') formElement: ElementRef; @ViewChild('credentialsForm') formElement: ElementRef;
@ -57,6 +58,7 @@ export class CoreLoginCredentialsPage {
protected viewLeft = false; protected viewLeft = false;
protected siteId: string; protected siteId: string;
protected urlToOpen: string; protected urlToOpen: string;
protected valueChangeSubscription: Subscription;
constructor(private navCtrl: NavController, constructor(private navCtrl: NavController,
navParams: NavParams, navParams: NavParams,
@ -89,6 +91,28 @@ export class CoreLoginCredentialsPage {
} else { } else {
this.showScanQR = false; 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 { CoreSitesProvider } from '@providers/sites';
import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate'; import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate';
import { CoreQuestionDefaultHandler } from './default-question-handler'; import { CoreQuestionDefaultHandler } from './default-question-handler';
import { CoreWSExternalFile } from '@providers/ws';
/** /**
* Interface that all question type handlers must implement. * Interface that all question type handlers must implement.
@ -119,9 +120,9 @@ export interface CoreQuestionHandler extends CoreDelegateHandler {
* *
* @param question Question. * @param question Question.
* @param usageId Usage ID. * @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. * Clear temporary data after the data has been saved.
@ -324,9 +325,9 @@ export class CoreQuestionDelegate extends CoreDelegate {
* *
* @param question Question. * @param question Question.
* @param usageId Usage ID. * @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); const type = this.getTypeName(question);
return this.executeFunctionOnEnabled(type, 'getAdditionalDownloadableFiles', [question, usageId]) || []; 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) prefetchQuestionFiles(question: any, component?: string, componentId?: string | number, siteId?: string, usageId?: number)
: Promise<any> { : Promise<any> {
const urls = this.filepoolProvider.extractDownloadableFilesFromHtml(question.html);
if (!component) { if (!component) {
component = CoreQuestionProvider.COMPONENT; component = CoreQuestionProvider.COMPONENT;
componentId = question.number; 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) => { return this.sitesProvider.getSite(siteId).then((site) => {
const promises = []; const promises = [];
const treated = {};
urls.forEach((url) => { files.forEach((file) => {
if (!site.canDownloadFiles() && this.urlUtils.isPluginFileUrl(url)) { 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; 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. // Ignore flag images.
return; return;
} }
promises.push(this.filepoolProvider.addToQueueByUrl(siteId, url, component, componentId)); promises.push(this.filepoolProvider.addToQueueByUrl(siteId, fileUrl, component, componentId, timemodified));
}); });
return Promise.all(promises); return Promise.all(promises);