MOBILE-2327 messages: Implement push notifications handlers
parent
2911cbb1aa
commit
2f95ccccf2
|
@ -38,9 +38,11 @@
|
|||
"@angular/http": "5.0.0",
|
||||
"@angular/platform-browser": "5.0.0",
|
||||
"@angular/platform-browser-dynamic": "5.0.0",
|
||||
"@ionic-native/badge": "^4.5.3",
|
||||
"@ionic-native/camera": "^4.5.2",
|
||||
"@ionic-native/clipboard": "^4.3.2",
|
||||
"@ionic-native/core": "4.3.0",
|
||||
"@ionic-native/device": "^4.5.3",
|
||||
"@ionic-native/file": "^4.3.3",
|
||||
"@ionic-native/file-transfer": "^4.3.3",
|
||||
"@ionic-native/globalization": "^4.3.2",
|
||||
|
@ -49,6 +51,7 @@
|
|||
"@ionic-native/local-notifications": "^4.4.0",
|
||||
"@ionic-native/media-capture": "^4.5.2",
|
||||
"@ionic-native/network": "^4.3.2",
|
||||
"@ionic-native/push": "^4.5.3",
|
||||
"@ionic-native/splash-screen": "4.3.0",
|
||||
"@ionic-native/sqlite": "^4.3.2",
|
||||
"@ionic-native/status-bar": "4.3.0",
|
||||
|
|
|
@ -33,6 +33,8 @@ import { CoreLocalNotificationsProvider } from '@providers/local-notifications';
|
|||
import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper';
|
||||
import { CoreSettingsDelegate } from '@core/settings/providers/delegate';
|
||||
import { AddonMessagesSettingsHandler } from './providers/settings-handler';
|
||||
import { AddonPushNotificationsDelegate } from '@addon/pushnotifications/providers/delegate';
|
||||
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
|
@ -59,7 +61,8 @@ export class AddonMessagesModule {
|
|||
network: Network, messagesSync: AddonMessagesSyncProvider, appProvider: CoreAppProvider,
|
||||
localNotifications: CoreLocalNotificationsProvider, messagesProvider: AddonMessagesProvider,
|
||||
sitesProvider: CoreSitesProvider, linkHelper: CoreContentLinksHelperProvider,
|
||||
settingsHandler: AddonMessagesSettingsHandler, settingsDelegate: CoreSettingsDelegate) {
|
||||
settingsHandler: AddonMessagesSettingsHandler, settingsDelegate: CoreSettingsDelegate,
|
||||
pushNotificationsDelegate: AddonPushNotificationsDelegate, utils: CoreUtilsProvider) {
|
||||
// Register handlers.
|
||||
mainMenuDelegate.registerHandler(mainmenuHandler);
|
||||
contentLinksDelegate.registerHandler(indexLinkHandler);
|
||||
|
@ -93,5 +96,17 @@ export class AddonMessagesModule {
|
|||
// Listen for clicks in simulated push notifications.
|
||||
localNotifications.registerClick(AddonMessagesProvider.PUSH_SIMULATION_COMPONENT, notificationClicked);
|
||||
}
|
||||
|
||||
// @todo: use addon manager $mmPushNotificationsDelegate = $mmAddonManager.get('$mmPushNotificationsDelegate');
|
||||
// Register push notification clicks.
|
||||
if (pushNotificationsDelegate) {
|
||||
pushNotificationsDelegate.registerHandler('mmaMessages', (notification) => {
|
||||
if (utils.isFalseOrZero(notification.notif)) {
|
||||
notificationClicked(notification);
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -97,12 +97,11 @@ export class AddonMessagesMainMenuHandler implements CoreMainMenuHandler, CoreCr
|
|||
this.messagesProvider.getUnreadConversationsCount(undefined, siteId).then((unread) => {
|
||||
// Leave badge enter if there is a 0+ or a 0.
|
||||
this.badge = parseInt(unread, 10) > 0 ? unread : '';
|
||||
// @todo: use addon manager $mmaPushNotifications = $mmAddonManager.get('$mmaPushNotifications');
|
||||
// Update badge.
|
||||
/*
|
||||
$mmaPushNotifications = $mmAddonManager.get('$mmaPushNotifications');
|
||||
if ($mmaPushNotifications) {
|
||||
$mmaPushNotifications.updateAddonCounter(siteId, 'mmaMessages', unread);
|
||||
}*/
|
||||
if (this.pushNotificationsProvider) {
|
||||
this.pushNotificationsProvider.updateAddonCounter('mmaMessages', unread, siteId);
|
||||
}
|
||||
}).catch(() => {
|
||||
this.badge = '';
|
||||
}).finally(() => {
|
||||
|
|
|
@ -14,10 +14,18 @@
|
|||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Platform } from 'ionic-angular';
|
||||
import { Badge } from '@ionic-native/badge';
|
||||
import { Push, PushObject, PushOptions } from '@ionic-native/push';
|
||||
import { Device } from '@ionic-native/device';
|
||||
import { CoreAppProvider } from '@providers/app';
|
||||
import { CoreLoggerProvider } from '@providers/logger';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
import { AddonPushNotificationsDelegate } from './delegate';
|
||||
import { CoreLocalNotificationsProvider } from '@providers/local-notifications';
|
||||
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
||||
import { CoreConfigProvider } from '@providers/config';
|
||||
import { CoreConfigConstants } from '.././../../configconstants';
|
||||
|
||||
/**
|
||||
* Service to handle push notifications.
|
||||
|
@ -28,6 +36,7 @@ export class AddonPushNotificationsProvider {
|
|||
protected logger;
|
||||
protected pushID: string;
|
||||
protected appDB: any;
|
||||
static COMPONENT = 'mmaPushNotifications';
|
||||
|
||||
// Variables for database.
|
||||
protected BADGE_TABLE = 'mma_pushnotifications_badge';
|
||||
|
@ -47,17 +56,64 @@ export class AddonPushNotificationsProvider {
|
|||
name: 'number',
|
||||
type: 'INTEGER'
|
||||
}
|
||||
]
|
||||
],
|
||||
primaryKeys: ['siteid', 'addon']
|
||||
}
|
||||
];
|
||||
|
||||
constructor(logger: CoreLoggerProvider, protected appProvider: CoreAppProvider, private platform: Platform,
|
||||
protected pushNotificationsDelegate: AddonPushNotificationsDelegate, protected sitesProvider: CoreSitesProvider) {
|
||||
protected pushNotificationsDelegate: AddonPushNotificationsDelegate, protected sitesProvider: CoreSitesProvider,
|
||||
private badge: Badge, private localNotificationsProvider: CoreLocalNotificationsProvider,
|
||||
private utils: CoreUtilsProvider, private textUtils: CoreTextUtilsProvider, private push: Push,
|
||||
private configProvider: CoreConfigProvider, private device: Device) {
|
||||
this.logger = logger.getInstance('AddonPushNotificationsProvider');
|
||||
this.appDB = appProvider.getDB();
|
||||
this.appDB.createTablesFromSchema(this.tablesSchema);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all badge records for a given site.
|
||||
*
|
||||
* @param {string} siteId Site ID.
|
||||
* @return {Promise<any>} Resolved when done.
|
||||
*/
|
||||
cleanSiteCounters(siteId: string): Promise<any> {
|
||||
return this.appDB.getRecords(this.BADGE_TABLE, {siteid: siteId} ).then((entries) => {
|
||||
const promises = [];
|
||||
entries.forEach((entry) => {
|
||||
promises.push(this.appDB.remove(this.BADGE_TABLE, { siteid: entry.siteid, addon: entry.addon }));
|
||||
});
|
||||
|
||||
return Promise.all(promises);
|
||||
}).finally(() => {
|
||||
this.updateAppCounter();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns options for push notifications based on
|
||||
* @return {Promise<PushOptions>} [description]
|
||||
*/
|
||||
protected getOptions(): Promise<PushOptions> {
|
||||
// @TODO: CoreSettingsProvider.NOTIFICATION_SOUND
|
||||
return this.configProvider.get('CoreSettingsProvider.NOTIFICATION_SOUND', true).then((soundEnabled) => {
|
||||
return {
|
||||
android: {
|
||||
senderID: CoreConfigConstants.gcmpn,
|
||||
sound: !!soundEnabled
|
||||
},
|
||||
ios: {
|
||||
alert: 'true',
|
||||
badge: true,
|
||||
sound: !!soundEnabled
|
||||
},
|
||||
windows: {
|
||||
sound: !!soundEnabled
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the pushID for this device.
|
||||
*
|
||||
|
@ -67,6 +123,16 @@ export class AddonPushNotificationsProvider {
|
|||
return this.pushID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Sitebadge counter from the database.
|
||||
*
|
||||
* @param {string} siteId Site ID.
|
||||
* @return {Promise<any>} Promise resolved with the stored badge counter for the site.
|
||||
*/
|
||||
getSiteCounter(siteId: string): Promise<any> {
|
||||
return this.getAddonBadge(siteId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function called when a push notification is clicked. Redirect the user to the right state.
|
||||
*
|
||||
|
@ -77,4 +143,271 @@ export class AddonPushNotificationsProvider {
|
|||
this.pushNotificationsDelegate.clicked(notification);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is called when we receive a Notification from APNS or a message notification from GCM.
|
||||
* The app can be in foreground or background,
|
||||
* if we are in background this code is executed when we open the app clicking in the notification bar.
|
||||
*
|
||||
* @param {any} notification Notification received.
|
||||
*/
|
||||
onMessageReceived(notification: any): void {
|
||||
const data = notification ? notification.additionalData : {};
|
||||
|
||||
this.sitesProvider.getSite(data.site).then(() => {
|
||||
if (this.utils.isTrueOrOne(data.foreground)) {
|
||||
// If the app is in foreground when the notification is received, it's not shown. Let's show it ourselves.
|
||||
if (this.localNotificationsProvider.isAvailable()) {
|
||||
const localNotif = {
|
||||
id: 1,
|
||||
at: new Date(),
|
||||
data: {
|
||||
notif: data.notif,
|
||||
site: data.site
|
||||
},
|
||||
title: '',
|
||||
text: ''
|
||||
},
|
||||
promises = [];
|
||||
|
||||
// Apply formatText to title and message.
|
||||
promises.push(this.textUtils.formatText(notification.title, true, true).then((formattedTitle) => {
|
||||
localNotif.title = formattedTitle;
|
||||
}).catch(() => {
|
||||
localNotif.title = notification.title;
|
||||
}));
|
||||
|
||||
promises.push(this.textUtils.formatText(notification.message, true, true).then((formattedMessage) => {
|
||||
localNotif.text = formattedMessage;
|
||||
}).catch(() => {
|
||||
localNotif.text = notification.message;
|
||||
}));
|
||||
|
||||
Promise.all(promises).then(() => {
|
||||
this.localNotificationsProvider.schedule(localNotif, AddonPushNotificationsProvider.COMPONENT, data.site);
|
||||
});
|
||||
}
|
||||
|
||||
// Trigger a notification received event.
|
||||
this.platform.ready().then(() => {
|
||||
data.title = notification.title;
|
||||
data.message = notification.message;
|
||||
this.pushNotificationsDelegate.received(data);
|
||||
});
|
||||
} else {
|
||||
// The notification was clicked. For compatibility with old push plugin implementation
|
||||
// we'll merge all the notification data in a single object.
|
||||
data.title = notification.title;
|
||||
data.message = notification.message;
|
||||
this.notificationClicked(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters a device from a certain Moodle site.
|
||||
*
|
||||
* @param {any} site Site to unregister from.
|
||||
* @return {Promise<any>} Promise resolved when device is unregistered.
|
||||
*/
|
||||
unregisterDeviceOnMoodle(site: any): Promise<any> {
|
||||
if (!site || !this.appProvider.isMobile()) {
|
||||
return Promise.reject(null);
|
||||
}
|
||||
|
||||
this.logger.debug(`Unregister device on Moodle: '${site.id}'`);
|
||||
|
||||
const data = {
|
||||
appid: CoreConfigConstants.app_id,
|
||||
uuid: this.device.uuid
|
||||
};
|
||||
|
||||
return site.write('core_user_remove_user_device', data).then((response) => {
|
||||
if (!response || !response.removed) {
|
||||
return Promise.reject(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update Counter for an addon. It will update the refered siteId counter and the total badge.
|
||||
* It will return the updated addon counter.
|
||||
*
|
||||
* @param {string} addon Registered addon name to set the badge number.
|
||||
* @param {number} value The number to be stored.
|
||||
* @param {string} [siteId] Site ID. If not defined, use current site.
|
||||
* @return {Promise<any>} Promise resolved with the stored badge counter for the addon on the site.
|
||||
*/
|
||||
updateAddonCounter(addon: string, value: number, siteId?: string): Promise<any> {
|
||||
if (this.pushNotificationsDelegate.isCounterHandlerRegistered(addon)) {
|
||||
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||
|
||||
return this.saveAddonBadge(value, siteId, addon).then(() => {
|
||||
return this.updateSiteCounter(siteId).then(() => {
|
||||
return value;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.resolve(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update total badge counter of the app.
|
||||
*
|
||||
* @return {Promise<any>} Promise resolved with the stored badge counter for the site.
|
||||
*/
|
||||
updateAppCounter(): Promise<any> {
|
||||
return this.sitesProvider.getSitesIds().then((sites) => {
|
||||
const promises = [];
|
||||
sites.forEach((siteId) => {
|
||||
promises.push(this.getAddonBadge(siteId));
|
||||
});
|
||||
|
||||
return Promise.all(promises).then((counters) => {
|
||||
const total = counters.reduce((previous, counter) => {
|
||||
// The app badge counter does not support strings, so parse to int before.
|
||||
return previous + parseInt(counter, 10);
|
||||
}, 0);
|
||||
|
||||
// Set the app badge.
|
||||
return this.badge.set(total).then(() => {
|
||||
return total;
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update counter for a site using the stored addon data. It will update the total badge application number.
|
||||
* It will return the updated site counter.
|
||||
*
|
||||
* @param {string} siteId Site ID.
|
||||
* @return {Promise<any>} Promise resolved with the stored badge counter for the site.
|
||||
*/
|
||||
updateSiteCounter(siteId: string): Promise<any> {
|
||||
const addons = this.pushNotificationsDelegate.getCounterHandlers(),
|
||||
promises = [];
|
||||
|
||||
addons.forEach((addon) => {
|
||||
promises.push(this.getAddonBadge(siteId, addon));
|
||||
});
|
||||
|
||||
return Promise.all(promises).then((counters) => {
|
||||
let plus = false,
|
||||
total = counters.reduce((previous, counter) => {
|
||||
// Check if there is a plus sign at the end of the counter.
|
||||
if (counter != parseInt(counter, 10)) {
|
||||
plus = true;
|
||||
counter = parseInt(counter, 10);
|
||||
}
|
||||
|
||||
return previous + counter;
|
||||
}, 0);
|
||||
|
||||
total = plus && total > 0 ? total + '+' : total;
|
||||
|
||||
// Save the counter on site.
|
||||
return this.saveAddonBadge(total, siteId);
|
||||
}).then((siteTotal) => {
|
||||
return this.updateAppCounter().then(() => {
|
||||
return siteTotal;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a device in Apple APNS or Google GCM.
|
||||
*
|
||||
* @return {Promise<any>} Promise resolved when the device is registered.
|
||||
*/
|
||||
registerDevice(): Promise<any> {
|
||||
try {
|
||||
// Check if sound is enabled for notifications.
|
||||
return this.getOptions().then((options) => {
|
||||
const pushObject: PushObject = this.push.init(options);
|
||||
|
||||
pushObject.on('notification').subscribe((notification: any) => {
|
||||
this.logger.log('Received a notification', notification);
|
||||
this.onMessageReceived(notification);
|
||||
});
|
||||
|
||||
pushObject.on('registration').subscribe((registrationId: any) => {
|
||||
this.pushID = registrationId;
|
||||
this.registerDeviceOnMoodle();
|
||||
});
|
||||
|
||||
pushObject.on('error').subscribe((error: any) => {
|
||||
this.logger.warn('Error with Push plugin', error);
|
||||
});
|
||||
});
|
||||
} catch (ex) {
|
||||
// Ignore errors.
|
||||
this.logger.warn(ex);
|
||||
}
|
||||
|
||||
return Promise.reject(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a device on current Moodle site.
|
||||
*
|
||||
* @return {Promise<any>} Promise resolved when device is registered.
|
||||
*/
|
||||
registerDeviceOnMoodle(): Promise<any> {
|
||||
this.logger.debug('Register device on Moodle.');
|
||||
|
||||
if (!this.sitesProvider.isLoggedIn() || !this.pushID || !this.appProvider.isMobile()) {
|
||||
return Promise.reject(null);
|
||||
}
|
||||
|
||||
const data = {
|
||||
appid: CoreConfigConstants.app_id,
|
||||
name: this.device.manufacturer || '',
|
||||
model: this.device.model,
|
||||
platform: this.device.platform,
|
||||
version: this.device.version,
|
||||
pushid: this.pushID,
|
||||
uuid: this.device.uuid
|
||||
};
|
||||
|
||||
return this.sitesProvider.getCurrentSite().write('core_user_add_user_device', data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the addon/site badge counter from the database.
|
||||
*
|
||||
* @param {string} siteId Site ID.
|
||||
* @param {string} [addon='site'] Registered addon name. If not defined it will store the site total.
|
||||
* @return {Promise<any>} Promise resolved with the stored badge counter for the addon or site or 0 if none.
|
||||
*/
|
||||
protected getAddonBadge(siteId?: string, addon: string = 'site'): Promise<any> {
|
||||
return this.appDB.getRecord(this.BADGE_TABLE, {siteid: siteId, addon: addon}).then((entry) => {
|
||||
return (entry && entry.number) || 0;
|
||||
}).catch(() => {
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the addon/site badgecounter on the database.
|
||||
*
|
||||
* @param {number} value The number to be stored.
|
||||
* @param {string} [siteId] Site ID. If not defined, use current site.
|
||||
* @param {string} [addon='site'] Registered addon name. If not defined it will store the site total.
|
||||
* @return {Promise<any>} Promise resolved with the stored badge counter for the addon or site.
|
||||
*/
|
||||
protected saveAddonBadge(value: number, siteId?: string, addon: string = 'site'): Promise<any> {
|
||||
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||
|
||||
const entry = {
|
||||
siteid: siteId,
|
||||
addon: addon,
|
||||
number: value
|
||||
};
|
||||
|
||||
return this.appDB.insertOrUpdateRecord(this.BADGE_TABLE, entry, {siteid: siteId, addon: addon}).then(() => {
|
||||
return value;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,8 +13,11 @@
|
|||
// limitations under the License.
|
||||
|
||||
import { NgModule } from '@angular/core';
|
||||
import { Platform } from 'ionic-angular';
|
||||
import { AddonPushNotificationsProvider } from './providers/pushnotifications';
|
||||
import { AddonPushNotificationsDelegate } from './providers/delegate';
|
||||
import { CoreEventsProvider } from '@providers/events';
|
||||
import { CoreLocalNotificationsProvider } from '@providers/local-notifications';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
|
@ -27,5 +30,31 @@ import { AddonPushNotificationsDelegate } from './providers/delegate';
|
|||
]
|
||||
})
|
||||
export class AddonPushNotificationsModule {
|
||||
constructor() {}
|
||||
constructor(platform: Platform, pushNotificationsProvider: AddonPushNotificationsProvider, eventsProvider: CoreEventsProvider,
|
||||
localNotificationsProvider: CoreLocalNotificationsProvider) {
|
||||
|
||||
// Register device on GCM or APNS server.
|
||||
platform.ready().then(() => {
|
||||
pushNotificationsProvider.registerDevice();
|
||||
});
|
||||
|
||||
eventsProvider.on(CoreEventsProvider.NOTIFICATION_SOUND_CHANGED, () => {
|
||||
// Notification sound has changed, register the device again to update the sound setting.
|
||||
pushNotificationsProvider.registerDevice();
|
||||
});
|
||||
|
||||
// Register device on Moodle site when login.
|
||||
eventsProvider.on(CoreEventsProvider.LOGIN, () => {
|
||||
pushNotificationsProvider.registerDeviceOnMoodle();
|
||||
});
|
||||
|
||||
eventsProvider.on(CoreEventsProvider.SITE_DELETED, (site) => {
|
||||
pushNotificationsProvider.unregisterDeviceOnMoodle(site);
|
||||
pushNotificationsProvider.cleanSiteCounters(site.id);
|
||||
});
|
||||
|
||||
// Listen for local notification clicks (generated by the app).
|
||||
localNotificationsProvider.registerClick(AddonPushNotificationsProvider.COMPONENT,
|
||||
pushNotificationsProvider.notificationClicked);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ import { Network } from '@ionic-native/network';
|
|||
|
||||
import { CoreDbProvider } from './db';
|
||||
import { CoreLoggerProvider } from './logger';
|
||||
import { SQLiteDB } from '../classes/sqlitedb';
|
||||
import { SQLiteDB } from '@classes/sqlitedb';
|
||||
|
||||
/**
|
||||
* Data stored for a redirect to another page/site.
|
||||
|
|
|
@ -33,4 +33,4 @@
|
|||
"atom": {
|
||||
"rewriteTsconfig": false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,4 +19,4 @@ const customConfig = {
|
|||
module.exports = {
|
||||
dev: webpackMerge(dev, customConfig),
|
||||
prod: webpackMerge(prod, customConfig),
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue