From 51e172cd23423aff7f916dc3849259136b0dc7bd Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Fri, 4 Jun 2021 09:29:53 +0200 Subject: [PATCH 1/5] MOBILE-3320 messages: Fix no error displayed when blocking some users --- scripts/langindex.json | 1 + src/addons/messages/lang.json | 1 + src/addons/messages/pages/discussion/discussion.page.ts | 6 ++++++ 3 files changed, 8 insertions(+) diff --git a/scripts/langindex.json b/scripts/langindex.json index 044c28f95..79e24efe1 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -237,6 +237,7 @@ "addon.messages.blocknoncontacts": "message", "addon.messages.blockuser": "message", "addon.messages.blockuserconfirm": "message", + "addon.messages.cantblockuser": "message", "addon.messages.contactableprivacy": "message", "addon.messages.contactableprivacy_coursemember": "message", "addon.messages.contactableprivacy_onlycontacts": "message", diff --git a/src/addons/messages/lang.json b/src/addons/messages/lang.json index a6c556cda..b63ffa55c 100644 --- a/src/addons/messages/lang.json +++ b/src/addons/messages/lang.json @@ -7,6 +7,7 @@ "blocknoncontacts": "Prevent non-contacts from messaging me", "blockuser": "Block user", "blockuserconfirm": "Are you sure you want to block {{$a}}?", + "cantblockuser": "You can't block {{$a}} because they have a role with permission to message all users.", "contactableprivacy": "Accept messages from:", "contactableprivacy_coursemember": "My contacts and anyone in my courses", "contactableprivacy_onlycontacts": "My contacts only", diff --git a/src/addons/messages/pages/discussion/discussion.page.ts b/src/addons/messages/pages/discussion/discussion.page.ts index 41f353973..e107f5756 100644 --- a/src/addons/messages/pages/discussion/discussion.page.ts +++ b/src/addons/messages/pages/discussion/discussion.page.ts @@ -1446,6 +1446,12 @@ export class AddonMessagesDiscussionPage implements OnInit, OnDestroy, AfterView throw new CoreError('No member selected to be blocked.'); } + if (this.otherMember.canmessageevenifblocked) { + CoreDomUtils.showErrorModal(Translate.instant('addon.messages.cantblockuser', { $a: this.otherMember.fullname })); + + return; + } + const template = Translate.instant('addon.messages.blockuserconfirm', { $a: this.otherMember.fullname }); const okText = Translate.instant('addon.messages.blockuser'); From 32810c54f0edc1dd0c91e7cda19b3de66f22c517 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Fri, 4 Jun 2021 10:37:34 +0200 Subject: [PATCH 2/5] MOBILE-3320 login: Fix iOS auto-fill in credentials --- .../login/pages/credentials/credentials.ts | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/core/features/login/pages/credentials/credentials.ts b/src/core/features/login/pages/credentials/credentials.ts index ebde590b7..c50e0ddc5 100644 --- a/src/core/features/login/pages/credentials/credentials.ts +++ b/src/core/features/login/pages/credentials/credentials.ts @@ -14,6 +14,8 @@ import { Component, OnInit, OnDestroy, ViewChild, ElementRef } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { Subscription } from 'rxjs'; +import { debounceTime } from 'rxjs/operators'; import { CoreApp } from '@services/app'; import { CoreSites } from '@services/sites'; @@ -36,7 +38,7 @@ import { CoreForms } from '@singletons/form'; }) export class CoreLoginCredentialsPage implements OnInit, OnDestroy { - @ViewChild('credentialsForm') formElement?: ElementRef; + @ViewChild('credentialsForm') formElement?: ElementRef; credForm!: FormGroup; siteUrl!: string; @@ -57,6 +59,7 @@ export class CoreLoginCredentialsPage implements OnInit, OnDestroy { protected viewLeft = false; protected siteId?: string; protected urlToOpen?: string; + protected valueChangeSubscription?: Subscription; constructor( protected fb: FormBuilder, @@ -96,6 +99,28 @@ export class CoreLoginCredentialsPage implements OnInit, OnDestroy { this.siteChecked = true; this.pageLoaded = true; } + + if (CoreApp.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.pipe(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?.value; + const passwordValue = passwordInput?.value; + + if (usernameValue !== undefined && usernameValue !== changes.username) { + this.credForm.get('username')?.setValue(usernameValue); + } + if (passwordValue !== undefined && passwordValue !== changes.password) { + this.credForm.get('password')?.setValue(passwordValue); + } + }); + } } /** @@ -307,6 +332,7 @@ export class CoreLoginCredentialsPage implements OnInit, OnDestroy { ngOnDestroy(): void { this.viewLeft = true; CoreEvents.trigger(CoreEvents.LOGIN_SITE_UNCHECKED, { config: this.siteConfig }, this.siteId); + this.valueChangeSubscription?.unsubscribe(); } } From 7cc8fa0b9eb160dd8a416108216c0441c6c480e2 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Fri, 4 Jun 2021 12:56:13 +0200 Subject: [PATCH 3/5] MOBILE-3320 android: Fix back button with leave guards --- src/app/app.component.ts | 13 +++++++++---- src/core/features/login/services/login-helper.ts | 8 ++------ src/core/features/mainmenu/pages/menu/menu.ts | 14 ++++---------- .../features/mainmenu/pages/more/more.module.ts | 3 +++ src/core/services/app.ts | 8 ++++++++ 5 files changed, 26 insertions(+), 20 deletions(-) diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 7457bda23..713c57567 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -224,11 +224,17 @@ export class AppComponent implements OnInit, AfterViewInit { document.addEventListener('ionBackButton', (event: BackButtonEvent) => { // This callback should have the lowest priority in the app. event.detail.register(-100, async () => { + const initialPath = CoreNavigator.getCurrentPath(); + if (initialPath.startsWith('/main/')) { + // Main menu has its own callback to handle back. If this callback is called it means we should exit app. + CoreApp.closeApp(); + + return; + } + // This callback can be called at the same time as Ionic's back navigation callback. // Check if the path changes due to the back navigation handler, to know if we're at root level. // Ionic doc recommends IonRouterOutlet.canGoBack, but there's no easy way to get the current outlet from here. - const initialPath = CoreNavigator.getCurrentPath(); - // The path seems to change immediately (0 ms timeout), but use 50ms just in case. await CoreUtils.wait(50); @@ -238,8 +244,7 @@ export class AppComponent implements OnInit, AfterViewInit { } // Quit the app. - const nav = window.navigator; // eslint-disable-line @typescript-eslint/no-explicit-any - nav.app?.exitApp(); + CoreApp.closeApp(); }); }); } diff --git a/src/core/features/login/services/login-helper.ts b/src/core/features/login/services/login-helper.ts index 5ebfdfe8e..955ac5457 100644 --- a/src/core/features/login/services/login-helper.ts +++ b/src/core/features/login/services/login-helper.ts @@ -661,9 +661,7 @@ export class CoreLoginHelperProvider { // Always open it in browser because the user might have the session stored in there. CoreUtils.openInBrowser(loginUrl); - - const nav = window.navigator; // eslint-disable-line @typescript-eslint/no-explicit-any - nav.app?.exitApp(); + CoreApp.closeApp(); return true; } @@ -695,9 +693,7 @@ export class CoreLoginHelperProvider { }); } else { CoreUtils.openInBrowser(loginUrl); - - const nav = window.navigator; // eslint-disable-line @typescript-eslint/no-explicit-any - nav.app?.exitApp(); + CoreApp.closeApp(); } } diff --git a/src/core/features/mainmenu/pages/menu/menu.ts b/src/core/features/mainmenu/pages/menu/menu.ts index 0bba478b1..b2c968bbd 100644 --- a/src/core/features/mainmenu/pages/menu/menu.ts +++ b/src/core/features/mainmenu/pages/menu/menu.ts @@ -226,16 +226,10 @@ export class CoreMainMenuPage implements OnInit, OnDestroy { // Use a priority lower than 0 (navigation). event.detail.register(-10, async (processNextHandler: () => void) => { // This callback can be called at the same time as Ionic's back navigation callback. - // Check if the path changes due to the back navigation handler, to know if we're at root level of the tab. - // Ionic doc recommends IonRouterOutlet.canGoBack, but there's no easy way to get the current outlet from here. - const initialPath = CoreNavigator.getCurrentPath(); - - // The path seems to change immediately (0 ms timeout), but use 50ms just in case. - await CoreUtils.wait(50); - - if (CoreNavigator.getCurrentPath() != initialPath) { - // Ionic has navigated back, nothing else to do. - return; + // Check if user is already at the root of a tab. + const mainMenuRootRoute = CoreNavigator.getCurrentRoute({ routeData: { isMainMenuRoot: true } }); + if (!mainMenuRootRoute) { + return; // Not at root level, let Ionic handle the navigation. } // No back navigation, already at root level. Check if we should change tab. diff --git a/src/core/features/mainmenu/pages/more/more.module.ts b/src/core/features/mainmenu/pages/more/more.module.ts index 48cd31b3b..e1a10ac61 100644 --- a/src/core/features/mainmenu/pages/more/more.module.ts +++ b/src/core/features/mainmenu/pages/more/more.module.ts @@ -30,6 +30,9 @@ import { buildTabMainRoutes } from '@features/mainmenu/mainmenu-tab-routing.modu deps: [Injector], useFactory: (injector: Injector) => buildTabMainRoutes(injector, { component: CoreMainMenuMorePage, + data: { + isMainMenuRoot: true, + }, }), }, ], diff --git a/src/core/services/app.ts b/src/core/services/app.ts index 8430448e2..27dc93587 100644 --- a/src/core/services/app.ts +++ b/src/core/services/app.ts @@ -562,6 +562,14 @@ export class CoreAppProvider { return redirect; } + /** + * Close the app. + */ + closeApp(): void { + const nav = window.navigator; // eslint-disable-line @typescript-eslint/no-explicit-any + nav.app?.exitApp(); + } + /** * Forget redirect data. */ From c15d5de95d31324aca614be561cd05dc21e5e9f3 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Fri, 4 Jun 2021 15:33:24 +0200 Subject: [PATCH 4/5] MOBILE-3320 sync: Fix cannot read 'updated' of undefined --- src/addons/mod/assign/components/index/index.ts | 4 ++-- src/addons/mod/assign/pages/edit/edit.ts | 6 +++--- src/addons/mod/data/components/index/index.ts | 4 ++-- src/addons/mod/survey/components/index/index.ts | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/addons/mod/assign/components/index/index.ts b/src/addons/mod/assign/components/index/index.ts index 83b84b236..edf0e7144 100644 --- a/src/addons/mod/assign/components/index/index.ts +++ b/src/addons/mod/assign/components/index/index.ts @@ -407,8 +407,8 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo * * @return Promise resolved when done. */ - protected async sync(): Promise { - await AddonModAssignSync.syncAssign(this.assign!.id); + protected sync(): Promise { + return AddonModAssignSync.syncAssign(this.assign!.id); } /** diff --git a/src/addons/mod/assign/pages/edit/edit.ts b/src/addons/mod/assign/pages/edit/edit.ts index 43dd2a858..98884b13e 100644 --- a/src/addons/mod/assign/pages/edit/edit.ts +++ b/src/addons/mod/assign/pages/edit/edit.ts @@ -235,12 +235,12 @@ export class AddonModAssignEditPage implements OnInit, OnDestroy, CanLeave { * @param inputData The input data. * @return Promise resolved with the data to submit. */ - protected prepareSubmissionData(inputData: CoreFormFields): Promise { + protected async prepareSubmissionData(inputData: CoreFormFields): Promise { // If there's offline data, always save it in offline. this.saveOffline = this.hasOffline; try { - return AddonModAssignHelper.prepareSubmissionPluginData( + return await AddonModAssignHelper.prepareSubmissionPluginData( this.assign!, this.userSubmission, inputData, @@ -251,7 +251,7 @@ export class AddonModAssignEditPage implements OnInit, OnDestroy, CanLeave { // Cannot submit in online, prepare for offline usage. this.saveOffline = true; - return AddonModAssignHelper.prepareSubmissionPluginData( + return await AddonModAssignHelper.prepareSubmissionPluginData( this.assign!, this.userSubmission, inputData, diff --git a/src/addons/mod/data/components/index/index.ts b/src/addons/mod/data/components/index/index.ts index 4f27405a5..c99612586 100644 --- a/src/addons/mod/data/components/index/index.ts +++ b/src/addons/mod/data/components/index/index.ts @@ -514,8 +514,8 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp * * @return Promise resolved when done. */ - protected async sync(): Promise { - await AddonModDataPrefetchHandler.sync(this.module, this.courseId); + protected sync(): Promise { + return AddonModDataPrefetchHandler.sync(this.module, this.courseId); } /** diff --git a/src/addons/mod/survey/components/index/index.ts b/src/addons/mod/survey/components/index/index.ts index 38db91dd8..8979f9e03 100644 --- a/src/addons/mod/survey/components/index/index.ts +++ b/src/addons/mod/survey/components/index/index.ts @@ -233,8 +233,8 @@ export class AddonModSurveyIndexComponent extends CoreCourseModuleMainActivityCo * * @return Promise resolved when done. */ - protected async sync(): Promise { - await AddonModSurveySync.syncSurvey(this.survey!.id, this.currentUserId); + protected sync(): Promise { + return AddonModSurveySync.syncSurvey(this.survey!.id, this.currentUserId); } /** From ae7b380a7269e708e7e12b6a254221b3965487b7 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Fri, 4 Jun 2021 16:04:40 +0200 Subject: [PATCH 5/5] MOBILE-3320 signup: Expire recaptcha if error returned --- src/core/components/recaptcha/core-recaptcha.html | 2 +- src/core/components/recaptcha/recaptcha.ts | 11 +++++++++-- .../features/login/pages/email-signup/email-signup.ts | 11 ++++++++--- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/core/components/recaptcha/core-recaptcha.html b/src/core/components/recaptcha/core-recaptcha.html index 9ce50edcf..d332e8958 100644 --- a/src/core/components/recaptcha/core-recaptcha.html +++ b/src/core/components/recaptcha/core-recaptcha.html @@ -7,7 +7,7 @@ {{ 'core.answered' | translate }} - + {{ 'core.login.recaptchaexpired' | translate }} diff --git a/src/core/components/recaptcha/recaptcha.ts b/src/core/components/recaptcha/recaptcha.ts index 987acf1b0..45e1c8934 100644 --- a/src/core/components/recaptcha/recaptcha.ts +++ b/src/core/components/recaptcha/recaptcha.ts @@ -87,8 +87,7 @@ export class CoreRecaptchaComponent implements OnInit { } if (event.data.action == 'expired') { - this.expired = true; - this.model![this.modelValueName] = ''; + this.expireRecaptchaAnswer(); } else if (event.data.action == 'callback') { this.expired = false; this.model![this.modelValueName] = event.data.value; @@ -101,4 +100,12 @@ export class CoreRecaptchaComponent implements OnInit { }); } + /** + * Expire the recaptcha answer. + */ + expireRecaptchaAnswer(): void { + this.expired = true; + this.model![this.modelValueName] = ''; + } + } diff --git a/src/core/features/login/pages/email-signup/email-signup.ts b/src/core/features/login/pages/email-signup/email-signup.ts index 0d6f53851..7e2462f6d 100644 --- a/src/core/features/login/pages/email-signup/email-signup.ts +++ b/src/core/features/login/pages/email-signup/email-signup.ts @@ -33,6 +33,7 @@ import { } from '@features/login/services/login-helper'; import { CoreNavigator } from '@services/navigator'; import { CoreForms } from '@singletons/form'; +import { CoreRecaptchaComponent } from '@components/recaptcha/recaptcha'; /** * Page to signup using email. @@ -45,6 +46,7 @@ import { CoreForms } from '@singletons/form'; export class CoreLoginEmailSignupPage implements OnInit { @ViewChild(IonContent) content?: IonContent; + @ViewChild(CoreRecaptchaComponent) recaptchaComponent?: CoreRecaptchaComponent; @ViewChild('ageForm') ageFormElement?: ElementRef; @ViewChild('signupFormEl') signupFormElement?: ElementRef; @@ -341,9 +343,12 @@ export class CoreLoginEmailSignupPage implements OnInit { CoreDomUtils.showAlert(Translate.instant('core.success'), message); CoreNavigator.back(); } else { - if (result.warnings && result.warnings.length) { - let error = result.warnings[0].message; - if (error == 'incorrect-captcha-sol') { + this.recaptchaComponent?.expireRecaptchaAnswer(); + + const warning = result.warnings?.[0]; + if (warning) { + let error = warning.message; + if (error == 'incorrect-captcha-sol' || (!error && warning.item == 'recaptcharesponse')) { error = Translate.instant('core.login.recaptchaincorrect'); }