diff --git a/src/addon/messages/messages.module.ts b/src/addon/messages/messages.module.ts index b05391378..2dd05562f 100644 --- a/src/addon/messages/messages.module.ts +++ b/src/addon/messages/messages.module.ts @@ -29,6 +29,7 @@ import { AddonMessagesContactRequestLinkHandler } from './providers/contact-requ import { AddonMessagesDiscussionLinkHandler } from './providers/discussion-link-handler'; import { AddonMessagesIndexLinkHandler } from './providers/index-link-handler'; import { AddonMessagesSyncCronHandler } from './providers/sync-cron-handler'; +import { AddonMessagesPushClickHandler } from './providers/push-click-handler'; import { CoreAppProvider } from '@providers/app'; import { CoreSitesProvider } from '@providers/sites'; import { CoreLocalNotificationsProvider } from '@providers/local-notifications'; @@ -63,7 +64,8 @@ export const ADDON_MESSAGES_PROVIDERS: any[] = [ AddonMessagesDiscussionLinkHandler, AddonMessagesIndexLinkHandler, AddonMessagesSyncCronHandler, - AddonMessagesSettingsHandler + AddonMessagesSettingsHandler, + AddonMessagesPushClickHandler ] }) export class AddonMessagesModule { @@ -77,7 +79,7 @@ export class AddonMessagesModule { settingsHandler: AddonMessagesSettingsHandler, settingsDelegate: CoreSettingsDelegate, pushNotificationsDelegate: CorePushNotificationsDelegate, utils: CoreUtilsProvider, addContactHandler: AddonMessagesAddContactUserHandler, blockContactHandler: AddonMessagesBlockContactUserHandler, - contactRequestLinkHandler: AddonMessagesContactRequestLinkHandler) { + contactRequestLinkHandler: AddonMessagesContactRequestLinkHandler, pushClickHandler: AddonMessagesPushClickHandler) { // Register handlers. mainMenuDelegate.registerHandler(mainmenuHandler); contentLinksDelegate.registerHandler(indexLinkHandler); @@ -89,6 +91,7 @@ export class AddonMessagesModule { cronDelegate.register(syncHandler); cronDelegate.register(mainmenuHandler); settingsDelegate.registerHandler(settingsHandler); + pushNotificationsDelegate.registerClickHandler(pushClickHandler); // Sync some discussions when device goes online. network.onConnect().subscribe(() => { @@ -134,18 +137,6 @@ export class AddonMessagesModule { localNotifications.registerClick(AddonMessagesProvider.PUSH_SIMULATION_COMPONENT, notificationClicked); } - // Register push notification clicks. - pushNotificationsDelegate.on('click').subscribe((notification) => { - if (utils.isFalseOrZero(notification.notif)) { - // Execute the callback in the Angular zone, so change detection doesn't stop working. - zone.run(() => { - notificationClicked(notification); - }); - - return true; - } - }); - // Allow migrating the table from the old app to the new schema. updateManager.registerSiteTableMigration({ name: 'mma_messages_offline_messages', diff --git a/src/addon/messages/providers/push-click-handler.ts b/src/addon/messages/providers/push-click-handler.ts new file mode 100644 index 000000000..a5fc3acf0 --- /dev/null +++ b/src/addon/messages/providers/push-click-handler.ts @@ -0,0 +1,77 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 { Injectable } from '@angular/core'; +import { CoreUtilsProvider } from '@providers/utils/utils'; +import { CorePushNotificationsClickHandler } from '@core/pushnotifications/providers/delegate'; +import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper'; +import { AddonMessagesProvider } from './messages'; + +/** + * Handler for messaging push notifications clicks. + */ +@Injectable() +export class AddonMessagesPushClickHandler implements CorePushNotificationsClickHandler { + name = 'AddonMessagesPushClickHandler'; + priority = 200; + featureName = 'CoreMainMenuDelegate_AddonMessages'; + + constructor(private utils: CoreUtilsProvider, private messagesProvider: AddonMessagesProvider, + private linkHelper: CoreContentLinksHelperProvider) {} + + /** + * Check if a notification click is handled by this handler. + * + * @param {any} notification The notification to check. + * @return {boolean} Whether the notification click is handled by this handler + */ + handles(notification: any): boolean | Promise { + if (this.utils.isTrueOrOne(notification.notif)) { + return false; + } + + // Check that messaging is enabled. + return this.messagesProvider.isPluginEnabled(notification.site); + } + + /** + * Handle the notification click. + * + * @param {any} notification The notification to check. + * @return {Promise} Promise resolved when done. + */ + handleClick(notification: any): Promise { + return this.messagesProvider.invalidateDiscussionsCache(notification.site).catch(() => { + // Ignore errors. + }).then(() => { + // Check if group messaging is enabled, to determine which page should be loaded. + return this.messagesProvider.isGroupMessagingEnabledInSite(notification.site).then((enabled) => { + const pageParams: any = {}; + let pageName = 'AddonMessagesIndexPage'; + if (enabled) { + pageName = 'AddonMessagesGroupConversationsPage'; + } + + // Check if we have enough information to open the conversation. + if (notification.convid && enabled) { + pageParams.conversationId = Number(notification.convid); + } else if (notification.userfromid) { + pageParams.discussionUserId = Number(notification.userfromid); + } + + return this.linkHelper.goInSite(undefined, pageName, pageParams, notification.site); + }); + }); + } +} diff --git a/src/addon/mod/forum/forum.module.ts b/src/addon/mod/forum/forum.module.ts index b925af0b4..215c343e6 100644 --- a/src/addon/mod/forum/forum.module.ts +++ b/src/addon/mod/forum/forum.module.ts @@ -17,6 +17,7 @@ import { CoreCronDelegate } from '@providers/cron'; import { CoreCourseModuleDelegate } from '@core/course/providers/module-delegate'; import { CoreCourseModulePrefetchDelegate } from '@core/course/providers/module-prefetch-delegate'; import { CoreContentLinksDelegate } from '@core/contentlinks/providers/delegate'; +import { CorePushNotificationsDelegate } from '@core/pushnotifications/providers/delegate'; import { AddonModForumProvider } from './providers/forum'; import { AddonModForumOfflineProvider } from './providers/offline'; import { AddonModForumHelperProvider } from './providers/helper'; @@ -27,6 +28,7 @@ import { AddonModForumSyncCronHandler } from './providers/sync-cron-handler'; import { AddonModForumIndexLinkHandler } from './providers/index-link-handler'; import { AddonModForumDiscussionLinkHandler } from './providers/discussion-link-handler'; import { AddonModForumListLinkHandler } from './providers/list-link-handler'; +import { AddonModForumPushClickHandler } from './providers/push-click-handler'; import { AddonModForumComponentsModule } from './components/components.module'; import { CoreUpdateManagerProvider } from '@providers/update-manager'; @@ -54,7 +56,8 @@ export const ADDON_MOD_FORUM_PROVIDERS: any[] = [ AddonModForumSyncCronHandler, AddonModForumIndexLinkHandler, AddonModForumListLinkHandler, - AddonModForumDiscussionLinkHandler + AddonModForumDiscussionLinkHandler, + AddonModForumPushClickHandler ] }) export class AddonModForumModule { @@ -62,7 +65,8 @@ export class AddonModForumModule { prefetchDelegate: CoreCourseModulePrefetchDelegate, prefetchHandler: AddonModForumPrefetchHandler, cronDelegate: CoreCronDelegate, syncHandler: AddonModForumSyncCronHandler, linksDelegate: CoreContentLinksDelegate, indexHandler: AddonModForumIndexLinkHandler, discussionHandler: AddonModForumDiscussionLinkHandler, - updateManager: CoreUpdateManagerProvider, listLinkHandler: AddonModForumListLinkHandler) { + updateManager: CoreUpdateManagerProvider, listLinkHandler: AddonModForumListLinkHandler, + pushNotificationsDelegate: CorePushNotificationsDelegate, pushClickHandler: AddonModForumPushClickHandler) { moduleDelegate.registerHandler(moduleHandler); prefetchDelegate.registerHandler(prefetchHandler); @@ -70,6 +74,7 @@ export class AddonModForumModule { linksDelegate.registerHandler(indexHandler); linksDelegate.registerHandler(discussionHandler); linksDelegate.registerHandler(listLinkHandler); + pushNotificationsDelegate.registerClickHandler(pushClickHandler); // Allow migrating the tables from the old app to the new schema. updateManager.registerSiteTablesMigration([ diff --git a/src/addon/mod/forum/providers/push-click-handler.ts b/src/addon/mod/forum/providers/push-click-handler.ts new file mode 100644 index 000000000..145d5bf88 --- /dev/null +++ b/src/addon/mod/forum/providers/push-click-handler.ts @@ -0,0 +1,68 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 { Injectable } from '@angular/core'; +import { CoreUrlUtilsProvider } from '@providers/utils/url'; +import { CoreUtilsProvider } from '@providers/utils/utils'; +import { CorePushNotificationsClickHandler } from '@core/pushnotifications/providers/delegate'; +import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper'; +import { AddonModForumProvider } from './forum'; + +/** + * Handler for forum push notifications clicks. + */ +@Injectable() +export class AddonModForumPushClickHandler implements CorePushNotificationsClickHandler { + name = 'AddonModForumPushClickHandler'; + priority = 200; + featureName = 'CoreCourseModuleDelegate_AddonModForum'; + + constructor(private utils: CoreUtilsProvider, private forumProvider: AddonModForumProvider, + private urlUtils: CoreUrlUtilsProvider, private linkHelper: CoreContentLinksHelperProvider) {} + + /** + * Check if a notification click is handled by this handler. + * + * @param {any} notification The notification to check. + * @return {boolean} Whether the notification click is handled by this handler + */ + handles(notification: any): boolean | Promise { + return this.utils.isTrueOrOne(notification.notif) && notification.moodlecomponent == 'mod_forum' && + notification.name == 'posts'; + } + + /** + * Handle the notification click. + * + * @param {any} notification The notification to check. + * @return {Promise} Promise resolved when done. + */ + handleClick(notification: any): Promise { + const contextUrlParams = this.urlUtils.extractUrlParams(notification.contexturl), + pageParams: any = { + courseId: Number(notification.courseid), + discussionId: Number(contextUrlParams.d), + }; + + if (contextUrlParams.urlHash) { + pageParams.postId = Number(contextUrlParams.urlHash.replace('p', '')); + } + + return this.forumProvider.invalidateDiscussionPosts(pageParams.discussionId).catch(() => { + // Ignore errors. + }).then(() => { + return this.linkHelper.goInSite(undefined, 'AddonModForumDiscussionPage', pageParams, notification.site); + }); + } +} diff --git a/src/addon/notifications/notifications.module.ts b/src/addon/notifications/notifications.module.ts index ff86d5f29..b19fd72dc 100644 --- a/src/addon/notifications/notifications.module.ts +++ b/src/addon/notifications/notifications.module.ts @@ -17,6 +17,7 @@ import { AddonNotificationsProvider } from './providers/notifications'; import { AddonNotificationsMainMenuHandler } from './providers/mainmenu-handler'; import { AddonNotificationsSettingsHandler } from './providers/settings-handler'; import { AddonNotificationsCronHandler } from './providers/cron-handler'; +import { AddonNotificationsPushClickHandler } from './providers/push-click-handler'; import { CoreAppProvider } from '@providers/app'; import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper'; import { CoreMainMenuDelegate } from '@core/mainmenu/providers/delegate'; @@ -24,10 +25,8 @@ import { CoreSettingsDelegate } from '@core/settings/providers/delegate'; import { CoreCronDelegate } from '@providers/cron'; import { CoreLocalNotificationsProvider } from '@providers/local-notifications'; import { CoreSitesProvider } from '@providers/sites'; -import { CoreUrlUtilsProvider } from '@providers/utils/url'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CorePushNotificationsDelegate } from '@core/pushnotifications/providers/delegate'; -import { AddonModForumProvider } from '@addon/mod/forum/providers/forum'; // List of providers (without handlers). export const ADDON_NOTIFICATIONS_PROVIDERS: any[] = [ @@ -44,6 +43,7 @@ export const ADDON_NOTIFICATIONS_PROVIDERS: any[] = [ AddonNotificationsMainMenuHandler, AddonNotificationsSettingsHandler, AddonNotificationsCronHandler, + AddonNotificationsPushClickHandler ] }) export class AddonNotificationsModule { @@ -53,43 +53,14 @@ export class AddonNotificationsModule { appProvider: CoreAppProvider, utils: CoreUtilsProvider, sitesProvider: CoreSitesProvider, notificationsProvider: AddonNotificationsProvider, localNotifications: CoreLocalNotificationsProvider, linkHelper: CoreContentLinksHelperProvider, pushNotificationsDelegate: CorePushNotificationsDelegate, - urlUtils: CoreUrlUtilsProvider, forumProvider: AddonModForumProvider) { + pushClickHandler: AddonNotificationsPushClickHandler) { mainMenuDelegate.registerHandler(mainMenuHandler); settingsDelegate.registerHandler(settingsHandler); cronDelegate.register(cronHandler); + pushNotificationsDelegate.registerClickHandler(pushClickHandler); const notificationClicked = (notification: any): void => { - - // Temporary fix to make forum notifications work. This will be improved in next release. - if (notification.moodlecomponent == 'mod_forum' && notification.name == 'posts') { - sitesProvider.isFeatureDisabled('CoreCourseModuleDelegate_AddonModForum', notification.site).then((disabled) => { - if (disabled) { - // Forum is disabled, stop. - return; - } - - const contextUrlParams = urlUtils.extractUrlParams(notification.contexturl), - pageParams: any = { - courseId: Number(notification.courseid), - discussionId: Number(contextUrlParams.d), - }; - - if (contextUrlParams.urlHash) { - pageParams.postId = Number(contextUrlParams.urlHash.replace('p', '')); - } - - forumProvider.invalidateDiscussionPosts(pageParams.discussionId).catch(() => { - // Ignore errors. - }).then(() => { - linkHelper.goInSite(undefined, 'AddonModForumDiscussionPage', pageParams, notification.site); - }); - }); - } else { - goToNotifications(notification); - } - }; - const goToNotifications = (notification: any): void => { sitesProvider.isFeatureDisabled('CoreMainMenuDelegate_AddonNotifications', notification.site).then((disabled) => { if (disabled) { // Notifications are disabled, stop. @@ -106,17 +77,5 @@ export class AddonNotificationsModule { // Listen for clicks in simulated push notifications. localNotifications.registerClick(AddonNotificationsProvider.PUSH_SIMULATION_COMPONENT, notificationClicked); } - - // Register push notification clicks. - pushNotificationsDelegate.on('click').subscribe((notification) => { - if (utils.isTrueOrOne(notification.notif)) { - // Execute the callback in the Angular zone, so change detection doesn't stop working. - zone.run(() => { - notificationClicked(notification); - }); - - return true; - } - }); } } diff --git a/src/addon/notifications/providers/push-click-handler.ts b/src/addon/notifications/providers/push-click-handler.ts new file mode 100644 index 000000000..74baeaa4d --- /dev/null +++ b/src/addon/notifications/providers/push-click-handler.ts @@ -0,0 +1,56 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 { Injectable } from '@angular/core'; +import { CoreUtilsProvider } from '@providers/utils/utils'; +import { CorePushNotificationsClickHandler } from '@core/pushnotifications/providers/delegate'; +import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper'; +import { AddonNotificationsProvider } from './notifications'; + +/** + * Handler for non-messaging push notifications clicks. + */ +@Injectable() +export class AddonNotificationsPushClickHandler implements CorePushNotificationsClickHandler { + name = 'AddonNotificationsPushClickHandler'; + priority = 0; // Low priority so it's used as a fallback if no other handler treats the notification. + featureName = 'CoreMainMenuDelegate_AddonNotifications'; + + constructor(private utils: CoreUtilsProvider, private notificationsProvider: AddonNotificationsProvider, + private linkHelper: CoreContentLinksHelperProvider) {} + + /** + * Check if a notification click is handled by this handler. + * + * @param {any} notification The notification to check. + * @return {boolean} Whether the notification click is handled by this handler + */ + handles(notification: any): boolean | Promise { + return this.utils.isTrueOrOne(notification.notif); + } + + /** + * Handle the notification click. + * + * @param {any} notification The notification to check. + * @return {Promise} Promise resolved when done. + */ + handleClick(notification: any): Promise { + return this.notificationsProvider.invalidateNotificationsList(notification.site).catch(() => { + // Ignore errors. + }).then(() => { + return this.linkHelper.goInSite(undefined, 'AddonNotificationsListPage', undefined, notification.site); + }); + } +} diff --git a/src/core/pushnotifications/providers/delegate.ts b/src/core/pushnotifications/providers/delegate.ts index dbd0f12fe..8f64a4ed5 100644 --- a/src/core/pushnotifications/providers/delegate.ts +++ b/src/core/pushnotifications/providers/delegate.ts @@ -14,8 +14,50 @@ import { Injectable } from '@angular/core'; import { CoreLoggerProvider } from '@providers/logger'; +import { CoreSitesProvider } from '@providers/sites'; +import { CoreUtilsProvider } from '@providers/utils/utils'; import { Subject } from 'rxjs'; +/** + * Interface that all click handlers must implement. + */ +export interface CorePushNotificationsClickHandler { + /** + * A name to identify the handler. + * @type {string} + */ + name: string; + + /** + * Handler's priority. The highest priority is treated first. + * @type {number} + */ + priority?: number; + + /** + * Name of the feature this handler is related to. + * It will be used to check if the feature is disabled (@see CoreSite.isFeatureDisabled). + * @type {string} + */ + featureName?: string; + + /** + * Check if a notification click is handled by this handler. + * + * @param {any} notification The notification to check. + * @return {boolean} Whether the notification click is handled by this handler. + */ + handles(notification: any): boolean | Promise; + + /** + * Handle the notification click. + * + * @param {any} notification The notification to check. + * @return {Promise} Promise resolved when done. + */ + handleClick(notification: any): Promise; +} + /** * Service to handle push notifications actions to perform when clicked and received. */ @@ -24,11 +66,11 @@ export class CorePushNotificationsDelegate { protected logger; protected observables: { [s: string]: Subject } = {}; + protected clickHandlers: { [s: string]: CorePushNotificationsClickHandler } = {}; protected counterHandlers: { [s: string]: string } = {}; - constructor(loggerProvider: CoreLoggerProvider) { + constructor(loggerProvider: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private utils: CoreUtilsProvider) { this.logger = loggerProvider.getInstance('CorePushNotificationsDelegate'); - this.observables['click'] = new Subject(); this.observables['receive'] = new Subject(); } @@ -36,9 +78,61 @@ export class CorePushNotificationsDelegate { * Function called when a push notification is clicked. Sends notification to handlers. * * @param {any} notification Notification clicked. + * @return {Promise} Promise resolved when done. */ - clicked(notification: any): void { - this.observables['click'].next(notification); + clicked(notification: any): Promise { + if (!notification) { + return; + } + + const promises = []; + let handlers: CorePushNotificationsClickHandler[] = []; + + for (const name in this.clickHandlers) { + const handler = this.clickHandlers[name]; + + // Check if the handler is disabled for the site. + promises.push(this.isFeatureDisabled(handler, notification.site).then((disabled) => { + if (!disabled) { + // Check if the handler handles the notification. + return Promise.resolve(handler.handles(notification)).then((handles) => { + if (handles) { + handlers.push(handler); + } + }); + } + })); + } + + return this.utils.allPromises(promises).catch(() => { + // Ignore errors. + }).then(() => { + // Sort by priority. + handlers = handlers.sort((a, b) => { + return a.priority <= b.priority ? 1 : -1; + }); + + if (handlers[0]) { + // Execute the first one. + handlers[0].handleClick(notification); + } + }); + } + + /** + * Check if a handler's feature is disabled for a certain site. + * + * @param {CorePushNotificationsClickHandler} handler Handler to check. + * @param {string} siteId The site ID to check. + * @return {Promise} Promise resolved with boolean: whether the handler feature is disabled. + */ + protected isFeatureDisabled(handler: CorePushNotificationsClickHandler, siteId: string): Promise { + if (handler.featureName) { + // Check if the feature is disabled. + return this.sitesProvider.isFeatureDisabled(handler.featureName, siteId); + } else { + return Promise.resolve(false); + } } /** @@ -52,13 +146,12 @@ export class CorePushNotificationsDelegate { } /** - * Register a push notifications observable for click and receive notification event. - * When a notification is clicked or received, the observable will receive a notification to treat. - * let observer = pushNotificationsDelegate.on('click').subscribe((notification) => { + * Register a push notifications observable for a certain event. Right now, only receive is supported. + * let observer = pushNotificationsDelegate.on('receive').subscribe((notification) => { * ... * observer.unsuscribe(); * - * @param {string} eventName Only click and receive are permitted. + * @param {string} eventName Only receive is permitted. * @return {Subject} Observer to subscribe. */ on(eventName: string): Subject { @@ -72,6 +165,25 @@ export class CorePushNotificationsDelegate { return this.observables[eventName]; } + /** + * Register a click handler. + * + * @param {CorePushNotificationsClickHandler} handler The handler to register. + * @return {boolean} True if registered successfully, false otherwise. + */ + registerClickHandler(handler: CorePushNotificationsClickHandler): boolean { + if (typeof this.clickHandlers[handler.name] !== 'undefined') { + this.logger.log(`Addon '${handler.name}' already registered`); + + return false; + } + + this.logger.log(`Registered addon '${handler.name}'`); + this.clickHandlers[handler.name] = handler; + + return true; + } + /** * Register a push notifications handler for update badge counter. *