MOBILE-2261 localnotif: Implement Local Notifs provider and Mock
parent
fe6a24a0f2
commit
a256ab9775
|
@ -89,6 +89,11 @@
|
|||
"resolved": "https://registry.npmjs.org/@ionic-native/keyboard/-/keyboard-4.3.2.tgz",
|
||||
"integrity": "sha512-iTvFCONbE26+dXNdJAHmNxkfLbtN+CmiuJQUy56kvjzVvOTFI5gIZ0aE5nlbYpB1GY4DFQVw/+E+xc0Bpw5mKw=="
|
||||
},
|
||||
"@ionic-native/local-notifications": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@ionic-native/local-notifications/-/local-notifications-4.4.0.tgz",
|
||||
"integrity": "sha512-h5P3+9lH0UeJnIBAqSM2LeKsNoJqlFAu2N0/43EtZgDsOkfWOjNSKCsCcrEoBEMi/qUnZyxNLAHfSMiYVGiuew=="
|
||||
},
|
||||
"@ionic-native/network": {
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@ionic-native/network/-/network-4.3.2.tgz",
|
||||
|
|
|
@ -41,6 +41,7 @@
|
|||
"@ionic-native/globalization": "^4.3.2",
|
||||
"@ionic-native/in-app-browser": "^4.3.3",
|
||||
"@ionic-native/keyboard": "^4.3.2",
|
||||
"@ionic-native/local-notifications": "^4.4.0",
|
||||
"@ionic-native/network": "^4.3.2",
|
||||
"@ionic-native/splash-screen": "4.3.0",
|
||||
"@ionic-native/sqlite": "^4.3.2",
|
||||
|
|
|
@ -43,6 +43,7 @@ import { CoreWSProvider } from '../providers/ws';
|
|||
import { CoreEventsProvider } from '../providers/events';
|
||||
import { CoreSitesFactoryProvider } from '../providers/sites-factory';
|
||||
import { CoreSitesProvider } from '../providers/sites';
|
||||
import { CoreLocalNotificationsProvider } from '../providers/local-notifications';
|
||||
|
||||
// For translate loader. AoT requires an exported function for factories.
|
||||
export function createTranslateLoader(http: HttpClient) {
|
||||
|
@ -93,6 +94,7 @@ export function createTranslateLoader(http: HttpClient) {
|
|||
CoreEventsProvider,
|
||||
CoreSitesFactoryProvider,
|
||||
CoreSitesProvider,
|
||||
CoreLocalNotificationsProvider,
|
||||
]
|
||||
})
|
||||
export class AppModule {
|
||||
|
|
|
@ -23,7 +23,10 @@ export class CoreConstants {
|
|||
public static wifiDownloadThreshold = 104857600; // 100MB.
|
||||
public static downloadThreshold = 10485760; // 10MB.
|
||||
public static dontShowError = 'CoreDontShowError';
|
||||
|
||||
// Settings constants.
|
||||
public static settingsRichTextEditor = 'CoreSettingsRichTextEditor';
|
||||
public static settingsNotificationSound = 'CoreSettingsNotificationSound';
|
||||
|
||||
// WS constants.
|
||||
public static wsTimeout = 30000;
|
||||
|
|
|
@ -19,6 +19,7 @@ import { Clipboard } from '@ionic-native/clipboard';
|
|||
import { File } from '@ionic-native/file';
|
||||
import { FileTransfer } from '@ionic-native/file-transfer';
|
||||
import { Globalization } from '@ionic-native/globalization';
|
||||
import { LocalNotifications } from '@ionic-native/local-notifications';
|
||||
import { Network } from '@ionic-native/network';
|
||||
import { Zip } from '@ionic-native/zip';
|
||||
|
||||
|
@ -26,6 +27,7 @@ import { ClipboardMock } from './providers/clipboard';
|
|||
import { FileMock } from './providers/file';
|
||||
import { FileTransferMock } from './providers/file-transfer';
|
||||
import { GlobalizationMock } from './providers/globalization';
|
||||
import { LocalNotificationsMock } from './providers/local-notifications';
|
||||
import { NetworkMock } from './providers/network';
|
||||
import { ZipMock } from './providers/zip';
|
||||
import { InAppBrowser } from '@ionic-native/in-app-browser';
|
||||
|
@ -35,6 +37,7 @@ import { CoreAppProvider } from '../../providers/app';
|
|||
import { CoreFileProvider } from '../../providers/file';
|
||||
import { CoreTextUtilsProvider } from '../../providers/utils/text';
|
||||
import { CoreMimetypeUtilsProvider } from '../../providers/utils/mimetype';
|
||||
import { CoreUtilsProvider } from '../../providers/utils/utils';
|
||||
import { CoreInitDelegate } from '../../providers/init';
|
||||
|
||||
@NgModule({
|
||||
|
@ -44,8 +47,6 @@ import { CoreInitDelegate } from '../../providers/init';
|
|||
],
|
||||
providers: [
|
||||
CoreEmulatorHelper,
|
||||
ClipboardMock,
|
||||
GlobalizationMock,
|
||||
{
|
||||
provide: Clipboard,
|
||||
deps: [CoreAppProvider],
|
||||
|
@ -76,6 +77,14 @@ import { CoreInitDelegate } from '../../providers/init';
|
|||
return appProvider.isMobile() ? new Globalization() : new GlobalizationMock(appProvider);
|
||||
}
|
||||
},
|
||||
{
|
||||
provide: LocalNotifications,
|
||||
deps: [CoreAppProvider, CoreUtilsProvider],
|
||||
useFactory: (appProvider: CoreAppProvider, utils: CoreUtilsProvider) => {
|
||||
// Use platform instead of CoreAppProvider to prevent circular dependencies.
|
||||
return appProvider.isMobile() ? new LocalNotifications() : new LocalNotificationsMock(appProvider, utils);
|
||||
}
|
||||
},
|
||||
{
|
||||
provide: Network,
|
||||
deps: [Platform],
|
||||
|
|
|
@ -13,9 +13,11 @@
|
|||
// limitations under the License.
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CoreAppProvider } from '../../../providers/app';
|
||||
import { CoreFileProvider } from '../../../providers/file';
|
||||
import { CoreUtilsProvider } from '../../../providers/utils/utils';
|
||||
import { File } from '@ionic-native/file';
|
||||
import { LocalNotifications } from '@ionic-native/local-notifications';
|
||||
import { CoreInitDelegate, CoreInitHandler } from '../../../providers/init';
|
||||
import { FileTransferErrorMock } from './file-transfer';
|
||||
|
||||
|
@ -29,10 +31,64 @@ export class CoreEmulatorHelper implements CoreInitHandler {
|
|||
blocking = true;
|
||||
|
||||
constructor(private file: File, private fileProvider: CoreFileProvider, private utils: CoreUtilsProvider,
|
||||
initDelegate: CoreInitDelegate) {
|
||||
initDelegate: CoreInitDelegate, private localNotif: LocalNotifications, private appProvider: CoreAppProvider) {
|
||||
this.priority = initDelegate.MAX_RECOMMENDED_PRIORITY + 500;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the app is running in a Linux environment.
|
||||
*
|
||||
* @return {boolean} Whether it's running in a Linux environment.
|
||||
*/
|
||||
isLinux() : boolean {
|
||||
if (!this.appProvider.isDesktop()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
var os = require('os');
|
||||
return os.platform().indexOf('linux') === 0;
|
||||
} catch(ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the app is running in a Mac OS environment.
|
||||
*
|
||||
* @return {boolean} Whether it's running in a Mac OS environment.
|
||||
*/
|
||||
isMac() : boolean {
|
||||
if (!this.appProvider.isDesktop()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
var os = require('os');
|
||||
return os.platform().indexOf('darwin') === 0;
|
||||
} catch(ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the app is running in a Windows environment.
|
||||
*
|
||||
* @return {boolean} Whether it's running in a Windows environment.
|
||||
*/
|
||||
isWindows() : boolean {
|
||||
if (!this.appProvider.isDesktop()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
var os = require('os');
|
||||
return os.platform().indexOf('win') === 0;
|
||||
} catch(ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the Mocks that need it.
|
||||
*
|
||||
|
@ -44,6 +100,8 @@ export class CoreEmulatorHelper implements CoreInitHandler {
|
|||
promises.push((<any>this.file).load().then((basePath: string) => {
|
||||
this.fileProvider.setHTMLBasePath(basePath);
|
||||
}));
|
||||
promises.push((<any>this.localNotif).load(this.isWindows()));
|
||||
|
||||
(<any>window).FileTransferError = FileTransferErrorMock;
|
||||
|
||||
return this.utils.allPromises(promises);
|
||||
|
|
|
@ -0,0 +1,762 @@
|
|||
// (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 { LocalNotifications, ILocalNotification } from '@ionic-native/local-notifications';
|
||||
import { CoreAppProvider } from '../../../providers/app';
|
||||
import { CoreUtilsProvider } from '../../../providers/utils/utils';
|
||||
import { SQLiteDB } from '../../../classes/sqlitedb';
|
||||
import { CoreConstants } from '../../constants';
|
||||
import { CoreConfigConstants } from '../../../configconstants';
|
||||
import * as moment from 'moment';
|
||||
|
||||
/**
|
||||
* Emulates the Cordova Globalization plugin in desktop apps and in browser.
|
||||
*/
|
||||
@Injectable()
|
||||
export class LocalNotificationsMock extends LocalNotifications {
|
||||
// Variables for Windows notifications.
|
||||
protected winNotif; // Library for Windows notifications.
|
||||
// Templates for Windows ToastNotifications and TileNotifications.
|
||||
protected toastTemplate = '<toast><visual><binding template="ToastText02"><text id="1" hint-wrap="true">%s</text>' +
|
||||
'<text id="2" hint-wrap="true">%s</text></binding></visual></toast>';
|
||||
protected tileBindingTemplate = '<text hint-style="base" hint-wrap="true">%s</text>' +
|
||||
'<text hint-style="captionSubtle" hint-wrap="true">%s</text>';
|
||||
protected tileTemplate = '<tile><visual branding="nameAndLogo">' +
|
||||
'<binding template="TileMedium">' + this.tileBindingTemplate + '</binding>' +
|
||||
'<binding template="TileWide">' + this.tileBindingTemplate + '</binding>' +
|
||||
'<binding template="TileLarge">' + this.tileBindingTemplate + '</binding>' +
|
||||
'</visual></tile>';
|
||||
|
||||
// Variables for database.
|
||||
protected DESKTOP_NOTIFS_TABLE = 'desktop_local_notifications';
|
||||
protected tableSchema = {
|
||||
name: this.DESKTOP_NOTIFS_TABLE,
|
||||
columns: [
|
||||
{
|
||||
name: 'id',
|
||||
type: 'INTEGER',
|
||||
primaryKey: true
|
||||
},
|
||||
{
|
||||
name: 'triggered',
|
||||
type: 'INTEGER'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
protected appDB: SQLiteDB;
|
||||
protected scheduled: {[i: number] : any} = {};
|
||||
protected triggered: {[i: number] : any} = {};
|
||||
protected observers;
|
||||
protected defaults = {
|
||||
text: '',
|
||||
title: '',
|
||||
sound: '',
|
||||
badge: 0,
|
||||
id: 0,
|
||||
data: undefined,
|
||||
every: undefined,
|
||||
at: undefined
|
||||
};
|
||||
|
||||
constructor(private appProvider: CoreAppProvider, private utils: CoreUtilsProvider) {
|
||||
super();
|
||||
|
||||
this.appDB = appProvider.getDB();
|
||||
this.appDB.createTableFromSchema(this.tableSchema);
|
||||
|
||||
// Initialize observers.
|
||||
this.observers = {
|
||||
schedule: [],
|
||||
trigger: [],
|
||||
click: [],
|
||||
update: [],
|
||||
clear: [],
|
||||
clearall: [],
|
||||
cancel: [],
|
||||
cancelall: []
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Cancels single or multiple notifications
|
||||
* @param notificationId {any} A single notification id, or an array of notification ids.
|
||||
* @returns {Promise<any>} Returns a promise when the notification is canceled
|
||||
*/
|
||||
cancel(notificationId: any): Promise<any> {
|
||||
let promises = [];
|
||||
|
||||
notificationId = Array.isArray(notificationId) ? notificationId : [notificationId];
|
||||
notificationId = this.convertIds(notificationId);
|
||||
|
||||
// Cancel the notifications.
|
||||
notificationId.forEach((id) => {
|
||||
if (this.scheduled[id]) {
|
||||
promises.push(this.cancelNotification(id, false, 'cancel'));
|
||||
}
|
||||
});
|
||||
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels all notifications.
|
||||
*
|
||||
* @returns {Promise<any>} Returns a promise when all notifications are canceled.
|
||||
*/
|
||||
cancelAll(): Promise<any> {
|
||||
let ids = Object.keys(this.scheduled);
|
||||
return this.cancel(ids).then(() => {
|
||||
this.triggerEvent('cancelall', 'foreground');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel a local notification.
|
||||
*
|
||||
* @param {number} id Notification ID.
|
||||
* @param {boolean} omitEvent If true, the clear/cancel event won't be triggered.
|
||||
* @param {string} eventName Name of the event to trigger.
|
||||
* @return {Void}
|
||||
*/
|
||||
protected cancelNotification(id: number, omitEvent: boolean, eventName: string) : void {
|
||||
let notification = this.scheduled[id].notification;
|
||||
|
||||
clearTimeout(this.scheduled[id].timeout);
|
||||
clearInterval(this.scheduled[id].interval);
|
||||
delete this.scheduled[id];
|
||||
delete this.triggered[id];
|
||||
|
||||
this.removeNotification(id);
|
||||
|
||||
if (!omitEvent) {
|
||||
this.triggerEvent(eventName, notification, 'foreground');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears single or multiple notifications.
|
||||
*
|
||||
* @param {any} notificationId A single notification id, or an array of notification ids.
|
||||
* @returns {Promise<any>} Returns a promise when the notification had been cleared.
|
||||
*/
|
||||
clear(notificationId: any): Promise<any> {
|
||||
let promises = [];
|
||||
|
||||
notificationId = Array.isArray(notificationId) ? notificationId : [notificationId];
|
||||
notificationId = this.convertIds(notificationId);
|
||||
|
||||
// Clear the notifications.
|
||||
notificationId.forEach((id) => {
|
||||
// Cancel only the notifications that aren't repeating.
|
||||
if (this.scheduled[id] && this.scheduled[id].notification && !this.scheduled[id].notification.every) {
|
||||
promises.push(this.cancelNotification(id, false, 'clear'));
|
||||
}
|
||||
});
|
||||
|
||||
return Promise.all(promises);
|
||||
}
|
||||
/**
|
||||
* Clears all notifications.
|
||||
*
|
||||
* @returns {Promise<any>} Returns a promise when all notifications have cleared
|
||||
*/
|
||||
clearAll(): Promise<any> {
|
||||
let ids = Object.keys(this.scheduled);
|
||||
return this.clear(ids).then(() => {
|
||||
this.triggerEvent('clearall', 'foreground');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a list of IDs to numbers.
|
||||
* Code extracted from the Cordova plugin.
|
||||
*
|
||||
* @param {any[]} ids List of IDs.
|
||||
* @return {Number[]} List of IDs as numbers.
|
||||
*/
|
||||
protected convertIds(ids: any[]) : number[] {
|
||||
let convertedIds = [];
|
||||
|
||||
for (let i = 0; i < ids.length; i++) {
|
||||
convertedIds.push(Number(ids[i]));
|
||||
}
|
||||
|
||||
return convertedIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the notification options to their required type.
|
||||
* Code extracted from the Cordova plugin.
|
||||
*
|
||||
* @param {ILocalNotification} notification Notification.
|
||||
* @return {ILocalNotification} Converted notification.
|
||||
*/
|
||||
protected convertProperties(notification: ILocalNotification) : ILocalNotification {
|
||||
if (notification.id) {
|
||||
if (isNaN(notification.id)) {
|
||||
notification.id = this.defaults.id;
|
||||
} else {
|
||||
notification.id = Number(notification.id);
|
||||
}
|
||||
}
|
||||
|
||||
if (notification.title) {
|
||||
notification.title = notification.title.toString();
|
||||
}
|
||||
|
||||
if (notification.text) {
|
||||
notification.text = notification.text.toString();
|
||||
}
|
||||
|
||||
if (notification.badge) {
|
||||
if (isNaN(notification.badge)) {
|
||||
notification.badge = this.defaults.badge;
|
||||
} else {
|
||||
notification.badge = Number(notification.badge);
|
||||
}
|
||||
}
|
||||
|
||||
if (notification.at) {
|
||||
if (typeof notification.at == 'object') {
|
||||
notification.at = notification.at.getTime();
|
||||
}
|
||||
|
||||
notification.at = Math.round(notification.at / 1000);
|
||||
}
|
||||
|
||||
if (typeof notification.data == 'object') {
|
||||
notification.data = JSON.stringify(notification.data);
|
||||
}
|
||||
|
||||
return notification;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a notification object.
|
||||
*
|
||||
* @param {any} notificationId The id of the notification to get.
|
||||
* @returns {Promise<ILocalNotification>}
|
||||
*/
|
||||
get(notificationId: any): Promise<ILocalNotification> {
|
||||
return Promise.resolve(this.getNotifications([Number(notificationId)], true, true)[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all notification objects.
|
||||
*
|
||||
* @returns {Promise<Array<ILocalNotification>>}
|
||||
*/
|
||||
getAll(): Promise<Array<ILocalNotification>> {
|
||||
return Promise.resolve(this.getNotifications(null, true, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the notification ids.
|
||||
*
|
||||
* @returns {Promise<Array<number>>}
|
||||
*/
|
||||
getAllIds(): Promise<Array<number>> {
|
||||
let ids = this.utils.mergeArraysWithoutDuplicates(Object.keys(this.scheduled), Object.keys(this.triggered));
|
||||
ids = ids.map((id) => {
|
||||
return Number(id);
|
||||
});
|
||||
return Promise.resolve(ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the notification stored in local DB.
|
||||
*
|
||||
* @return {Promise<any>} Promise resolved with the notifications.
|
||||
*/
|
||||
protected getAllNotifications() : Promise<any> {
|
||||
return this.appDB.getAllRecords(this.DESKTOP_NOTIFS_TABLE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all scheduled notification objects.
|
||||
*
|
||||
* @returns {Promise<Array<ILocalNotification>>}
|
||||
*/
|
||||
getAllScheduled(): Promise<Array<ILocalNotification>> {
|
||||
return Promise.resolve(this.getNotifications(null, true, false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all triggered notification objects.
|
||||
*
|
||||
* @returns {Promise<Array<ILocalNotification>>}
|
||||
*/
|
||||
getAllTriggered(): Promise<Array<ILocalNotification>> {
|
||||
return Promise.resolve(this.getNotifications(null, false, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a set of notifications. If ids isn't specified, return all the notifications.
|
||||
*
|
||||
* @param {Number[]} [ids] Ids of notifications to get. If not specified, get all notifications.
|
||||
* @param {boolean} [getScheduled] Get scheduled notifications.
|
||||
* @param {boolean} [getTriggered] Get triggered notifications.
|
||||
* @return {ILocalNotification[]} List of notifications.
|
||||
*/
|
||||
protected getNotifications(ids?: number[], getScheduled?: boolean, getTriggered?: boolean) : ILocalNotification[] {
|
||||
let notifications = [];
|
||||
|
||||
if (getScheduled) {
|
||||
for (let id in this.scheduled) {
|
||||
if (!ids || ids.indexOf(Number(id)) != -1) {
|
||||
notifications.push(this.scheduled[id].notification);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (getTriggered) {
|
||||
for (let id in this.triggered) {
|
||||
if ((!getScheduled || !this.scheduled[id]) && (!ids || ids.indexOf(Number(id)) != -1)) {
|
||||
notifications.push(this.triggered[id].notification);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return notifications;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a scheduled notification object.
|
||||
*
|
||||
* @param {any} notificationId The id of the notification to ge.
|
||||
* @returns {Promise<ILocalNotification>}
|
||||
*/
|
||||
getScheduled(notificationId: any): Promise<ILocalNotification> {
|
||||
return Promise.resolve(this.getNotifications([Number(notificationId)], true, false)[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ids of scheduled notifications.
|
||||
*
|
||||
* @returns {Promise<Array<number>>} Returns a promise
|
||||
*/
|
||||
getScheduledIds(): Promise<Array<number>> {
|
||||
let ids = Object.keys(this.scheduled).map((id) => {
|
||||
return Number(id);
|
||||
});
|
||||
return Promise.resolve(ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a triggered notification object.
|
||||
*
|
||||
* @param {any} notificationId The id of the notification to get.
|
||||
* @returns {Promise<ILocalNotification>}
|
||||
*/
|
||||
getTriggered(notificationId: any): Promise<ILocalNotification> {
|
||||
return Promise.resolve(this.getNotifications([Number(notificationId)], false, true)[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ids of triggered notifications.
|
||||
*
|
||||
* @returns {Promise<Array<number>>}
|
||||
*/
|
||||
getTriggeredIds(): Promise<Array<number>> {
|
||||
let ids = Object.keys(this.triggered).map((id) => {
|
||||
return Number(id);
|
||||
});
|
||||
return Promise.resolve(ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an object of options and a list of properties, return the first property that exists.
|
||||
* Code extracted from the Cordova plugin.
|
||||
*
|
||||
* @param {ILocalNotification} notification Notification.
|
||||
* @param {any} ...args List of keys to check.
|
||||
* @return {any} First value found.
|
||||
*/
|
||||
protected getValueFor(notification: ILocalNotification, ...args) : any {
|
||||
for (let i in args) {
|
||||
const key = args[i];
|
||||
if (notification.hasOwnProperty(key)) {
|
||||
return notification[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Informs if the app has the permission to show notifications.
|
||||
*
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
hasPermission(): Promise<boolean> {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks presence of a notification.
|
||||
*
|
||||
* @param {number} notificationId Notification ID.
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
isPresent(notificationId: number): Promise<boolean> {
|
||||
return Promise.resolve(!!this.scheduled[notificationId] || !!this.triggered[notificationId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks is a notification is scheduled.
|
||||
*
|
||||
* @param {number} notificationId Notification ID.
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
isScheduled(notificationId: number): Promise<boolean> {
|
||||
return Promise.resolve(!!this.scheduled[notificationId]);
|
||||
}
|
||||
/**
|
||||
* Checks if a notification is triggered.
|
||||
*
|
||||
* @param {number} notificationId Notification ID.
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
isTriggered(notificationId: number): Promise<boolean> {
|
||||
return Promise.resolve(!!this.triggered[notificationId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads an initialize the API for desktop.
|
||||
*
|
||||
* @param {boolean} isWindows Whether the app is running in a Windows environment.
|
||||
* @return {Promise<any>} Promise resolved when done.
|
||||
*/
|
||||
load(isWindows: boolean) : Promise<any> {
|
||||
if (!this.appProvider.isDesktop()) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
if (isWindows) {
|
||||
try {
|
||||
this.winNotif = require('electron-windows-notifications');
|
||||
} catch(ex) {}
|
||||
}
|
||||
|
||||
|
||||
// App is being loaded, re-schedule all the notifications that were scheduled before.
|
||||
return this.getAllNotifications().catch(() => {
|
||||
return [];
|
||||
}).then((notifications) => {
|
||||
notifications.forEach((notification) => {
|
||||
if (notification.triggered) {
|
||||
// Notification was triggered already, store it in memory but don't schedule it again.
|
||||
delete notification.triggered;
|
||||
this.scheduled[notification.id] = {
|
||||
notification: notification
|
||||
};
|
||||
this.triggered[notification.id] = notification;
|
||||
} else {
|
||||
// Schedule the notification again unless it should have been triggered more than an hour ago.
|
||||
delete notification.triggered;
|
||||
notification.at = notification.at * 1000;
|
||||
if (notification.at - Date.now() > - CoreConstants.secondsHour * 1000) {
|
||||
this.schedule(notification);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge notification options with default values.
|
||||
* Code extracted from the Cordova plugin.
|
||||
*
|
||||
* @param {ILocalNotification} notification Notification.
|
||||
* @return {ILocalNotification} Treated notification.
|
||||
*/
|
||||
protected mergeWithDefaults(notification: ILocalNotification) : ILocalNotification {
|
||||
notification.at = this.getValueFor(notification, 'at', 'firstAt', 'date');
|
||||
notification.text = this.getValueFor(notification, 'text', 'message');
|
||||
notification.data = this.getValueFor(notification, 'data', 'json');
|
||||
|
||||
if (notification.at === undefined || notification.at === null) {
|
||||
notification.at = new Date();
|
||||
}
|
||||
|
||||
for (let key in this.defaults) {
|
||||
if (notification[key] === null || notification[key] === undefined) {
|
||||
if (notification.hasOwnProperty(key) && ['data','sound'].indexOf(key) > -1) {
|
||||
notification[key] = undefined;
|
||||
} else {
|
||||
notification[key] = this.defaults[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let key in notification) {
|
||||
if (!this.defaults.hasOwnProperty(key)) {
|
||||
delete notification[key];
|
||||
}
|
||||
}
|
||||
|
||||
return notification;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function called when a notification is clicked.
|
||||
*
|
||||
* @param {ILocalNotification} notification Clicked notification.
|
||||
*/
|
||||
protected notificationClicked(notification: ILocalNotification) : void {
|
||||
this.triggerEvent('click', notification, 'foreground');
|
||||
// Focus the app.
|
||||
require('electron').ipcRenderer.send('focusApp');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a callback for a specific event.
|
||||
*
|
||||
* @param {string} eventName Name of the event. Events: schedule, trigger, click, update, clear, clearall, cancel, cancelall
|
||||
* @param {any} callback Call back function.
|
||||
*/
|
||||
on(eventName: string, callback: any): void {
|
||||
if (!this.observers[eventName] || typeof callback != 'function') {
|
||||
// Event not supported, stop.
|
||||
return;
|
||||
}
|
||||
this.observers[eventName].push(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a interval and convert it to a number of milliseconds (0 if not valid).
|
||||
* Code extracted from the Cordova plugin.
|
||||
*
|
||||
* @param {string} every Interval to convert.
|
||||
* @return {number} Number of milliseconds of the interval-
|
||||
*/
|
||||
protected parseInterval(every: string) {
|
||||
let interval;
|
||||
|
||||
every = String(every).toLowerCase();
|
||||
|
||||
if (!every || every == 'undefined') {
|
||||
interval = 0;
|
||||
} else if (every == 'second') {
|
||||
interval = 1000;
|
||||
} else if (every == 'minute') {
|
||||
interval = CoreConstants.secondsMinute * 1000;
|
||||
} else if (every == 'hour') {
|
||||
interval = CoreConstants.secondsHour * 1000;
|
||||
} else if (every == 'day') {
|
||||
interval = CoreConstants.secondsDay * 1000;
|
||||
} else if (every == 'week') {
|
||||
interval = CoreConstants.secondsDay * 7 * 1000;
|
||||
} else if (every == 'month') {
|
||||
interval = CoreConstants.secondsDay * 31 * 1000;
|
||||
} else if (every == 'quarter') {
|
||||
interval = CoreConstants.secondsHour * 2190 * 1000;
|
||||
} else if (every == 'year') {
|
||||
interval = CoreConstants.secondsYear * 1000;
|
||||
} else {
|
||||
interval = parseInt(every, 10);
|
||||
if (isNaN(interval)) {
|
||||
interval = 0;
|
||||
} else {
|
||||
interval *= 60000;
|
||||
}
|
||||
}
|
||||
|
||||
return interval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register permission to show notifications if not already granted.
|
||||
*
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
registerPermission(): Promise<boolean> {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a notification from local DB.
|
||||
*
|
||||
* @param {number} id ID of the notification.
|
||||
* @return {Promise<any>} Promise resolved when done.
|
||||
*/
|
||||
protected removeNotification(id: number) : Promise<any> {
|
||||
return this.appDB.deleteRecords(this.DESKTOP_NOTIFS_TABLE, {id: id});
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules a single or multiple notifications.
|
||||
*
|
||||
* @param {ILocalNotification | Array<ILocalNotification>} [options] Notification or notifications.
|
||||
*/
|
||||
schedule(options?: ILocalNotification | Array<ILocalNotification>): void {
|
||||
this.scheduleOrUpdate(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules or updates a single or multiple notifications.
|
||||
*
|
||||
* @param {ILocalNotification | Array<ILocalNotification>} [options] Notification or notifications.
|
||||
* @param {string} [eventName] Name of the event: schedule or update.
|
||||
*/
|
||||
protected scheduleOrUpdate(options?: ILocalNotification | Array<ILocalNotification>, eventName = 'schedule'): void {
|
||||
options = Array.isArray(options) ? options : [options];
|
||||
|
||||
options.forEach((notification) => {
|
||||
this.mergeWithDefaults(notification);
|
||||
this.convertProperties(notification);
|
||||
|
||||
// Cancel current notification if exists.
|
||||
this.cancelNotification(notification.id, true, 'cancel');
|
||||
|
||||
// Store the notification in the scheduled list and in the DB.
|
||||
this.scheduled[notification.id] = {
|
||||
notification: notification
|
||||
};
|
||||
this.storeNotification(notification, false);
|
||||
|
||||
if (Math.abs(moment().diff(notification.at * 1000, 'days')) > 15) {
|
||||
// Notification should trigger more than 15 days from now, don't schedule it.
|
||||
return;
|
||||
}
|
||||
|
||||
// Schedule the notification.
|
||||
let toTriggerTime = notification.at * 1000 - Date.now(),
|
||||
trigger = () => {
|
||||
// Trigger the notification.
|
||||
this.triggerNotification(notification);
|
||||
|
||||
// Store the notification as triggered. Don't remove it from scheduled, it's how the plugin works.
|
||||
this.triggered[notification.id] = notification;
|
||||
this.storeNotification(notification, true);
|
||||
|
||||
// Launch the trigger event.
|
||||
this.triggerEvent('trigger', notification, 'foreground');
|
||||
|
||||
if (notification.every && this.scheduled[notification.id] && !this.scheduled[notification.id].interval) {
|
||||
const interval = this.parseInterval(notification.every);
|
||||
if (interval > 0) {
|
||||
this.scheduled[notification.id].interval = setInterval(trigger, interval);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.scheduled[notification.id].timeout = setTimeout(trigger, toTriggerTime);
|
||||
|
||||
// Launch the scheduled/update event.
|
||||
this.triggerEvent(eventName, notification, 'foreground');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a notification in local DB.
|
||||
*
|
||||
* @param {any} notification Notification to store.
|
||||
* @param {boolean} triggered Whether the notification has been triggered.
|
||||
* @return {Promise<any>} Promise resolved when stored.
|
||||
*/
|
||||
protected storeNotification(notification: any, triggered: boolean) : Promise<any> {
|
||||
notification = Object.assign({}, notification); // Clone the object.
|
||||
notification.triggered = !!triggered;
|
||||
return this.appDB.insertOrUpdateRecord(this.DESKTOP_NOTIFS_TABLE, notification, {id: notification.id});
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger an event.
|
||||
*
|
||||
* @param {string} eventName Event name.
|
||||
* @param {any[]} ...args List of parameters to pass.
|
||||
*/
|
||||
protected triggerEvent(eventName: string, ...args: any[]) {
|
||||
if (this.observers[eventName]) {
|
||||
this.observers[eventName].forEach((callback) => {
|
||||
callback.apply(null, args);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger a notification, using the best method depending on the OS.
|
||||
*
|
||||
* @param {ILocalNotification} notification Notification to trigger.
|
||||
*/
|
||||
protected triggerNotification(notification: ILocalNotification) : void {
|
||||
if (this.winNotif) {
|
||||
// Use Windows notifications.
|
||||
let notifInstance = new this.winNotif.ToastNotification({
|
||||
appId: CoreConfigConstants.app_id,
|
||||
template: this.toastTemplate,
|
||||
strings: [notification.title, notification.text]
|
||||
});
|
||||
|
||||
// Listen for click events.
|
||||
notifInstance.on('activated', () => {
|
||||
this.notificationClicked(notification);
|
||||
});
|
||||
|
||||
notifInstance.show();
|
||||
|
||||
try {
|
||||
// Show it in Tile too.
|
||||
let tileNotif = new this.winNotif.TileNotification({
|
||||
tag: notification.id + '',
|
||||
template: this.tileTemplate,
|
||||
strings: [notification.title, notification.text, notification.title, notification.text, notification.title, notification.text],
|
||||
expirationTime: new Date(Date.now() + CoreConstants.secondsHour * 1000) // Expire in 1 hour.
|
||||
})
|
||||
|
||||
tileNotif.show()
|
||||
} catch(ex) {
|
||||
console.warn('Error showing TileNotification. Please notice they only work with the app installed.', ex);
|
||||
}
|
||||
} else {
|
||||
// Use Electron default notifications.
|
||||
const notifInstance = new Notification(notification.title, {
|
||||
body: notification.text
|
||||
});
|
||||
|
||||
// Listen for click events.
|
||||
notifInstance.onclick = () => {
|
||||
this.notificationClicked(notification);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a callback of a specific event.
|
||||
*
|
||||
* @param {string} eventName Name of the event. Events: schedule, trigger, click, update, clear, clearall, cancel, cancelall
|
||||
* @param {any} callback Call back function.
|
||||
*/
|
||||
un(eventName: string, callback: any): void {
|
||||
if (this.observers[eventName] && this.observers[eventName].length) {
|
||||
for (let i = 0; i < this.observers[eventName].length; i++) {
|
||||
if (this.observers[eventName][i] == callback) {
|
||||
this.observers[eventName].splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a previously scheduled notification. Must include the id in the options parameter.
|
||||
*
|
||||
* @param {ILocalNotification} [options] Notification.
|
||||
*/
|
||||
update(options?: ILocalNotification): void {
|
||||
return this.scheduleOrUpdate(options, 'update');
|
||||
};
|
||||
}
|
|
@ -0,0 +1,484 @@
|
|||
// (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 { LocalNotifications, ILocalNotification } from '@ionic-native/local-notifications';
|
||||
import { CoreAppProvider } from './app';
|
||||
import { CoreConfigProvider } from './config';
|
||||
import { CoreLoggerProvider } from './logger';
|
||||
import { CoreDomUtilsProvider } from './utils/dom';
|
||||
import { CoreUtilsProvider } from './utils/utils';
|
||||
import { SQLiteDB } from '../classes/sqlitedb';
|
||||
import { CoreConstants } from '../core/constants';
|
||||
import { Subject } from 'rxjs';
|
||||
|
||||
export interface CoreILocalNotification extends ILocalNotification {
|
||||
ledOnTime?: number;
|
||||
ledOffTime?: number;
|
||||
}
|
||||
|
||||
/*
|
||||
Generated class for the LocalNotificationsProvider provider.
|
||||
|
||||
See https://angular.io/guide/dependency-injection for more info on providers
|
||||
and Angular DI.
|
||||
*/
|
||||
@Injectable()
|
||||
export class CoreLocalNotificationsProvider {
|
||||
// Variables for the database.
|
||||
protected SITES_TABLE = 'notification_sites'; // Store to asigne unique codes to each site.
|
||||
protected COMPONENTS_TABLE = 'notification_components'; // Store to asigne unique codes to each component.
|
||||
protected TRIGGERED_TABLE = 'notifications_triggered'; // Store to prevent re-triggering notifications.
|
||||
protected tablesSchema = [
|
||||
{
|
||||
name: this.SITES_TABLE,
|
||||
columns: [
|
||||
{
|
||||
name: 'id',
|
||||
type: 'TEXT',
|
||||
primaryKey: true
|
||||
},
|
||||
{
|
||||
name: 'code',
|
||||
type: 'INTEGER',
|
||||
notNull: true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: this.COMPONENTS_TABLE,
|
||||
columns: [
|
||||
{
|
||||
name: 'id',
|
||||
type: 'TEXT',
|
||||
primaryKey: true
|
||||
},
|
||||
{
|
||||
name: 'code',
|
||||
type: 'INTEGER',
|
||||
notNull: true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: this.TRIGGERED_TABLE,
|
||||
columns: [
|
||||
{
|
||||
name: 'id',
|
||||
type: 'INTEGER',
|
||||
primaryKey: true
|
||||
},
|
||||
{
|
||||
name: 'at',
|
||||
type: 'INTEGER',
|
||||
notNull: true
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
protected logger;
|
||||
protected appDB: SQLiteDB;
|
||||
protected codes: {[s: string]: number} = {};
|
||||
protected codeRequestsQueue = {};
|
||||
protected observables = {};
|
||||
|
||||
constructor(logger: CoreLoggerProvider, private localNotifications: LocalNotifications, private platform: Platform,
|
||||
private appProvider: CoreAppProvider, private utils: CoreUtilsProvider, private configProvider: CoreConfigProvider,
|
||||
private domUtils: CoreDomUtilsProvider) {
|
||||
this.logger = logger.getInstance('CoreLocalNotificationsProvider');
|
||||
this.appDB = appProvider.getDB();
|
||||
this.appDB.createTablesFromSchema(this.tablesSchema);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel a local notification.
|
||||
*
|
||||
* @param {number} id Notification id.
|
||||
* @param {string} component Component of the notification.
|
||||
* @param {string} siteId Site ID.
|
||||
* @return {Promise<any>} Promise resolved when the notification is cancelled.
|
||||
*/
|
||||
cancel(id, component, siteId) : Promise<any> {
|
||||
return this.getUniqueNotificationId(id, component, siteId).then((uniqueId) => {
|
||||
return this.localNotifications.cancel(uniqueId);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel all the scheduled notifications belonging to a certain site.
|
||||
*
|
||||
* @param {string} siteId Site ID.
|
||||
* @return {Promise<any>} Promise resolved when the notifications are cancelled.
|
||||
*/
|
||||
cancelSiteNotifications(siteId) : Promise<any> {
|
||||
|
||||
if (!this.isAvailable()) {
|
||||
return Promise.resolve();
|
||||
} else if (!siteId) {
|
||||
return Promise.reject(null);
|
||||
}
|
||||
|
||||
return this.localNotifications.getAllScheduled().then((scheduled) => {
|
||||
let ids = [];
|
||||
|
||||
scheduled.forEach((notif) => {
|
||||
if (typeof notif.data == 'string') {
|
||||
notif.data = JSON.parse(notif.data);
|
||||
}
|
||||
|
||||
if (typeof notif.data == 'object' && notif.data.siteId === siteId) {
|
||||
ids.push(notif.id);
|
||||
}
|
||||
});
|
||||
|
||||
return this.localNotifications.cancel(ids);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a code to create unique notifications. If there's no code assigned, create a new one.
|
||||
*
|
||||
* @param {string} table Table to search in local DB.
|
||||
* @param {string} id ID of the element to get its code.
|
||||
* @return {Promise<number>} Promise resolved when the code is retrieved.
|
||||
*/
|
||||
protected getCode(table, id) : Promise<number> {
|
||||
const key = table + '#' + id;
|
||||
|
||||
// Check if the code is already in memory.
|
||||
if (typeof this.codes[key] != 'undefined') {
|
||||
return Promise.resolve(this.codes[key]);
|
||||
}
|
||||
|
||||
// Check if we already have a code stored for that ID.
|
||||
return this.appDB.getRecord(table, {id: id}).then((entry) => {
|
||||
this.codes[key] = entry.code;
|
||||
return entry.code;
|
||||
}).catch(() => {
|
||||
// No code stored for that ID. Create a new code for it.
|
||||
return this.appDB.getRecords(table, undefined, 'code DESC').then((entries) => {
|
||||
let newCode = 0;
|
||||
if (entries.length > 0) {
|
||||
newCode = entries[0].code + 1;
|
||||
}
|
||||
return this.appDB.insertRecord(table, {id: id, code: newCode}).then(() => {
|
||||
this.codes[key] = newCode;
|
||||
return newCode;
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a notification component code to be used.
|
||||
* If it's the first time this component is used to send notifications, create a new code for it.
|
||||
*
|
||||
* @param {string} component Component name.
|
||||
* @return {Promise<number>} Promise resolved when the component code is retrieved.
|
||||
*/
|
||||
protected getComponentCode(component: string) : Promise<number> {
|
||||
return this.requestCode(this.COMPONENTS_TABLE, component);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a site code to be used.
|
||||
* If it's the first time this site is used to send notifications, create a new code for it.
|
||||
*
|
||||
* @param {string} siteId Site ID.
|
||||
* @return {Promise<number>} Promise resolved when the site code is retrieved.
|
||||
*/
|
||||
protected getSiteCode(siteId: string) : Promise<number> {
|
||||
return this.requestCode(this.SITES_TABLE, siteId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a unique notification ID, trying to prevent collisions. Generated ID must be a Number (Android).
|
||||
* The generated ID shouldn't be higher than 2147483647 or it's going to cause problems in Android.
|
||||
* This function will prevent collisions and keep the number under Android limit if:
|
||||
* -User has used less than 21 sites.
|
||||
* -There are less than 11 components.
|
||||
* -The notificationId passed as parameter is lower than 10000000.
|
||||
*
|
||||
* @param {number} notificationId Notification ID.
|
||||
* @param {string} component Component triggering the notification.
|
||||
* @param {string} siteId Site ID.
|
||||
* @return {Promise<number>} Promise resolved when the notification ID is generated.
|
||||
*/
|
||||
protected getUniqueNotificationId(notificationId: number, component: string, siteId: string) : Promise<number> {
|
||||
if (!siteId || !component) {
|
||||
return Promise.reject(null);
|
||||
}
|
||||
|
||||
return this.getSiteCode(siteId).then((siteCode) => {
|
||||
return this.getComponentCode(component).then((componentCode) => {
|
||||
// We use the % operation to keep the number under Android's limit.
|
||||
return (siteCode * 100000000 + componentCode * 10000000 + notificationId) % 2147483647;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether local notifications plugin is installed.
|
||||
*
|
||||
* @return {boolean} Whether local notifications plugin is installed.
|
||||
*/
|
||||
isAvailable() : boolean {
|
||||
let win = <any>window;
|
||||
return this.appProvider.isDesktop() || !!(win.plugin && win.plugin.notification && win.plugin.notification.local);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a notification has been triggered with the same trigger time.
|
||||
*
|
||||
* @param {CoreILocalNotification} notification Notification to check.
|
||||
* @return {Promise} Promise resolved with a boolean indicating if promise is triggered (true) or not.
|
||||
*/
|
||||
isTriggered(notification: CoreILocalNotification) {
|
||||
return this.appDB.getRecord(this.TRIGGERED_TABLE, {id: notification.id}).then((stored) => {
|
||||
return stored.at === notification.at.getTime() / 1000;
|
||||
}).catch(() => {
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify notification click to observers. Only the observers with the same component as the notification will be notified.
|
||||
*
|
||||
* @param {any} data Data received by the notification.
|
||||
*/
|
||||
notifyClick(data: any) : void {
|
||||
const component = data.component;
|
||||
if (component) {
|
||||
if (this.observables[component]) {
|
||||
this.observables[component].next(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the next request in queue.
|
||||
*/
|
||||
protected processNextRequest() : void {
|
||||
let nextKey = Object.keys(this.codeRequestsQueue)[0],
|
||||
request,
|
||||
promise;
|
||||
|
||||
if (typeof nextKey == 'undefined') {
|
||||
// No more requests in queue, stop.
|
||||
return;
|
||||
}
|
||||
|
||||
request = this.codeRequestsQueue[nextKey];
|
||||
|
||||
// Check if request is valid.
|
||||
if (typeof request == 'object' && typeof request.table != 'undefined' && typeof request.id != 'undefined') {
|
||||
// Get the code and resolve/reject all the promises of this request.
|
||||
promise = this.getCode(request.table, request.id).then((code) => {
|
||||
request.promises.forEach((p) => {
|
||||
p.resolve(code);
|
||||
});
|
||||
}).catch((error) => {
|
||||
request.promises.forEach((p) => {
|
||||
p.reject(error);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
promise = Promise.resolve();
|
||||
}
|
||||
|
||||
// Once this item is treated, remove it and process next.
|
||||
promise.finally(() => {
|
||||
delete this.codeRequestsQueue[nextKey];
|
||||
this.processNextRequest();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Register an observer to be notified when a notification belonging to a certain component is clicked.
|
||||
*
|
||||
* @param {string} component Component to listen notifications for.
|
||||
* @param {Function} callback Function to call with the data received by the notification.
|
||||
* @return {any} Object with an "off" property to stop listening for clicks.
|
||||
*/
|
||||
registerClick(component: string, callback: Function) : any {
|
||||
this.logger.debug(`Register observer '${component}' for notification click.`);
|
||||
|
||||
if (typeof this.observables[component] == 'undefined') {
|
||||
// No observable for this component, create a new one.
|
||||
this.observables[component] = new Subject<any>();
|
||||
}
|
||||
|
||||
this.observables[component].subscribe(callback);
|
||||
|
||||
return {
|
||||
off: () => {
|
||||
this.observables[component].unsubscribe(callback);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a notification from triggered store.
|
||||
*
|
||||
* @param {number} id Notification ID.
|
||||
* @return {Promise<any>} Promise resolved when it is removed.
|
||||
*/
|
||||
removeTriggered(id: number) : Promise<any> {
|
||||
return this.appDB.deleteRecords(this.TRIGGERED_TABLE, {id: id});
|
||||
}
|
||||
|
||||
/**
|
||||
* Request a unique code. The request will be added to the queue and the queue is going to be started if it's paused.
|
||||
*
|
||||
* @param {string} table Table to search in local DB.
|
||||
* @param {string} id ID of the element to get its code.
|
||||
* @return {Promise} Promise resolved when the code is retrieved.
|
||||
*/
|
||||
protected requestCode(table: string, id: string) : Promise<number> {
|
||||
let deferred = this.utils.promiseDefer(),
|
||||
key = table + '#' + id,
|
||||
isQueueEmpty = Object.keys(this.codeRequestsQueue).length == 0;
|
||||
|
||||
if (typeof this.codeRequestsQueue[key] != 'undefined') {
|
||||
// There's already a pending request for this store and ID, add the promise to it.
|
||||
this.codeRequestsQueue[key].promises.push(deferred);
|
||||
} else {
|
||||
// Add a pending request to the queue.
|
||||
this.codeRequestsQueue[key] = {
|
||||
table: table,
|
||||
id: id,
|
||||
promises: [deferred]
|
||||
}
|
||||
}
|
||||
|
||||
if (isQueueEmpty) {
|
||||
this.processNextRequest();
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reschedule all notifications that are already scheduled.
|
||||
*
|
||||
* @return {Promise<any>} Promise resolved when all notifications have been rescheduled.
|
||||
*/
|
||||
rescheduleAll() : Promise<any> {
|
||||
// Get all the scheduled notifications.
|
||||
return this.localNotifications.getAllScheduled().then((notifications) => {
|
||||
let promises = [];
|
||||
|
||||
notifications.forEach((notification) => {
|
||||
// Convert some properties to the needed types.
|
||||
notification.at = new Date(notification.at * 1000);
|
||||
notification.data = notification.data ? JSON.parse(notification.data) : {};
|
||||
|
||||
promises.push(this.scheduleNotification(notification));
|
||||
});
|
||||
|
||||
return Promise.all(promises);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule a local notification.
|
||||
*
|
||||
* @param {CoreILocalNotification} notification Notification to schedule. Its ID should be lower than 10000000 and it should
|
||||
* be unique inside its component and site.
|
||||
* @param {string} component Component triggering the notification. It is used to generate unique IDs.
|
||||
* @param {string} siteId Site ID.
|
||||
* @return {Promise<any>} Promise resolved when the notification is scheduled.
|
||||
*/
|
||||
schedule(notification: CoreILocalNotification, component: string, siteId: string) : Promise<any> {
|
||||
return this.getUniqueNotificationId(notification.id, component, siteId).then((uniqueId) => {
|
||||
notification.id = uniqueId;
|
||||
notification.data = notification.data || {};
|
||||
notification.data.component = component;
|
||||
notification.data.siteId = siteId;
|
||||
|
||||
if (this.platform.is('android')) {
|
||||
notification.icon = notification.icon || 'res://icon';
|
||||
notification.smallIcon = notification.smallIcon || 'res://icon';
|
||||
notification.led = notification.led || 'FF9900';
|
||||
notification.ledOnTime = notification.ledOnTime || 1000;
|
||||
notification.ledOffTime = notification.ledOffTime || 1000;
|
||||
}
|
||||
|
||||
return this.scheduleNotification(notification);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to schedule a notification object if it hasn't been triggered already.
|
||||
*
|
||||
* @param {CoreILocalNotification} notification Notification to schedule.
|
||||
* @return {Promise<any>} Promise resolved when scheduled.
|
||||
*/
|
||||
protected scheduleNotification(notification: CoreILocalNotification) : Promise<any> {
|
||||
// Check if the notification has been triggered already.
|
||||
return this.isTriggered(notification).then((triggered) => {
|
||||
if (!triggered) {
|
||||
// Check if sound is enabled for notifications.
|
||||
return this.configProvider.get(CoreConstants.settingsNotificationSound, true).then((soundEnabled) => {
|
||||
if (!soundEnabled) {
|
||||
notification.sound = null;
|
||||
} else {
|
||||
delete notification.sound; // Use default value.
|
||||
}
|
||||
|
||||
// Remove from triggered, since the notification could be in there with a different time.
|
||||
this.removeTriggered(notification.id);
|
||||
this.localNotifications.schedule(notification);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Show an in app notification popover.
|
||||
*
|
||||
* @param {CoreILocalNotification} notification Notification.
|
||||
*/
|
||||
showNotificationPopover(notification: CoreILocalNotification) : void {
|
||||
// @todo Improve it. For now, show Toast.
|
||||
if (!notification || !notification.title || !notification.text) {
|
||||
// Invalid data.
|
||||
return;
|
||||
}
|
||||
|
||||
this.domUtils.showToast(notification.text, false, 4000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to call when a notification is triggered. Stores the notification so it's not scheduled again unless the
|
||||
* time is changed.
|
||||
*
|
||||
* @param {CoreILocalNotification} notification Triggered notification.
|
||||
* @return {Promise<any>} Promise resolved when stored, rejected otherwise.
|
||||
*/
|
||||
trigger(notification: CoreILocalNotification) : Promise<any> {
|
||||
if (this.platform.is('ios') && this.platform.version().num >= 10) {
|
||||
// In iOS10 show in app notification.
|
||||
this.showNotificationPopover(notification);
|
||||
}
|
||||
|
||||
let entry = {
|
||||
id: notification.id,
|
||||
at: parseInt(notification.at, 10)
|
||||
};
|
||||
return this.appDB.insertOrUpdateRecord(this.TRIGGERED_TABLE, entry, {id: notification.id});
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue