MOBILE-2327 pushnotifications: Add push notifications delegate

main
Pau Ferrer Ocaña 2018-02-23 14:44:17 +01:00
parent 0c4f834c0a
commit b9c0bb5653
14 changed files with 325 additions and 15 deletions

View File

@ -35,7 +35,7 @@ export class AddonFilesProvider {
* @return {boolean} Whether the WS is available, false otherwise. * @return {boolean} Whether the WS is available, false otherwise.
*/ */
canGetPrivateFilesInfo(): boolean { canGetPrivateFilesInfo(): boolean {
return this.sitesProvider.getCurrentSite().wsAvailable('core_user_get_private_files_info'); return this.sitesProvider.wsAvailableInCurrentSite('core_user_get_private_files_info');
} }
/** /**

View File

@ -27,6 +27,10 @@ import { AddonMessagesDiscussionLinkHandler } from './providers/discussion-link-
import { AddonMessagesIndexLinkHandler } from './providers/index-link-handler'; import { AddonMessagesIndexLinkHandler } from './providers/index-link-handler';
import { AddonMessagesSyncCronHandler } from './providers/sync-cron-handler'; import { AddonMessagesSyncCronHandler } from './providers/sync-cron-handler';
import { CoreEventsProvider } from '../../providers/events'; import { CoreEventsProvider } from '../../providers/events';
import { CoreAppProvider } from '../../providers/app';
import { CoreSitesProvider } from '../../providers/sites';
import { CoreLocalNotificationsProvider } from '../../providers/local-notifications';
import { CoreContentLinksHelperProvider } from '../../core/contentlinks/providers/helper';
@NgModule({ @NgModule({
declarations: [ declarations: [
@ -49,7 +53,9 @@ export class AddonMessagesModule {
contentLinksDelegate: CoreContentLinksDelegate, indexLinkHandler: AddonMessagesIndexLinkHandler, contentLinksDelegate: CoreContentLinksDelegate, indexLinkHandler: AddonMessagesIndexLinkHandler,
discussionLinkHandler: AddonMessagesDiscussionLinkHandler, sendMessageHandler: AddonMessagesSendMessageUserHandler, discussionLinkHandler: AddonMessagesDiscussionLinkHandler, sendMessageHandler: AddonMessagesSendMessageUserHandler,
userDelegate: CoreUserDelegate, cronDelegate: CoreCronDelegate, syncHandler: AddonMessagesSyncCronHandler, userDelegate: CoreUserDelegate, cronDelegate: CoreCronDelegate, syncHandler: AddonMessagesSyncCronHandler,
network: Network, messagesSync: AddonMessagesSyncProvider) { network: Network, messagesSync: AddonMessagesSyncProvider, appProvider: CoreAppProvider,
localNotifications: CoreLocalNotificationsProvider, messagesProvider: AddonMessagesProvider,
sitesProvider: CoreSitesProvider, linkHelper: CoreContentLinksHelperProvider) {
// Register handlers. // Register handlers.
mainMenuDelegate.registerHandler(mainmenuHandler); mainMenuDelegate.registerHandler(mainmenuHandler);
contentLinksDelegate.registerHandler(indexLinkHandler); contentLinksDelegate.registerHandler(indexLinkHandler);
@ -62,5 +68,25 @@ export class AddonMessagesModule {
network.onConnect().subscribe(() => { network.onConnect().subscribe(() => {
messagesSync.syncAllDiscussions(undefined, true); messagesSync.syncAllDiscussions(undefined, true);
}); });
const notificationClicked = (notification: any): void => {
messagesProvider.isMessagingEnabledForSite(notification.site).then(() => {
sitesProvider.isFeatureDisabled('$mmSideMenuDelegate_mmaMessages', notification.site).then((disabled) => {
if (disabled) {
// Messages are disabled, stop.
return;
}
messagesProvider.invalidateDiscussionsCache().finally(() => {
linkHelper.goInSite(undefined, 'AddonMessagesIndexPage', undefined, notification.site);
});
});
});
};
if (appProvider.isDesktop()) {
// Listen for clicks in simulated push notifications.
localNotifications.registerClick(AddonMessagesProvider.PUSH_SIMULATION_COMPONENT, notificationClicked);
}
} }
} }

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 { Injectable } from '@angular/core'; import { Injectable, Inject } from '@angular/core';
import { AddonMessagesProvider } from './messages'; import { AddonMessagesProvider } from './messages';
import { CoreMainMenuDelegate, CoreMainMenuHandler, CoreMainMenuHandlerToDisplay } from '@core/mainmenu/providers/delegate'; import { CoreMainMenuDelegate, CoreMainMenuHandler, CoreMainMenuHandlerToDisplay } from '@core/mainmenu/providers/delegate';
import { CoreCronHandler } from '@providers/cron'; import { CoreCronHandler } from '@providers/cron';
@ -21,6 +21,7 @@ import { CoreEventsProvider } from '@providers/events';
import { CoreAppProvider } from '@providers/app'; import { CoreAppProvider } from '@providers/app';
import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreTextUtilsProvider } from '@providers/utils/text';
import { CoreLocalNotificationsProvider } from '@providers/local-notifications'; import { CoreLocalNotificationsProvider } from '@providers/local-notifications';
import { AddonPushNotificationsProvider } from '@addon/pushnotifications/providers/pushnotifications';
/** /**
* Handler to inject an option into main menu. * Handler to inject an option into main menu.
@ -34,7 +35,8 @@ export class AddonMessagesMainMenuHandler implements CoreMainMenuHandler, CoreCr
constructor(private messagesProvider: AddonMessagesProvider, private sitesProvider: CoreSitesProvider, constructor(private messagesProvider: AddonMessagesProvider, private sitesProvider: CoreSitesProvider,
private eventsProvider: CoreEventsProvider, private appProvider: CoreAppProvider, private eventsProvider: CoreEventsProvider, private appProvider: CoreAppProvider,
private localNotificationsProvider: CoreLocalNotificationsProvider, private textUtils: CoreTextUtilsProvider) { private localNotificationsProvider: CoreLocalNotificationsProvider, private textUtils: CoreTextUtilsProvider,
private pushNotificationsProvider: AddonPushNotificationsProvider) {
eventsProvider.on(AddonMessagesProvider.READ_CHANGED_EVENT, (data) => { eventsProvider.on(AddonMessagesProvider.READ_CHANGED_EVENT, (data) => {
this.updateBadge(data.siteId); this.updateBadge(data.siteId);

View File

@ -33,6 +33,7 @@ export class AddonMessagesProvider {
static READ_CRON_EVENT = 'read_cron_event'; static READ_CRON_EVENT = 'read_cron_event';
static SPLIT_VIEW_LOAD_EVENT = 'split_view_load_event'; static SPLIT_VIEW_LOAD_EVENT = 'split_view_load_event';
static POLL_INTERVAL = 10000; static POLL_INTERVAL = 10000;
static PUSH_SIMULATION_COMPONENT = 'AddonMessagesPushSimulation';
protected logger; protected logger;
@ -521,7 +522,7 @@ export class AddonMessagesProvider {
* @since 3.2 * @since 3.2
*/ */
isMarkAllMessagesReadEnabled(): boolean { isMarkAllMessagesReadEnabled(): boolean {
return this.sitesProvider.getCurrentSite().wsAvailable('core_message_mark_all_messages_as_read'); return this.sitesProvider.wsAvailableInCurrentSite('core_message_mark_all_messages_as_read');
} }
/** /**
@ -531,7 +532,25 @@ export class AddonMessagesProvider {
* @since 3.2 * @since 3.2
*/ */
isMessageCountEnabled(): boolean { isMessageCountEnabled(): boolean {
return this.sitesProvider.getCurrentSite().wsAvailable('core_message_get_unread_conversations_count'); return this.sitesProvider.wsAvailableInCurrentSite('core_message_get_unread_conversations_count');
}
/**
* Returns whether or not messaging is enabled for a certain site.
*
* This could call a WS so do not abuse this method.
*
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Resolved when enabled, otherwise rejected.
*/
isMessagingEnabledForSite(siteId?: string): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => {
if (!site.canUseAdvancedFeature('messaging')) {
return Promise.reject(null);
}
return Promise.resolve(true);
});
} }
/** /**
@ -553,7 +572,7 @@ export class AddonMessagesProvider {
* @since 3.2 * @since 3.2
*/ */
isSearchMessagesEnabled(): boolean { isSearchMessagesEnabled(): boolean {
return this.sitesProvider.getCurrentSite().wsAvailable('core_message_data_for_messagearea_search_messages'); return this.sitesProvider.wsAvailableInCurrentSite('core_message_data_for_messagearea_search_messages');
} }
/** /**

View File

@ -0,0 +1,131 @@
// (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 { CoreLoggerProvider } from '../../../providers/logger';
/**
* Service to handle push notifications clicks.
*/
@Injectable()
export class AddonPushNotificationsDelegate {
protected logger;
protected clickHandlers: { [s: string]: Function } = {};
protected receiveHandlers: { [s: string]: Function } = {};
protected counterHandlers: { [s: string]: string } = {};
constructor(loggerProvider: CoreLoggerProvider) {
this.logger = loggerProvider.getInstance('AddonPushNotificationsDelegate');
}
/**
* Function called when a push notification is clicked. Sends notification to handlers.
*
* @param {any} notification Notification clicked.
*/
clicked(notification: any): void {
for (const name in this.clickHandlers) {
const callback = this.clickHandlers[name];
if (typeof callback == 'function') {
const treated = callback(notification);
if (treated) {
return; // Stop execution when notification is treated.
}
}
}
}
/**
* Function called when a push notification is received in foreground (cannot tell when it's received in background).
* Sends notification to all handlers.
*
* @param {any} notification Notification received.
*/
received(notification: any): void {
for (const name in this.receiveHandlers) {
const callback = this.receiveHandlers[name];
if (typeof callback == 'function') {
callback(notification);
}
}
}
/**
* Register a push notifications handler for CLICKS.
* When a notification is clicked, the handler will receive a notification to treat.
*
* @param {string} name Handler's name.
* @param {Function} callback The callback function. Will get as parameter the clicked notification.
* @description
* The handler should return true if the notification is the one expected, false otherwise.
* @see {@link AddonPushNotificationsDelegate#clicked}
*/
registerHandler(name: string, callback: Function): void {
this.logger.debug(`Registered handler '${name}' as CLICK push notification handler.`);
this.clickHandlers[name] = callback;
}
/**
* Register a push notifications handler for RECEIVE notifications in foreground (cannot tell when it's received in background).
* When a notification is received, the handler will receive a notification to treat.
*
* @param {string} name Handler's name.
* @param {Function} callback The callback function. Will get as parameter the clicked notification.
* @see {@link AddonPushNotificationsDelegate#received}
*/
registerReceiveHandler(name: string, callback: Function): void {
this.logger.debug(`Registered handler '${name}' as RECEIVE push notification handler.`);
this.receiveHandlers[name] = callback;
}
/**
* Unregister a push notifications handler for RECEIVE notifications.
*
* @param {string} name Handler's name.
*/
unregisterReceiveHandler(name: string): void {
this.logger.debug(`Unregister handler '${name}' from RECEIVE push notification handlers.`);
delete this.receiveHandlers[name];
}
/**
* Register a push notifications handler for update badge counter.
*
* @param {string} name Handler's name.
*/
registerCounterHandler(name: string): void {
this.logger.debug(`Registered handler '${name}' as badge counter handler.`);
this.counterHandlers[name] = name;
}
/**
* Check if a counter handler is present.
*
* @param {string} name Handler's name.
* @return {boolean} If handler name is present.
*/
isCounterHandlerRegistered(name: string): boolean {
return typeof this.counterHandlers[name] != 'undefined';
}
/**
* Get all counter badge handlers.
*
* @return {any} with all the handler names.
*/
getCounterHandlers(): any {
return this.counterHandlers;
}
}

