diff --git a/package-lock.json b/package-lock.json index dc953fc66..b85a06741 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47,7 +47,6 @@ "@moodlehq/cordova-plugin-inappbrowser": "5.0.0-moodle.3", "@moodlehq/cordova-plugin-intent": "2.2.0-moodle.3", "@moodlehq/cordova-plugin-ionic-webview": "5.0.0-moodle.4", - "@moodlehq/cordova-plugin-local-notification": "0.9.0-moodle.12", "@moodlehq/cordova-plugin-qrscanner": "3.0.1-moodle.5", "@moodlehq/cordova-plugin-statusbar": "4.0.0-moodle.3", "@moodlehq/cordova-plugin-zip": "3.1.0-moodle.1", @@ -72,6 +71,7 @@ "cordova-plugin-file": "^8.0.1", "cordova-plugin-geolocation": "^5.0.0", "cordova-plugin-ionic-keyboard": "^2.2.0", + "cordova-plugin-local-notification": "1.0.0", "cordova-plugin-media-capture": "^5.0.0", "cordova-plugin-network-information": "^3.0.0", "cordova-plugin-prevent-override": "^1.0.1", @@ -5408,33 +5408,6 @@ } } }, - "node_modules/@moodlehq/cordova-plugin-local-notification": { - "version": "0.9.0-moodle.12", - "resolved": "https://registry.npmjs.org/@moodlehq/cordova-plugin-local-notification/-/cordova-plugin-local-notification-0.9.0-moodle.12.tgz", - "integrity": "sha512-gt6BhqsltCnNmk/CRUIDxTha/c1/UGTsh2d15zoUVeGaKYqE6olmIyN/HCAn+ofy7CA6DNHOdm1v0uqC3YbPZg==", - "engines": [ - { - "name": "cordova", - "version": ">=3.6.0" - }, - { - "name": "cordova-android", - "version": ">=6.0.0" - }, - { - "name": "cordova-windows", - "version": ">=4.2.0" - }, - { - "name": "android-sdk", - "version": ">=26" - }, - { - "name": "apple-ios", - "version": ">=10.0.0" - } - ] - }, "node_modules/@moodlehq/cordova-plugin-qrscanner": { "version": "3.0.1-moodle.5", "resolved": "https://registry.npmjs.org/@moodlehq/cordova-plugin-qrscanner/-/cordova-plugin-qrscanner-3.0.1-moodle.5.tgz", @@ -11263,6 +11236,25 @@ "resolved": "https://registry.npmjs.org/cordova-plugin-ionic-keyboard/-/cordova-plugin-ionic-keyboard-2.2.0.tgz", "integrity": "sha512-yDUG+9ieKVRitq5mGlNxjaZh/MgEhFFIgTIPhqSbUaQ8UuZbawy5mhJAVClqY97q8/rcQtL6dCDa7x2sEtCLcA==" }, + "node_modules/cordova-plugin-local-notification": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/cordova-plugin-local-notification/-/cordova-plugin-local-notification-1.0.0.tgz", + "integrity": "sha512-dhw00d38cRo/6JlDR7i4u4u+In7wkzH5ffDnIZVzverct9XLcDyxs6WnPUGmWrpUjK6CpXrn3aGmt5kwkck+8g==", + "engines": { + "cordovaDependencies": { + "0.9.0-beta.3": { + "cordova": ">=3.6.0", + "cordova-android": ">=6.0.0", + "cordova-ios": ">=10.0.0" + }, + "1.0.0": { + "cordova": ">=12.0.0", + "cordova-android": ">=13.0.0", + "cordova-ios": ">=10.0.0" + } + } + } + }, "node_modules/cordova-plugin-media-capture": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/cordova-plugin-media-capture/-/cordova-plugin-media-capture-5.0.0.tgz", diff --git a/package.json b/package.json index 8dc45b8f9..8aa6aa200 100644 --- a/package.json +++ b/package.json @@ -81,7 +81,6 @@ "@moodlehq/cordova-plugin-inappbrowser": "5.0.0-moodle.3", "@moodlehq/cordova-plugin-intent": "2.2.0-moodle.3", "@moodlehq/cordova-plugin-ionic-webview": "5.0.0-moodle.4", - "@moodlehq/cordova-plugin-local-notification": "0.9.0-moodle.12", "@moodlehq/cordova-plugin-qrscanner": "3.0.1-moodle.5", "@moodlehq/cordova-plugin-statusbar": "4.0.0-moodle.3", "@moodlehq/cordova-plugin-zip": "3.1.0-moodle.1", @@ -106,6 +105,7 @@ "cordova-plugin-file": "^8.0.1", "cordova-plugin-geolocation": "^5.0.0", "cordova-plugin-ionic-keyboard": "^2.2.0", + "cordova-plugin-local-notification": "1.0.0", "cordova-plugin-media-capture": "^5.0.0", "cordova-plugin-network-information": "^3.0.0", "cordova-plugin-prevent-override": "^1.0.1", @@ -201,9 +201,6 @@ "@moodlehq/cordova-plugin-inappbrowser": {}, "@moodlehq/cordova-plugin-intent": {}, "@moodlehq/cordova-plugin-ionic-webview": {}, - "@moodlehq/cordova-plugin-local-notification": { - "ANDROID_SUPPORT_V4_VERSION": "26.+" - }, "@moodlehq/cordova-plugin-qrscanner": {}, "@moodlehq/cordova-plugin-statusbar": {}, "@moodlehq/cordova-plugin-zip": {}, @@ -227,6 +224,9 @@ "GPS_REQUIRED": "false" }, "cordova-plugin-ionic-keyboard": {}, + "cordova-plugin-local-notification": { + "ANDROID_SUPPORT_V4_VERSION": "26.+" + }, "cordova-plugin-media-capture": {}, "cordova-plugin-moodleapp": {}, "cordova-plugin-network-information": {}, diff --git a/src/addons/blog/pages/index/index.ts b/src/addons/blog/pages/index/index.ts index 1bdab2551..c1cc8e4fc 100644 --- a/src/addons/blog/pages/index/index.ts +++ b/src/addons/blog/pages/index/index.ts @@ -29,7 +29,6 @@ import { AddonBlogOffline, AddonBlogOfflineEntry } from '@addons/blog/services/b import { AddonBlogSync } from '@addons/blog/services/blog-sync'; import { Component, computed, OnDestroy, OnInit, signal } from '@angular/core'; import { CoreComments } from '@features/comments/services/comments'; -import { CoreMainMenuDeepLinkManager } from '@features/mainmenu/classes/deep-link-manager'; import { CoreTag } from '@features/tag/services/tag'; import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; import { CoreNavigator } from '@services/navigator'; @@ -202,8 +201,7 @@ export class AddonBlogIndexPage implements OnInit, OnDestroy { this.commentsEnabled = CoreComments.areCommentsEnabledInSite(); this.tagsEnabled = CoreTag.areTagsAvailableInSite(); - const deepLinkManager = new CoreMainMenuDeepLinkManager(); - deepLinkManager.treatLink(); + CoreSites.loginNavigationFinished(); await this.fetchEntries(false, false, true); this.optionsAvailable = await AddonBlog.isEditingEnabled(); diff --git a/src/addons/calendar/pages/index/index.ts b/src/addons/calendar/pages/index/index.ts index 7ee837bf6..7bfa6e2d6 100644 --- a/src/addons/calendar/pages/index/index.ts +++ b/src/addons/calendar/pages/index/index.ts @@ -30,7 +30,6 @@ import { AddonCalendarCalendarComponent } from '../../components/calendar/calend import { AddonCalendarUpcomingEventsComponent } from '../../components/upcoming-events/upcoming-events'; import { CoreNavigator } from '@services/navigator'; import { CoreConstants } from '@/core/constants'; -import { CoreMainMenuDeepLinkManager } from '@features/mainmenu/classes/deep-link-manager'; import { CoreModals } from '@services/modals'; /** @@ -178,8 +177,7 @@ export class AddonCalendarIndexPage implements OnInit, OnDestroy { } }); - const deepLinkManager = new CoreMainMenuDeepLinkManager(); - deepLinkManager.treatLink(); + CoreSites.loginNavigationFinished(); } /** diff --git a/src/addons/messages/pages/discussions-35/discussions.ts b/src/addons/messages/pages/discussions-35/discussions.ts index c44a252ad..b6375820b 100644 --- a/src/addons/messages/pages/discussions-35/discussions.ts +++ b/src/addons/messages/pages/discussions-35/discussions.ts @@ -30,7 +30,6 @@ import { Subscription } from 'rxjs'; import { Translate } from '@singletons'; import { CoreNavigator } from '@services/navigator'; import { CoreScreen } from '@services/screen'; -import { CoreMainMenuDeepLinkManager } from '@features/mainmenu/classes/deep-link-manager'; import { CorePlatform } from '@services/platform'; import { CoreSplitViewComponent } from '@components/split-view/split-view'; import { CoreKeyboard } from '@singletons/keyboard'; @@ -145,8 +144,6 @@ export class AddonMessagesDiscussions35Page implements OnInit, OnDestroy { this.discussionUserId = CoreNavigator.getRouteNumberParam('userId', { params }) ?? this.discussionUserId; }); - const deepLinkManager = new CoreMainMenuDeepLinkManager(); - await this.fetchData(); if (!this.discussionUserId && this.discussions.length > 0 && CoreScreen.isTablet && this.discussions[0].message) { @@ -154,8 +151,8 @@ export class AddonMessagesDiscussions35Page implements OnInit, OnDestroy { await this.gotoDiscussion(this.discussions[0].message.user); } - // Treat deep link now that the conversation route has been loaded if needed. - deepLinkManager.treatLink(); + // Mark login navigation finished now that the conversation route has been loaded if needed. + CoreSites.loginNavigationFinished(); } /** diff --git a/src/addons/messages/pages/group-conversations/group-conversations.ts b/src/addons/messages/pages/group-conversations/group-conversations.ts index 20fbedc11..03f1eca6e 100644 --- a/src/addons/messages/pages/group-conversations/group-conversations.ts +++ b/src/addons/messages/pages/group-conversations/group-conversations.ts @@ -38,7 +38,6 @@ import { ActivatedRoute, Params } from '@angular/router'; import { CoreUtils } from '@services/utils/utils'; import { CoreNavigator } from '@services/navigator'; import { CoreScreen } from '@services/screen'; -import { CoreMainMenuDeepLinkManager } from '@features/mainmenu/classes/deep-link-manager'; import { CorePlatform } from '@services/platform'; import { CoreSplitViewComponent } from '@components/split-view/split-view'; @@ -312,8 +311,6 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { } }); - const deepLinkManager = new CoreMainMenuDeepLinkManager(); - await this.fetchData(); if (!this.selectedConversationId && !this.selectedUserId && CoreScreen.isTablet) { @@ -326,8 +323,8 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { } } - // Treat deep link now that the conversation route has been loaded if needed. - deepLinkManager.treatLink(); + // Mark login navigation finished now that the conversation route has been loaded if needed. + CoreSites.loginNavigationFinished(); } /** diff --git a/src/addons/notifications/pages/list/list.ts b/src/addons/notifications/pages/list/list.ts index 52ab8136b..79602218e 100644 --- a/src/addons/notifications/pages/list/list.ts +++ b/src/addons/notifications/pages/list/list.ts @@ -26,7 +26,6 @@ import { CoreSplitViewComponent } from '@components/split-view/split-view'; import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker'; import { CorePushNotificationsDelegate } from '@features/pushnotifications/services/push-delegate'; import { CoreSites } from '@services/sites'; -import { CoreMainMenuDeepLinkManager } from '@features/mainmenu/classes/deep-link-manager'; import { CoreTimeUtils } from '@services/utils/time'; import { AddonNotificationsNotificationsSource } from '@addons/notifications/classes/notifications-source'; import { CoreListItemsManager } from '@classes/items-management/list-items-manager'; @@ -130,8 +129,7 @@ export class AddonNotificationsListPage implements AfterViewInit, OnDestroy { this.loadMarkAllAsReadButton(); }); - const deepLinkManager = new CoreMainMenuDeepLinkManager(); - deepLinkManager.treatLink(); + CoreSites.loginNavigationFinished(); } /** diff --git a/src/core/classes/queue-runner.ts b/src/core/classes/queue-runner.ts index 6795af7ad..ff0fcc69b 100644 --- a/src/core/classes/queue-runner.ts +++ b/src/core/classes/queue-runner.ts @@ -39,6 +39,11 @@ export type CoreQueueRunnerItem = { * Deferred with a promise resolved/rejected with the result of the function. */ deferred: CorePromisedValue; + + /** + * Item's priority. Only used if usePriority=true. + */ + priority: number; }; /** @@ -49,6 +54,12 @@ export type CoreQueueRunnerAddOptions = { * Whether to allow having multiple entries with same ID in the queue. */ allowRepeated?: boolean; + + /** + * If usePriority=true, the priority of the item. Higher priority means it will be executed first. + * Please notice that the first item is always run immediately, so it's not affected by the priority. + */ + priority?: number; }; /** @@ -60,7 +71,13 @@ export class CoreQueueRunner { protected orderedQueue: CoreQueueRunnerItem[] = []; protected numberRunning = 0; - constructor(protected maxParallel: number = 1) { } + /** + * Constructor. + * + * @param maxParallel Max number of parallel executions. + * @param usePriority If true, the queue will be ordered by priority. + */ + constructor(protected maxParallel: number = 1, protected usePriority = false) { } /** * Get unique ID. @@ -147,10 +164,14 @@ export class CoreQueueRunner { id, fn, deferred: new CorePromisedValue(), + priority: options.priority ?? 0, }; this.queue[id] = item; this.orderedQueue.push(item); + if (this.usePriority) { + this.orderedQueue.sort((a, b) => b.priority - a.priority); + } // Process next item if we haven't reached the max yet. this.processNextItem(); diff --git a/src/core/features/courses/pages/my/my.ts b/src/core/features/courses/pages/my/my.ts index f4f1ff63b..370e9b244 100644 --- a/src/core/features/courses/pages/my/my.ts +++ b/src/core/features/courses/pages/my/my.ts @@ -21,7 +21,6 @@ import { CoreBlockComponent } from '@features/block/components/block/block'; import { CoreBlockDelegate } from '@features/block/services/block-delegate'; import { CoreCourseBlock } from '@features/course/services/course'; import { CoreCoursesDashboard, CoreCoursesDashboardProvider } from '@features/courses/services/dashboard'; -import { CoreMainMenuDeepLinkManager } from '@features/mainmenu/classes/deep-link-manager'; import { CoreSites } from '@services/sites'; import { CoreDomUtils } from '@services/utils/dom'; import { CoreUtils } from '@services/utils/utils'; @@ -97,8 +96,7 @@ export class CoreCoursesMyPage implements OnInit, OnDestroy, AsyncDirective { async ngOnInit(): Promise { this.downloadCoursesEnabled = !CoreCourses.isDownloadCoursesDisabledInSite(); - const deepLinkManager = new CoreMainMenuDeepLinkManager(); - deepLinkManager.treatLink(); + CoreSites.loginNavigationFinished(); await this.loadSiteName(); diff --git a/src/core/features/mainmenu/classes/deep-link-manager.ts b/src/core/features/mainmenu/classes/deep-link-manager.ts index 2dc0ceb6e..9cb68bc0b 100644 --- a/src/core/features/mainmenu/classes/deep-link-manager.ts +++ b/src/core/features/mainmenu/classes/deep-link-manager.ts @@ -50,18 +50,18 @@ export class CoreMainMenuDeepLinkManager { /** * Treat a deep link if there's any to treat. */ - treatLink(): void { - if (!this.pendingRedirect) { + async treatLink(): Promise { + const pendingRedirect = this.pendingRedirect; + if (!pendingRedirect) { return; } - if (this.pendingRedirect.redirectPath) { - this.treatPath(this.pendingRedirect.redirectPath, this.pendingRedirect.redirectOptions); - } else if (this.pendingRedirect.urlToOpen) { - this.treatUrlToOpen(this.pendingRedirect.urlToOpen); - } - delete this.pendingRedirect; + if (pendingRedirect.redirectPath) { + await this.treatPath(pendingRedirect.redirectPath, pendingRedirect.redirectOptions); + } else if (pendingRedirect.urlToOpen) { + await this.treatUrlToOpen(pendingRedirect.urlToOpen); + } } /** @@ -70,18 +70,18 @@ export class CoreMainMenuDeepLinkManager { * @param path Path. * @param navOptions Navigation options. */ - protected treatPath(path: string, navOptions: CoreNavigationOptions = {}): void { + protected async treatPath(path: string, navOptions: CoreNavigationOptions = {}): Promise { const params = navOptions.params; const coursePathMatches = path.match(/^course\/(\d+)\/?$/); if (coursePathMatches) { if (!params?.course) { - CoreCourseHelper.getAndOpenCourse(Number(coursePathMatches[1]), params); + await CoreCourseHelper.getAndOpenCourse(Number(coursePathMatches[1]), params); } else { - CoreCourse.openCourse(params.course, navOptions); + await CoreCourse.openCourse(params.course, navOptions); } } else { - CoreNavigator.navigateToSitePath(path, { + await CoreNavigator.navigateToSitePath(path, { ...navOptions, preferCurrentTab: false, }); @@ -96,7 +96,7 @@ export class CoreMainMenuDeepLinkManager { protected async treatUrlToOpen(url: string): Promise { const action = await CoreContentLinksHelper.getFirstValidActionFor(url); if (action?.sites?.[0]) { - action.action(action.sites[0]); + await action.action(action.sites[0]); } } diff --git a/src/core/features/mainmenu/pages/home/home.ts b/src/core/features/mainmenu/pages/home/home.ts index ac827d54f..ab928b4c7 100644 --- a/src/core/features/mainmenu/pages/home/home.ts +++ b/src/core/features/mainmenu/pages/home/home.ts @@ -21,7 +21,6 @@ import { CoreTabsOutletComponent, CoreTabsOutletTab } from '@components/tabs-out import { CoreMainMenuHomeDelegate, CoreMainMenuHomeHandlerToDisplay } from '../../services/home-delegate'; import { CoreUtils } from '@services/utils/utils'; import { CoreMainMenuHomeHandlerService } from '@features/mainmenu/services/handlers/mainmenu'; -import { CoreMainMenuDeepLinkManager } from '@features/mainmenu/classes/deep-link-manager'; /** * Page that displays the Home. @@ -40,14 +39,11 @@ export class CoreMainMenuHomePage implements OnInit { protected subscription?: Subscription; protected updateSiteObserver?: CoreEventObserver; - protected deepLinkManager?: CoreMainMenuDeepLinkManager; /** * @inheritdoc */ async ngOnInit(): Promise { - this.deepLinkManager = new CoreMainMenuDeepLinkManager(); - await this.loadSiteName(); this.subscription = CoreMainMenuHomeDelegate.getHandlersObservable().subscribe((handlers) => { @@ -108,7 +104,7 @@ export class CoreMainMenuHomePage implements OnInit { * Tab was selected. */ tabSelected(): void { - this.deepLinkManager?.treatLink(); + CoreSites.loginNavigationFinished(); } /** diff --git a/src/core/features/mainmenu/pages/menu/menu.ts b/src/core/features/mainmenu/pages/menu/menu.ts index 1c62bf7ad..d2c43a7f0 100644 --- a/src/core/features/mainmenu/pages/menu/menu.ts +++ b/src/core/features/mainmenu/pages/menu/menu.ts @@ -23,7 +23,7 @@ import { CoreMainMenuDelegate, CoreMainMenuHandlerToDisplay } from '../../servic import { Router } from '@singletons'; import { CoreUtils } from '@services/utils/utils'; import { CoreAriaRoleTab, CoreAriaRoleTabFindable } from '@classes/aria-role-tab'; -import { CoreNavigationOptions, CoreNavigator } from '@services/navigator'; +import { CoreNavigator } from '@services/navigator'; import { filter } from 'rxjs/operators'; import { NavigationEnd } from '@angular/router'; import { trigger, state, style, transition, animate } from '@angular/animations'; @@ -32,6 +32,7 @@ import { CoreDom } from '@singletons/dom'; import { CoreLogger } from '@singletons/logger'; import { CorePlatform } from '@services/platform'; import { CoreWait } from '@singletons/wait'; +import { CoreMainMenuDeepLinkManager } from '@features/mainmenu/classes/deep-link-manager'; const ANIMATION_DURATION = 500; @@ -83,9 +84,6 @@ export class CoreMainMenuPage implements OnInit, OnDestroy { protected backButtonFunction: (event: BackButtonEvent) => void; protected selectHistory: string[] = []; protected firstSelectedTab?: string; - protected urlToOpen?: string; - protected redirectPath?: string; - protected redirectOptions?: CoreNavigationOptions; protected logger: CoreLogger; @ViewChild('mainTabs') mainTabs?: IonTabs; @@ -111,9 +109,16 @@ export class CoreMainMenuPage implements OnInit, OnDestroy { */ async ngOnInit(): Promise { this.showTabs = true; - this.urlToOpen = CoreNavigator.getRouteParam('urlToOpen'); - this.redirectPath = CoreNavigator.getRouteParam('redirectPath'); - this.redirectOptions = CoreNavigator.getRouteParam('redirectOptions'); + + const deepLinkManager = new CoreMainMenuDeepLinkManager(); + + // Treat the deep link (if any) when the login navigation finishes. + CoreSites.runAfterLoginNavigation({ + priority: 800, + callback: async () => { + await deepLinkManager.treatLink(); + }, + }); this.isMainScreen = !this.mainTabs?.outlet.canGoBack(); this.updateVisibility(); @@ -213,12 +218,7 @@ export class CoreMainMenuPage implements OnInit, OnDestroy { // Use navigate instead of mainTabs.select to be able to pass page params. CoreNavigator.navigateToSitePath(tabPage, { preferCurrentTab: false, - params: { - urlToOpen: this.urlToOpen, - redirectPath: this.redirectPath, - redirectOptions: this.redirectOptions, - ...tabPageParams, - }, + params: tabPageParams, }); } } diff --git a/src/core/features/mainmenu/pages/more/more.ts b/src/core/features/mainmenu/pages/more/more.ts index 5571d74d8..d20ae7e31 100644 --- a/src/core/features/mainmenu/pages/more/more.ts +++ b/src/core/features/mainmenu/pages/more/more.ts @@ -22,7 +22,6 @@ import { CoreMainMenu, CoreMainMenuCustomItem } from '../../services/mainmenu'; import { CoreEventObserver, CoreEvents } from '@singletons/events'; import { CoreNavigator } from '@services/navigator'; import { Translate } from '@singletons'; -import { CoreMainMenuDeepLinkManager } from '@features/mainmenu/classes/deep-link-manager'; import { CoreDom } from '@singletons/dom'; import { CoreViewer } from '@features/viewer/services/viewer'; @@ -75,8 +74,7 @@ export class CoreMainMenuMorePage implements OnInit, OnDestroy { this.initHandlers(); }); - const deepLinkManager = new CoreMainMenuDeepLinkManager(); - deepLinkManager.treatLink(); + CoreSites.loginNavigationFinished(); } /** diff --git a/src/core/features/pushnotifications/services/push-delegate.ts b/src/core/features/pushnotifications/services/push-delegate.ts index 5b519744f..af24f47c9 100644 --- a/src/core/features/pushnotifications/services/push-delegate.ts +++ b/src/core/features/pushnotifications/services/push-delegate.ts @@ -108,7 +108,7 @@ export class CorePushNotificationsDelegateService { handlers = handlers.sort((a, b) => (a.priority || 0) <= (b.priority || 0) ? 1 : -1); // Execute the first one. - handlers[0]?.handleClick(notification); + await handlers[0]?.handleClick(notification); } /** diff --git a/src/core/features/pushnotifications/services/pushnotifications.ts b/src/core/features/pushnotifications/services/pushnotifications.ts index f8bb9cfb3..0ac678149 100644 --- a/src/core/features/pushnotifications/services/pushnotifications.ts +++ b/src/core/features/pushnotifications/services/pushnotifications.ts @@ -54,6 +54,8 @@ import { CorePlatform } from '@services/platform'; import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; import { CoreSiteInfo } from '@classes/sites/unauthenticated-site'; import { Push } from '@features/native/plugins'; +import { CoreNavigator } from '@services/navigator'; +import { CoreWait } from '@singletons/wait'; /** * Service to handle push notifications. @@ -442,7 +444,27 @@ export class CorePushNotificationsProvider { async notificationClicked(data: CorePushNotificationsNotificationBasicData): Promise { await ApplicationInit.donePromise; - CorePushNotificationsDelegate.clicked(data); + // This code is also done when clicking local notifications. If it's modified, it should be modified in there too. + if (CoreSites.isLoggedIn()) { + CoreSites.runAfterLoginNavigation({ + priority: 600, + callback: async () => { + await CorePushNotificationsDelegate.clicked(data); + }, + }); + + return; + } + + // User not logged in, wait for the path to be a "valid" path (not a parent path used when starting the app). + await CoreWait.waitFor(() => { + const currentPath = CoreNavigator.getCurrentPath(); + + return currentPath !== '/' && currentPath !== '/login'; + }, { timeout: 400 }); + + await CorePushNotificationsDelegate.clicked(data); + } /** diff --git a/src/core/features/tag/pages/search/search.ts b/src/core/features/tag/pages/search/search.ts index 6cc3147de..7387dd3a8 100644 --- a/src/core/features/tag/pages/search/search.ts +++ b/src/core/features/tag/pages/search/search.ts @@ -21,10 +21,10 @@ import { CoreTagCloud, CoreTagCollection, CoreTagCloudTag, CoreTag } from '@feat import { Translate } from '@singletons'; import { CoreContentLinksHelper } from '@features/contentlinks/services/contentlinks-helper'; import { CoreNavigator } from '@services/navigator'; -import { CoreMainMenuDeepLinkManager } from '@features/mainmenu/classes/deep-link-manager'; import { CoreTime } from '@singletons/time'; import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; import { CoreKeyboard } from '@singletons/keyboard'; +import { CoreSites } from '@services/sites'; /** * Page that displays most used tags and allows searching. @@ -65,8 +65,7 @@ export class CoreTagSearchPage implements OnInit { this.collectionId = CoreNavigator.getRouteNumberParam('collectionId') || 0; this.query = CoreNavigator.getRouteParam('query') || ''; - const deepLinkManager = new CoreMainMenuDeepLinkManager(); - deepLinkManager.treatLink(); + CoreSites.loginNavigationFinished(); this.fetchData().finally(() => { this.loaded = true; diff --git a/src/core/services/local-notifications.ts b/src/core/services/local-notifications.ts index 77608781e..06daa1eea 100644 --- a/src/core/services/local-notifications.ts +++ b/src/core/services/local-notifications.ts @@ -23,7 +23,7 @@ import { CoreText } from '@singletons/text'; import { CoreQueueRunner } from '@classes/queue-runner'; import { CoreError } from '@classes/errors/error'; import { CoreConstants } from '@/core/constants'; -import { makeSingleton, NgZone, Translate, LocalNotifications } from '@singletons'; +import { makeSingleton, NgZone, Translate, LocalNotifications, ApplicationInit } from '@singletons'; import { CoreLogger } from '@singletons/logger'; import { APP_SCHEMA, @@ -42,6 +42,9 @@ import { AsyncInstance, asyncInstance } from '@/core/utils/async-instance'; import { CoreDatabaseTable } from '@classes/database/database-table'; import { CoreDatabaseCachingStrategy, CoreDatabaseTableProxy } from '@classes/database/database-table-proxy'; import { CoreDomUtils } from './utils/dom'; +import { CoreSites } from './sites'; +import { CoreNavigator } from './navigator'; +import { CoreWait } from '@singletons/wait'; /** * Service to handle local notifications. @@ -87,7 +90,28 @@ export class CoreLocalNotificationsProvider { this.handleEvent('trigger', notification); }); - this.clickSubscription = LocalNotifications.on('click').subscribe((notification: ILocalNotification) => { + this.clickSubscription = LocalNotifications.on('click').subscribe(async (notification: ILocalNotification) => { + await ApplicationInit.donePromise; + + // This code is also done when clicking push notifications. If it's modified, it should be modified in there too. + if (CoreSites.isLoggedIn()) { + CoreSites.runAfterLoginNavigation({ + priority: 0, // Use a low priority because the execution of this process doesn't block the next ones. + callback: async () => { + this.handleEvent('click', notification); + }, + }); + + return; + } + + // User not logged in, wait for the path to be a "valid" path (not a parent path used when starting the app). + await CoreWait.waitFor(() => { + const currentPath = CoreNavigator.getCurrentPath(); + + return currentPath !== '/' && currentPath !== '/login'; + }, { timeout: 400 }); + this.handleEvent('click', notification); }); diff --git a/src/core/services/sites.ts b/src/core/services/sites.ts index 21d7901f3..f8ffdc56e 100644 --- a/src/core/services/sites.ts +++ b/src/core/services/sites.ts @@ -67,6 +67,7 @@ import { firstValueFrom } from 'rxjs'; import { CoreHTMLClasses } from '@singletons/html-classes'; import { CoreSiteErrorDebug } from '@classes/errors/siteerror'; import { CoreErrorHelper } from './error-helper'; +import { CoreQueueRunner } from '@classes/queue-runner'; export const CORE_SITE_SCHEMAS = new InjectionToken('CORE_SITE_SCHEMAS'); export const CORE_SITE_CURRENT_SITE_ID_CONFIG = 'current_site_id'; @@ -96,6 +97,11 @@ export class CoreSitesProvider { protected schemasTables: Record>> = {}; protected sitesTable = asyncInstance>(); + // Variables to run code after login navigation. + protected isLoginNavigationFinished = false; + protected afterLoginNavigationQueue: CoreSitesAfterLoginNavigationProcess[] = []; + protected afterLoginNavigationQueueRunner = new CoreQueueRunner(1, true); + constructor(@Optional() @Inject(CORE_SITE_SCHEMAS) siteSchemas: CoreSiteSchema[][] | null) { this.logger = CoreLogger.getInstance('CoreSitesProvider'); this.siteSchemas = (siteSchemas ?? []).flat().reduce( @@ -1462,6 +1468,8 @@ export class CoreSitesProvider { const siteId = this.currentSite.getId(); this.currentSite = undefined; + this.isLoginNavigationFinished = false; + this.afterLoginNavigationQueue = []; if (options.forceLogout || (siteConfig && siteConfig.tool_mobile_forcelogout == '1')) { promises.push(this.setSiteLoggedOut(siteId)); @@ -2168,6 +2176,47 @@ export class CoreSitesProvider { ); } + /** + * Run some code when the login navigation is finished. Login navigation finishes when the proper main menu page + * has loaded. + * If not logged in or the login navigation is already finished, the callback will run immediately (waiting for currently + * running processes to finish). + * Otherwise, the process will be added to a queue and will run once the login navigation is finished. + * + * @param data Process data. + */ + runAfterLoginNavigation(data: CoreSitesAfterLoginNavigationProcess): void { + if (!this.isLoggedIn() || this.isLoginNavigationFinished) { + this.afterLoginNavigationQueueRunner.run(data.callback, { priority: data.priority }); + + return; + } + + this.afterLoginNavigationQueue.push(data); + + // Sort the list by priority. The queue runner also uses priority, but the first run is always executed immediately + // so it's important to always pass the highest priority process first. + this.afterLoginNavigationQueue.sort((a, b) => b.priority - a.priority); + } + + /** + * Notify that the login navigation is finished. This function should only be used by main menu pages. + */ + loginNavigationFinished(): void { + if (this.isLoginNavigationFinished) { + // Already finished, nothing else to do. + return; + } + + this.isLoginNavigationFinished = true; + + // Run the processes in the queue. + this.afterLoginNavigationQueue.forEach(data => { + this.afterLoginNavigationQueueRunner.run(data.callback, { priority: data.priority }); + }); + this.afterLoginNavigationQueue = []; + } + } export const CoreSites = makeSingleton(CoreSitesProvider); @@ -2392,3 +2441,11 @@ export type CoreSitesLogoutOptions = { forceLogout?: boolean; // If true, site will be marked as logged out, no matter the value tool_mobile_forcelogout. removeAccount?: boolean; // If true, site will be removed too after logout. }; + +/** + * Process to run after login navigation finishes. + */ +export type CoreSitesAfterLoginNavigationProcess = { + priority: number; + callback: () => Promise; +};