MOBILE-3833 mainmenu: Support deep links in all tabs, not just home

main
Dani Palou 2022-01-27 14:59:14 +01:00
parent 49afa105bc
commit f83c8f4e1a
14 changed files with 183 additions and 88 deletions

View File

@ -16,6 +16,7 @@ import { ContextLevel } from '@/core/constants';
import { AddonBlog, AddonBlogFilter, AddonBlogPost, AddonBlogProvider } from '@addons/blog/services/blog';
import { Component, OnInit } 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 { CoreUser, CoreUserProfile } from '@features/user/services/user';
import { IonRefresher } from '@ionic/angular';
@ -118,6 +119,9 @@ export class AddonBlogEntriesPage implements OnInit {
this.commentsEnabled = !CoreComments.areCommentsDisabledInSite();
this.tagsEnabled = CoreTag.areTagsAvailableInSite();
const deepLinkManager = new CoreMainMenuDeepLinkManager();
deepLinkManager.treatLink();
await this.fetchEntries();
CoreUtils.ignoreErrors(AddonBlog.logView(this.filter));

View File

@ -33,6 +33,7 @@ import { AddonCalendarFilterPopoverComponent } from '../../components/filter/fil
import { CoreNavigator } from '@services/navigator';
import { CoreLocalNotifications } from '@services/local-notifications';
import { CoreConstants } from '@/core/constants';
import { CoreMainMenuDeepLinkManager } from '@features/mainmenu/classes/deep-link-manager';
/**
* Page that displays the calendar events.
@ -177,6 +178,9 @@ export class AddonCalendarIndexPage implements OnInit, OnDestroy {
this.fetchData(true, false);
});
const deepLinkManager = new CoreMainMenuDeepLinkManager();
deepLinkManager.treatLink();
}
/**

View File

@ -13,7 +13,7 @@
// limitations under the License.
import { Injectable } from '@angular/core';
import { CanActivate, UrlTree } from '@angular/router';
import { ActivatedRouteSnapshot, CanActivate, UrlTree } from '@angular/router';
import { Router } from '@singletons';
import { AddonMessagesMainMenuHandlerService } from '../services/handlers/mainmenu';
import { AddonMessages } from '../services/messages';
@ -27,18 +27,22 @@ export class AddonMessagesIndexGuard implements CanActivate {
/**
* @inheritdoc
*/
canActivate(): UrlTree {
return this.guard();
canActivate(route: ActivatedRouteSnapshot): UrlTree {
return this.guard(route);
}
/**
* Check if there is a pending redirect and trigger it.
*/
private guard(): UrlTree {
private guard(route: ActivatedRouteSnapshot): UrlTree {
const enabled = AddonMessages.isGroupMessagingEnabled();
const path = `/main/${AddonMessagesMainMenuHandlerService.PAGE_NAME}/` + ( enabled ? 'group-conversations' : 'index');
return Router.parseUrl(path);
const newRoute = Router.parseUrl(path);
newRoute.queryParams = route.queryParams;
return newRoute;
}
}

View File

@ -32,6 +32,7 @@ import { Translate, Platform } from '@singletons';
import { IonRefresher } from '@ionic/angular';
import { CoreNavigator } from '@services/navigator';
import { CoreScreen } from '@services/screen';
import { CoreMainMenuDeepLinkManager } from '@features/mainmenu/classes/deep-link-manager';
/**
* Page that displays the list of discussions.
@ -141,12 +142,17 @@ 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) {
// Take first and load it.
this.gotoDiscussion(this.discussions[0].message!.user);
await this.gotoDiscussion(this.discussions[0].message!.user);
}
// Treat deep link now that the conversation route has been loaded if needed.
deepLinkManager.treatLink();
}
/**
@ -247,7 +253,7 @@ export class AddonMessagesDiscussions35Page implements OnInit, OnDestroy {
* @param messageId Message to scroll after loading the discussion. Used when searching.
* @param onlyWithSplitView Only go to Discussion if split view is on.
*/
gotoDiscussion(discussionUserId: number, messageId?: number): void {
async gotoDiscussion(discussionUserId: number, messageId?: number): Promise<void> {
this.discussionUserId = discussionUserId;
const params: Params = {
@ -261,7 +267,7 @@ export class AddonMessagesDiscussions35Page implements OnInit, OnDestroy {
const splitViewLoaded = CoreNavigator.isCurrentPathInTablet('**/messages/index/discussion');
const path = (splitViewLoaded ? '../' : '') + 'discussion';
CoreNavigator.navigate(path, { params });
await CoreNavigator.navigate(path, { params });
}
/**

View File

@ -36,6 +36,7 @@ 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';
/**
* Page that displays the list of conversations, including group conversations.
@ -279,6 +280,8 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
}
});
const deepLinkManager = new CoreMainMenuDeepLinkManager();
await this.fetchData();
if (!this.selectedConversationId && !this.selectedUserId && CoreScreen.isTablet) {
@ -290,10 +293,13 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
conversation = expandedOption.conversations[0];
if (conversation) {
this.gotoConversation(conversation.id);
await this.gotoConversation(conversation.id);
}
}
}
// Treat deep link now that the conversation route has been loaded if needed.
deepLinkManager.treatLink();
}
/**
@ -507,7 +513,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
* @param userId User of the conversation. Only if there is no conversationId.
* @param messageId Message to scroll after loading the discussion. Used when searching.
*/
gotoConversation(conversationId?: number, userId?: number, messageId?: number): void {
async gotoConversation(conversationId?: number, userId?: number, messageId?: number): Promise<void> {
this.selectedConversationId = conversationId;
this.selectedUserId = userId;
@ -524,7 +530,8 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
const splitViewLoaded = CoreNavigator.isCurrentPathInTablet('**/messages/group-conversations/discussion');
const path = (splitViewLoaded ? '../' : '') + 'discussion';
CoreNavigator.navigate(path, { params });
await CoreNavigator.navigate(path, { params });
}
/**

View File

@ -29,6 +29,7 @@ import {
AddonNotificationsHelper,
AddonNotificationsNotificationToRender,
} from '@addons/notifications/services/notifications-helper';
import { CoreMainMenuDeepLinkManager } from '@features/mainmenu/classes/deep-link-manager';
/**
* Page that displays the list of notifications.
@ -82,6 +83,9 @@ export class AddonNotificationsListPage implements OnInit, OnDestroy {
this.notificationsLoaded = false;
this.refreshNotifications();
});
const deepLinkManager = new CoreMainMenuDeepLinkManager();
deepLinkManager.treatLink();
}
/**

View File

@ -17,6 +17,7 @@ import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { CoreBlockComponent } from '@features/block/components/block/block';
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 { IonRefresher } from '@ionic/angular';
import { CoreNavigator } from '@services/navigator';
import { CoreSites } from '@services/sites';
@ -65,6 +66,9 @@ export class CoreCoursesMyCoursesPage implements OnInit, OnDestroy {
this.searchEnabled = !CoreCourses.isSearchCoursesDisabledInSite();
this.downloadCoursesEnabled = !CoreCourses.isDownloadCoursesDisabledInSite();
const deepLinkManager = new CoreMainMenuDeepLinkManager();
deepLinkManager.treatLink();
this.loadSiteName();
this.loadContent();

View File

@ -0,0 +1,102 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// 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 { CoreContentLinksDelegate } from '@features/contentlinks/services/contentlinks-delegate';
import { CoreContentLinksHelper } from '@features/contentlinks/services/contentlinks-helper';
import { CoreCourse } from '@features/course/services/course';
import { CoreCourseHelper } from '@features/course/services/course-helper';
import { CoreNavigator, CoreRedirectPayload } from '@services/navigator';
/**
* A class to handle opening deep links in a main menu page. There are 2 type of deep links:
* -A Moodle URL to treat.
* -A combination of path + options.
*/
export class CoreMainMenuDeepLinkManager {
protected pendingRedirect?: CoreRedirectPayload;
protected urlToOpen?: string;
constructor() {
this.urlToOpen = CoreNavigator.getRouteParam('urlToOpen');
const redirectPath = CoreNavigator.getRouteParam('redirectPath');
if (redirectPath) {
this.pendingRedirect = {
redirectPath,
redirectOptions: CoreNavigator.getRouteParam('redirectOptions'),
};
}
}
/**
* Whether there is a deep link to be treated.
*
* @return Whether there is a deep link to be treated.
*/
hasDeepLinkToTreat(): boolean {
return !!this.urlToOpen || !!this.pendingRedirect;
}
/**
* Treat a deep link if there's any to treat.
*/
treatLink(): void {
if (this.pendingRedirect) {
this.treatRedirect(this.pendingRedirect);
} else if (this.urlToOpen) {
this.treatUrlToOpen(this.urlToOpen);
}
delete this.pendingRedirect;
delete this.urlToOpen;
}
/**
* Treat a redirect.
*
* @param data Data received.
*/
protected treatRedirect(data: CoreRedirectPayload): void {
const params = data.redirectOptions?.params;
const coursePathMatches = data.redirectPath.match(/^course\/(\d+)\/?$/);
if (coursePathMatches) {
if (!params?.course) {
CoreCourseHelper.getAndOpenCourse(Number(coursePathMatches[1]), params);
} else {
CoreCourse.openCourse(params.course, params);
}
} else {
CoreNavigator.navigateToSitePath(data.redirectPath, {
...data.redirectOptions,
preferCurrentTab: false,
});
}
}
/**
* Treat a URL to open.
*
* @param url URL to open.
*/
protected async treatUrlToOpen(url: string): Promise<void> {
const actions = await CoreContentLinksDelegate.getActionsFor(url, undefined);
const action = CoreContentLinksHelper.getFirstValidAction(actions);
if (action?.sites?.[0]) {
action.action(action.sites[0]);
}
}
}

View File

@ -20,13 +20,8 @@ import { CoreEventObserver, CoreEvents } from '@singletons/events';
import { CoreTabsOutletComponent, CoreTabsOutletTab } from '@components/tabs-outlet/tabs-outlet';
import { CoreMainMenuHomeDelegate, CoreMainMenuHomeHandlerToDisplay } from '../../services/home-delegate';
import { CoreUtils } from '@services/utils/utils';
import { ActivatedRoute } from '@angular/router';
import { CoreNavigator, CoreRedirectPayload } from '@services/navigator';
import { CoreCourseHelper } from '@features/course/services/course-helper';
import { CoreCourse } from '@features/course/services/course';
import { CoreContentLinksDelegate } from '@features/contentlinks/services/contentlinks-delegate';
import { CoreContentLinksHelper } from '@features/contentlinks/services/contentlinks-helper';
import { CoreMainMenuHomeHandlerService } from '@features/mainmenu/services/handlers/mainmenu';
import { CoreMainMenuDeepLinkManager } from '@features/mainmenu/classes/deep-link-manager';
/**
* Page that displays the Home.
@ -46,28 +41,13 @@ export class CoreMainMenuHomePage implements OnInit {
protected subscription?: Subscription;
protected updateSiteObserver?: CoreEventObserver;
protected pendingRedirect?: CoreRedirectPayload;
protected urlToOpen?: string;
constructor(
protected route: ActivatedRoute,
) {
}
protected deepLinkManager?: CoreMainMenuDeepLinkManager;
/**
* @inheritdoc
*/
ngOnInit(): void {
this.route.queryParams.subscribe((params: Partial<CoreRedirectPayload> & { urlToOpen?: string }) => {
this.urlToOpen = params.urlToOpen ?? this.urlToOpen;
if (params.redirectPath) {
this.pendingRedirect = {
redirectPath: params.redirectPath,
redirectOptions: params.redirectOptions,
};
}
});
this.deepLinkManager = new CoreMainMenuDeepLinkManager();
this.loadSiteName();
@ -124,55 +104,11 @@ export class CoreMainMenuHomePage implements OnInit {
this.siteName = CoreSites.getRequiredCurrentSite().getSiteName() || '';
}
/**
* Handle a redirect.
*
* @param data Data received.
*/
protected handleRedirect(data: CoreRedirectPayload): void {
const params = data.redirectOptions?.params;
const coursePathMatches = data.redirectPath.match(/^course\/(\d+)\/?$/);
if (coursePathMatches) {
if (!params?.course) {
CoreCourseHelper.getAndOpenCourse(Number(coursePathMatches[1]), params);
} else {
CoreCourse.openCourse(params.course, params);
}
} else {
CoreNavigator.navigateToSitePath(data.redirectPath, {
...data.redirectOptions,
preferCurrentTab: false,
});
}
}
/**
* Handle a URL to open.
*
* @param url URL to open.
*/
protected async handleUrlToOpen(url: string): Promise<void> {
const actions = await CoreContentLinksDelegate.getActionsFor(url, undefined);
const action = CoreContentLinksHelper.getFirstValidAction(actions);
if (action?.sites?.[0]) {
action.action(action.sites[0]);
}
}
/**
* Tab was selected.
*/
tabSelected(): void {
if (this.pendingRedirect) {
this.handleRedirect(this.pendingRedirect);
} else if (this.urlToOpen) {
this.handleUrlToOpen(this.urlToOpen);
}
delete this.pendingRedirect;
delete this.urlToOpen;
this.deepLinkManager?.treatLink();
}
/**

View File

@ -24,7 +24,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 { CoreNavigator } from '@services/navigator';
import { CoreNavigationOptions, CoreNavigator } from '@services/navigator';
import { filter } from 'rxjs/operators';
import { NavigationEnd } from '@angular/router';
import { trigger, state, style, transition, animate } from '@angular/animations';
@ -77,6 +77,9 @@ 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;
@ViewChild('mainTabs') mainTabs?: IonTabs;
@ -100,6 +103,9 @@ export class CoreMainMenuPage implements OnInit, OnDestroy {
*/
async ngOnInit(): Promise<void> {
this.showTabs = true;
this.urlToOpen = CoreNavigator.getRouteParam('urlToOpen');
this.redirectPath = CoreNavigator.getRouteParam('redirectPath');
this.redirectOptions = CoreNavigator.getRouteParam('redirectOptions');
this.isMainScreen = !this.mainTabs?.outlet.canGoBack();
@ -173,10 +179,22 @@ export class CoreMainMenuPage implements OnInit, OnDestroy {
this.loaded = CoreMainMenuDelegate.areHandlersLoaded();
if (this.loaded && this.tabs[0] && !CoreNavigator.getCurrentMainMenuTab()) {
if (this.loaded && !CoreNavigator.getCurrentMainMenuTab()) {
// No tab selected, select the first one.
await CoreUtils.nextTick();
this.mainTabs?.select(this.tabs[0].page);
const tabPage = this.tabs[0] ? this.tabs[0].page : this.morePageName;
const tabPageParams = this.tabs[0] ? this.tabs[0].pageParams : {};
// Use navigate instead of mainTabs.select to be able to pass page params.
CoreNavigator.navigate(tabPage, {
params: {
urlToOpen: this.urlToOpen,
redirectPath: this.redirectPath,
redirectOptions: this.redirectOptions,
...tabPageParams,
},
});
}
}

View File

@ -25,6 +25,7 @@ import { CoreCustomURLSchemes } from '@services/urlschemes';
import { CoreContentLinksHelper } from '@features/contentlinks/services/contentlinks-helper';
import { CoreTextUtils } from '@services/utils/text';
import { Translate } from '@singletons';
import { CoreMainMenuDeepLinkManager } from '@features/mainmenu/classes/deep-link-manager';
/**
* Page that displays the more page of the app.
@ -71,6 +72,9 @@ export class CoreMainMenuMorePage implements OnInit, OnDestroy {
});
window.addEventListener('resize', this.initHandlers.bind(this));
const deepLinkManager = new CoreMainMenuDeepLinkManager();
deepLinkManager.treatLink();
}
/**

View File

@ -23,6 +23,7 @@ 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';
/**
* Page that displays most used tags and allows searching.
@ -48,6 +49,9 @@ export class CoreTagSearchPage implements OnInit {
this.collectionId = CoreNavigator.getRouteNumberParam('collectionId') || 0;
this.query = CoreNavigator.getRouteParam('query') || '';
const deepLinkManager = new CoreMainMenuDeepLinkManager();
deepLinkManager.treatLink();
this.fetchData().finally(() => {
this.loaded = true;
});

View File

@ -14,7 +14,6 @@
import { Injectable } from '@angular/core';
import { CanActivate, CanLoad, UrlTree } from '@angular/router';
import { CoreMainMenuHomeHandlerService } from '@features/mainmenu/services/handlers/mainmenu';
import { CoreApp } from '@services/app';
import { CoreRedirectPayload } from '@services/navigator';
import { CoreSites } from '@services/sites';
@ -60,7 +59,7 @@ export class CoreRedirectGuard implements CanLoad, CanActivate {
redirect.page,
redirect.options,
);
const route = Router.parseUrl(`/main/${CoreMainMenuHomeHandlerService.PAGE_NAME}`);
const route = Router.parseUrl('/main');
route.queryParams = {
redirectPath: redirect.page,

View File

@ -31,7 +31,6 @@ import { CoreApp } from './app';
import { CoreSitePlugins } from '@features/siteplugins/services/siteplugins';
import { CoreError } from '@classes/errors/error';
import { CoreMainMenuDelegate } from '@features/mainmenu/services/mainmenu-delegate';
import { CoreMainMenuHomeHandlerService } from '@features/mainmenu/services/handlers/mainmenu';
/**
* Redirect payload.
@ -560,11 +559,11 @@ export class CoreNavigatorService {
return this.navigate(`/main/${path}`, options);
}
// Open the path within the home tab.
return this.navigate(`/main/${CoreMainMenuHomeHandlerService.PAGE_NAME}`, {
// Open the path within in main menu.
return this.navigate('/main', {
...options,
params: {
redirectPath: `/main/${CoreMainMenuHomeHandlerService.PAGE_NAME}/${path}`,
redirectPath: path,
redirectOptions: options.params || options.nextNavigation ? options : undefined,
} as CoreRedirectPayload,
});