View File

@ -0,0 +1,80 @@
// (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 { Platform } from 'ionic-angular';
import { CoreAppProvider } from '@providers/app';
import { CoreLoggerProvider } from '@providers/logger';
import { CoreSitesProvider } from '@providers/sites';
import { AddonPushNotificationsDelegate } from './delegate';
/**
* Service to handle push notifications.
*/
@Injectable()
export class AddonPushNotificationsProvider {
protected logger;
protected pushID: string;
protected appDB: any;
// Variables for database.
protected BADGE_TABLE = 'mma_pushnotifications_badge';
protected tablesSchema = [
{
name: this.BADGE_TABLE,
columns: [
{
name: 'siteid',
type: 'INTEGER'
},
{
name: 'addon',
type: 'TEXT'
},
{
name: 'number',
type: 'INTEGER'
}
]
}
];
constructor(logger: CoreLoggerProvider, protected appProvider: CoreAppProvider, private platform: Platform,
protected pushNotificationsDelegate: AddonPushNotificationsDelegate, protected sitesProvider: CoreSitesProvider) {
this.logger = logger.getInstance('AddonPushNotificationsProvider');
this.appDB = appProvider.getDB();
this.appDB.createTablesFromSchema(this.tablesSchema);
}
/**
* Get the pushID for this device.
*
* @return {string} Push ID.
*/
getPushId(): string {
return this.pushID;
}
/**
* Function called when a push notification is clicked. Redirect the user to the right state.
*
* @param {any} notification Notification.
*/
notificationClicked(notification: any): void {
this.platform.ready().then(() => {
this.pushNotificationsDelegate.clicked(notification);
});
}
}

View File

@ -0,0 +1,31 @@
// (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 { NgModule } from '@angular/core';
import { AddonPushNotificationsProvider } from './providers/pushnotifications';
import { AddonPushNotificationsDelegate } from './providers/delegate';
@NgModule({
declarations: [
],
imports: [
],
providers: [
AddonPushNotificationsProvider,
AddonPushNotificationsDelegate
]
})
export class AddonPushNotificationsModule {
constructor() {}
}

View File

@ -16,10 +16,10 @@ import { Component, OnInit } from '@angular/core';
import { Platform } from 'ionic-angular'; import { Platform } from 'ionic-angular';
import { StatusBar } from '@ionic-native/status-bar'; import { StatusBar } from '@ionic-native/status-bar';
import { SplashScreen } from '@ionic-native/splash-screen'; import { SplashScreen } from '@ionic-native/splash-screen';
import { CoreAppProvider } from '../providers/app'; import { CoreAppProvider } from '@providers/app';
import { CoreEventsProvider } from '../providers/events'; import { CoreEventsProvider } from '@providers/events';
import { CoreLoggerProvider } from '../providers/logger'; import { CoreLoggerProvider } from '@providers/logger';
import { CoreLoginHelperProvider } from '../core/login/providers/helper'; import { CoreLoginHelperProvider } from '@core/login/providers/helper';
@Component({ @Component({
templateUrl: 'app.html' templateUrl: 'app.html'

View File

@ -71,6 +71,7 @@ import { AddonFilesModule } from '@addon/files/files.module';
import { AddonModBookModule } from '@addon/mod/book/book.module'; import { AddonModBookModule } from '@addon/mod/book/book.module';
import { AddonModLabelModule } from '@addon/mod/label/label.module'; import { AddonModLabelModule } from '@addon/mod/label/label.module';
import { AddonMessagesModule } from '@addon/messages/messages.module'; import { AddonMessagesModule } from '@addon/messages/messages.module';
import { AddonPushNotificationsModule } from '@addon/pushnotifications/pushnotifications.module';
// For translate loader. AoT requires an exported function for factories. // For translate loader. AoT requires an exported function for factories.
export function createTranslateLoader(http: HttpClient): TranslateHttpLoader { export function createTranslateLoader(http: HttpClient): TranslateHttpLoader {
@ -113,7 +114,8 @@ export function createTranslateLoader(http: HttpClient): TranslateHttpLoader {
AddonFilesModule, AddonFilesModule,
AddonModBookModule, AddonModBookModule,
AddonModLabelModule, AddonModLabelModule,
AddonMessagesModule AddonMessagesModule,
AddonPushNotificationsModule
], ],
bootstrap: [IonicApp], bootstrap: [IonicApp],
entryComponents: [ entryComponents: [

View File

@ -238,7 +238,7 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate {
* @return {boolean} True if can check updates, false otherwise. * @return {boolean} True if can check updates, false otherwise.
*/ */
canCheckUpdates(): boolean { canCheckUpdates(): boolean {
return this.sitesProvider.getCurrentSite().wsAvailable('core_course_check_updates'); return this.sitesProvider.wsAvailableInCurrentSite('core_course_check_updates');
} }
/** /**

View File

@ -355,7 +355,7 @@ export class CoreCoursesProvider {
* @return {boolean} Whether get courses by field is available. * @return {boolean} Whether get courses by field is available.
*/ */
isGetCoursesByFieldAvailable(): boolean { isGetCoursesByFieldAvailable(): boolean {
return this.sitesProvider.getCurrentSite().wsAvailable('core_course_get_courses_by_field'); return this.sitesProvider.wsAvailableInCurrentSite('core_course_get_courses_by_field');
} }
/** /**

View File

@ -16,8 +16,10 @@ import { NgModule } from '@angular/core';
import { Platform } from 'ionic-angular'; import { Platform } from 'ionic-angular';
// Ionic Native services. // Ionic Native services.
import { Badge } from '@ionic-native/badge';
import { Camera } from '@ionic-native/camera'; import { Camera } from '@ionic-native/camera';
import { Clipboard } from '@ionic-native/clipboard'; import { Clipboard } from '@ionic-native/clipboard';
import { Device } from '@ionic-native/device';
import { File } from '@ionic-native/file'; import { File } from '@ionic-native/file';
import { FileTransfer } from '@ionic-native/file-transfer'; import { FileTransfer } from '@ionic-native/file-transfer';
import { Globalization } from '@ionic-native/globalization'; import { Globalization } from '@ionic-native/globalization';
@ -26,6 +28,7 @@ import { Keyboard } from '@ionic-native/keyboard';
import { LocalNotifications } from '@ionic-native/local-notifications'; import { LocalNotifications } from '@ionic-native/local-notifications';
import { MediaCapture } from '@ionic-native/media-capture'; import { MediaCapture } from '@ionic-native/media-capture';
import { Network } from '@ionic-native/network'; import { Network } from '@ionic-native/network';
import { Push } from '@ionic-native/push';
import { SplashScreen } from '@ionic-native/splash-screen'; import { SplashScreen } from '@ionic-native/splash-screen';
import { StatusBar } from '@ionic-native/status-bar'; import { StatusBar } from '@ionic-native/status-bar';
import { SQLite } from '@ionic-native/sqlite'; import { SQLite } from '@ionic-native/sqlite';
@ -68,6 +71,7 @@ import { CoreInitDelegate } from '../../providers/init';
imports: [ imports: [
], ],
providers: [ providers: [
Badge,
CoreEmulatorHelperProvider, CoreEmulatorHelperProvider,
CoreEmulatorCaptureHelperProvider, CoreEmulatorCaptureHelperProvider,
{ {
@ -84,6 +88,7 @@ import { CoreInitDelegate } from '../../providers/init';
return appProvider.isMobile() ? new Clipboard() : new ClipboardMock(appProvider); return appProvider.isMobile() ? new Clipboard() : new ClipboardMock(appProvider);
} }
}, },
Device,
{ {
provide: File, provide: File,
deps: [CoreAppProvider, CoreTextUtilsProvider], deps: [CoreAppProvider, CoreTextUtilsProvider],
@ -139,6 +144,7 @@ import { CoreInitDelegate } from '../../providers/init';
return platform.is('cordova') ? new Network() : new NetworkMock(); return platform.is('cordova') ? new Network() : new NetworkMock();
} }
}, },
Push,
SplashScreen, SplashScreen,
StatusBar, StatusBar,
SQLite, SQLite,

View File

@ -901,7 +901,7 @@ export class CoreLoginHelperProvider {
return; return;
} }
if (!this.sitesProvider.getCurrentSite().wsAvailable('core_user_agree_site_policy')) { if (!this.sitesProvider.wsAvailableInCurrentSite('core_user_agree_site_policy')) {
// WS not available, stop. // WS not available, stop.
return; return;
} }

View File

@ -1212,4 +1212,17 @@ export class CoreSitesProvider {
this.sites[id].getDb().createTablesFromSchema(tables); this.sites[id].getDb().createTablesFromSchema(tables);
} }
} }
/**
* Check if a WS is available in the current site, if any.
*
* @param {string} method WS name.
* @param {boolean} [checkPrefix=true] When true also checks with the compatibility prefix.
* @return {boolean} Whether the WS is available.
*/
wsAvailableInCurrentSite(method: string, checkPrefix: boolean = true): boolean {
const site = this.getCurrentSite();
return site && site.wsAvailable(method, checkPrefix);
}
} }