diff --git a/src/addon/messageoutput/airnotifier/pages/devices/devices.ts b/src/addon/messageoutput/airnotifier/pages/devices/devices.ts index cdc4a4d47..0bea819b4 100644 --- a/src/addon/messageoutput/airnotifier/pages/devices/devices.ts +++ b/src/addon/messageoutput/airnotifier/pages/devices/devices.ts @@ -28,7 +28,6 @@ import { AddonMessageOutputAirnotifierProvider } from '../../providers/airnotifi }) export class AddonMessageOutputAirnotifierDevicesPage implements OnDestroy { - title = ''; devices = []; devicesLoaded = false; @@ -108,8 +107,8 @@ export class AddonMessageOutputAirnotifierDevicesPage implements OnDestroy { /** * Enable or disable a certain device. * - * @param {any} device - * @param {boolean} enable + * @param {any} device The device object. + * @param {boolean} enable True to enable the device, false to disable it. */ enableDevice(device: any, enable: boolean): void { device.updating = true; diff --git a/src/addon/messageoutput/airnotifier/providers/airnotifier.ts b/src/addon/messageoutput/airnotifier/providers/airnotifier.ts index 7abef2901..f8ca5be71 100644 --- a/src/addon/messageoutput/airnotifier/providers/airnotifier.ts +++ b/src/addon/messageoutput/airnotifier/providers/airnotifier.ts @@ -70,7 +70,7 @@ export class AddonMessageOutputAirnotifierProvider { /** * Get user devices. * - * @param {string} [siteid] Site ID. If not defined, use current site. + * @param {string} [siteId] Site ID. If not defined, use current site. * @return {Promise} Promise resolved with the devices. */ getUserDevices(siteId?: string): Promise { diff --git a/src/addon/messages/providers/mainmenu-handler.ts b/src/addon/messages/providers/mainmenu-handler.ts index a30ac88a4..f934224be 100644 --- a/src/addon/messages/providers/mainmenu-handler.ts +++ b/src/addon/messages/providers/mainmenu-handler.ts @@ -24,6 +24,7 @@ import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreLocalNotificationsProvider } from '@providers/local-notifications'; import { AddonPushNotificationsProvider } from '@addon/pushnotifications/providers/pushnotifications'; import { AddonPushNotificationsDelegate } from '@addon/pushnotifications/providers/delegate'; +import { CoreEmulatorHelperProvider } from '@core/emulator/providers/helper'; /** * Handler to inject an option into main menu. @@ -46,7 +47,7 @@ export class AddonMessagesMainMenuHandler implements CoreMainMenuHandler, CoreCr private eventsProvider: CoreEventsProvider, private appProvider: CoreAppProvider, private localNotificationsProvider: CoreLocalNotificationsProvider, private textUtils: CoreTextUtilsProvider, private pushNotificationsProvider: AddonPushNotificationsProvider, utils: CoreUtilsProvider, - pushNotificationsDelegate: AddonPushNotificationsDelegate) { + pushNotificationsDelegate: AddonPushNotificationsDelegate, private emulatorHelper: CoreEmulatorHelperProvider) { eventsProvider.on(AddonMessagesProvider.READ_CHANGED_EVENT, (data) => { this.updateBadge(data.siteId); @@ -132,9 +133,9 @@ export class AddonMessagesMainMenuHandler implements CoreMainMenuHandler, CoreCr } if (this.appProvider.isDesktop() && this.localNotificationsProvider.isAvailable()) { - // @todo - /*$mmEmulatorHelper.checkNewNotifications( - AddonMessagesProvider.PUSH_SIMULATION_COMPONENT, this.fetchMessages, this.getTitleAndText, siteId);*/ + this.emulatorHelper.checkNewNotifications( + AddonMessagesProvider.PUSH_SIMULATION_COMPONENT, + this.fetchMessages.bind(this), this.getTitleAndText.bind(this), siteId); } return Promise.resolve(); diff --git a/src/addon/messages/providers/messages.ts b/src/addon/messages/providers/messages.ts index da898e64b..19ece296b 100644 --- a/src/addon/messages/providers/messages.ts +++ b/src/addon/messages/providers/messages.ts @@ -20,6 +20,7 @@ import { CoreUserProvider } from '@core/user/providers/user'; import { AddonMessagesOfflineProvider } from './messages-offline'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreTimeUtilsProvider } from '@providers/utils/time'; +import { CoreEmulatorHelperProvider } from '@core/emulator/providers/helper'; /** * Service to handle messages. @@ -40,7 +41,8 @@ export class AddonMessagesProvider { constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private appProvider: CoreAppProvider, private userProvider: CoreUserProvider, private messagesOffline: AddonMessagesOfflineProvider, - private utils: CoreUtilsProvider, private timeUtils: CoreTimeUtilsProvider) { + private utils: CoreUtilsProvider, private timeUtils: CoreTimeUtilsProvider, + private emulatorHelper: CoreEmulatorHelperProvider) { this.logger = logger.getInstance('AddonMessagesProvider'); } @@ -1088,7 +1090,6 @@ export class AddonMessagesProvider { /** * Store the last received message if it's newer than the last stored. - * @todo * * @param {number} userIdFrom ID of the useridfrom retrieved, 0 for all users. * @param {any} message Last message received. @@ -1096,10 +1097,10 @@ export class AddonMessagesProvider { * @return {Promise} Promise resolved when done. */ protected storeLastReceivedMessageIfNeeded(userIdFrom: number, message: any, siteId?: string): Promise { - /*let component = mmaMessagesPushSimulationComponent; + const component = AddonMessagesProvider.PUSH_SIMULATION_COMPONENT; // Get the last received message. - return $mmEmulatorHelper.getLastReceivedNotification(component, siteId).then((lastMessage) => { + return this.emulatorHelper.getLastReceivedNotification(component, siteId).then((lastMessage) => { if (userIdFrom > 0 && (!message || !lastMessage)) { // Seeing a single discussion. No received message or cannot know if it really is the last received message. Stop. return; @@ -1110,9 +1111,8 @@ export class AddonMessagesProvider { return; } - return $mmEmulatorHelper.storeLastReceivedNotification(component, message, siteId); - });*/ - return Promise.resolve(); + return this.emulatorHelper.storeLastReceivedNotification(component, message, siteId); + }); } /** diff --git a/src/addon/notifications/components/components.module.ts b/src/addon/notifications/components/components.module.ts index 60b77fa20..a1eaa049a 100644 --- a/src/addon/notifications/components/components.module.ts +++ b/src/addon/notifications/components/components.module.ts @@ -32,8 +32,5 @@ import { AddonNotificationsActionsComponent } from './actions/actions'; exports: [ AddonNotificationsActionsComponent ], - entryComponents: [ - AddonNotificationsActionsComponent - ] }) export class AddonNotificationsComponentsModule {} diff --git a/src/addon/notifications/pages/list/list.html b/src/addon/notifications/pages/list/list.html index 6d97d2800..9d53f2f02 100644 --- a/src/addon/notifications/pages/list/list.html +++ b/src/addon/notifications/pages/list/list.html @@ -14,11 +14,11 @@

{{notification.userfromfullname}}

-
+

{{notification.timecreated | coreDateDayOrTime}}

-

+

diff --git a/src/addon/notifications/pages/list/list.ts b/src/addon/notifications/pages/list/list.ts index aa548af71..8bcf66f5d 100644 --- a/src/addon/notifications/pages/list/list.ts +++ b/src/addon/notifications/pages/list/list.ts @@ -70,8 +70,8 @@ export class AddonNotificationsListPage { /** * Convenience function to get notifications. Gets unread notifications first. * - * @param {boolean} refreh - * @return {Primise} Resolved when done. + * @param {boolean} refreh Whether we're refreshing data. + * @return {Promise} Resolved when done. */ protected fetchNotifications(refresh?: boolean): Promise { if (refresh) { @@ -83,6 +83,9 @@ export class AddonNotificationsListPage { return this.notificationsProvider.getUnreadNotifications(this.unreadCount, limit).then((unread) => { let promise; + + unread.forEach(this.formatText.bind(this)); + /* Don't add the unread notifications to this.notifications yet. If there are no unread notifications that causes that the "There are no notifications" message is shown in pull to refresh. */ this.unreadCount += unread.length; @@ -91,6 +94,7 @@ export class AddonNotificationsListPage { // Limit not reached. Get read notifications until reach the limit. const readLimit = limit - unread.length; promise = this.notificationsProvider.getReadNotifications(this.readCount, readLimit).then((read) => { + read.forEach(this.formatText.bind(this)); this.readCount += read.length; if (refresh) { this.notifications = unread.concat(read); @@ -127,7 +131,7 @@ export class AddonNotificationsListPage { /** * Mark notifications as read. * - * @param {any[]} notifications + * @param {any[]} notifications Array of notification objects. */ protected markNotificationsAsRead(notifications: any[]): void { if (notifications.length > 0) { @@ -172,14 +176,12 @@ export class AddonNotificationsListPage { /** * Formats the text of a notification. - * @param {string} text - * @return {string} Formatted text. + * + * @param {any} notification The notification object. */ - formatText(text: string): string { - text = text.replace(/-{4,}/ig, ''); - text = this.textUtils.replaceNewLines(text, '
'); - - return text; + protected formatText(notification: any): void { + const text = notification.mobiletext.replace(/-{4,}/ig, ''); + notification.mobiletext = this.textUtils.replaceNewLines(text, '
'); } /** diff --git a/src/addon/notifications/pages/settings/settings.scss b/src/addon/notifications/pages/settings/settings.scss new file mode 100644 index 000000000..354425bb6 --- /dev/null +++ b/src/addon/notifications/pages/settings/settings.scss @@ -0,0 +1,10 @@ +page-addon-notifications-settings { + .list-header { + margin-bottom: 0; + border-top: 0; + } + + .toggle { + display: inline-block; + } +} diff --git a/src/addon/notifications/pages/settings/settings.ts b/src/addon/notifications/pages/settings/settings.ts index fff15f6f0..cf1d19ee3 100644 --- a/src/addon/notifications/pages/settings/settings.ts +++ b/src/addon/notifications/pages/settings/settings.ts @@ -115,7 +115,7 @@ export class AddonNotificationsSettingsPage implements OnDestroy { /** * Load a processor. * - * @param {any} processor + * @param {any} processor Processor object. */ protected loadProcessor(processor: any): void { if (!processor) { @@ -242,7 +242,7 @@ export class AddonNotificationsSettingsPage implements OnDestroy { /** * Change the notification sound setting. * - * @param {enabled} enabled + * @param {enabled} enabled True to enable the notification sound, false to disable it. */ changeNotificationSound(enabled: boolean): void { this.configProvider.set(CoreConstants.SETTINGS_NOTIFICATION_SOUND, enabled).finally(() => { diff --git a/src/addon/notifications/providers/cron-handler.ts b/src/addon/notifications/providers/cron-handler.ts index a95cc1e3f..78f317c41 100644 --- a/src/addon/notifications/providers/cron-handler.ts +++ b/src/addon/notifications/providers/cron-handler.ts @@ -18,6 +18,8 @@ import { CoreCronHandler } from '@providers/cron'; import { CoreEventsProvider } from '@providers/events'; import { CoreLocalNotificationsProvider } from '@providers/local-notifications'; import { CoreSitesProvider } from '@providers/sites'; +import { CoreTextUtilsProvider } from '@providers/utils/text'; +import { CoreEmulatorHelperProvider } from '@core/emulator/providers/helper'; import { AddonNotificationsProvider } from './notifications'; /** @@ -29,7 +31,8 @@ export class AddonNotificationsCronHandler implements CoreCronHandler { constructor(private appProvider: CoreAppProvider, private eventsProvider: CoreEventsProvider, private sitesProvider: CoreSitesProvider, private localNotifications: CoreLocalNotificationsProvider, - private notificationsProvider: AddonNotificationsProvider) {} + private notificationsProvider: AddonNotificationsProvider, private textUtils: CoreTextUtilsProvider, + private emulatorHelper: CoreEmulatorHelperProvider) {} /** * Get the time between consecutive executions. @@ -73,12 +76,37 @@ export class AddonNotificationsCronHandler implements CoreCronHandler { } if (this.appProvider.isDesktop() && this.localNotifications.isAvailable()) { - /* @todo - $mmEmulatorHelper.checkNewNotifications( - mmaNotificationsPushSimulationComponent, fetchNotifications, getTitleAndText, siteId); - */ + this.emulatorHelper.checkNewNotifications( + AddonNotificationsProvider.PUSH_SIMULATION_COMPONENT, + this.fetchNotifications.bind(this), this.getTitleAndText.bind(this), siteId); } return Promise.resolve(null); } + + /** + * Get the latest unread notifications from a site. + * + * @param {string} siteId Site ID. + * @return {Promise} Promise resolved with the notifications. + */ + protected fetchNotifications(siteId: string): Promise { + return this.notificationsProvider.getUnreadNotifications(0, undefined, true, false, true, siteId); + } + + /** + * Given a notification, return the title and the text for the notification. + * + * @param {any} notification Notification. + * @return {Promise} Promise resvoled with an object with title and text. + */ + protected getTitleAndText(notification: any): Promise { + const data = { + title: notification.userfromfullname, + text: notification.mobiletext.replace(/-{4,}/ig, '') + }; + data.text = this.textUtils.replaceNewLines(data.text, '
'); + + return Promise.resolve(data); + } } diff --git a/src/addon/notifications/providers/mainmenu-handler.ts b/src/addon/notifications/providers/mainmenu-handler.ts index 2cc26a68c..8866d14a8 100644 --- a/src/addon/notifications/providers/mainmenu-handler.ts +++ b/src/addon/notifications/providers/mainmenu-handler.ts @@ -95,7 +95,7 @@ export class AddonNotificationsMainMenuHandler implements CoreMainMenuHandler { /** * Triggers an update for the badge number and loading status. Mandatory if showBadge is enabled. * - * @param {string} siteId Site ID or current Site if undefined. + * @param {string} [siteId] Site ID or current Site if undefined. */ updateBadge(siteId?: string): void { siteId = siteId || this.sitesProvider.getCurrentSiteId(); diff --git a/src/addon/notifications/providers/notifications.ts b/src/addon/notifications/providers/notifications.ts index 75a3aa823..3fefc469c 100644 --- a/src/addon/notifications/providers/notifications.ts +++ b/src/addon/notifications/providers/notifications.ts @@ -18,6 +18,7 @@ import { CoreLoggerProvider } from '@providers/logger'; import { CoreSitesProvider } from '@providers/sites'; import { CoreTimeUtilsProvider } from '@providers/utils/time'; import { CoreUserProvider } from '@core/user/providers/user'; +import { CoreEmulatorHelperProvider } from '@core/emulator/providers/helper'; /** * Service to handle notifications. @@ -27,14 +28,15 @@ export class AddonNotificationsProvider { static READ_CHANGED_EVENT = 'addon_notifications_read_changed_event'; static READ_CRON_EVENT = 'addon_notifications_read_cron_event'; - static PUSH_SIMULATION_COMPONENT = 'AddonMessagesPushSimulation'; + static PUSH_SIMULATION_COMPONENT = 'AddonNotificationsPushSimulation'; static LIST_LIMIT = 20; protected ROOT_CACHE_KEY = 'mmaNotifications:'; protected logger; constructor(logger: CoreLoggerProvider, private appProvider: CoreAppProvider, private sitesProvider: CoreSitesProvider, - private timeUtils: CoreTimeUtilsProvider, private userProvider: CoreUserProvider) { + private timeUtils: CoreTimeUtilsProvider, private userProvider: CoreUserProvider, + private emulatorHelper: CoreEmulatorHelperProvider) { this.logger = logger.getInstance('AddonNotificationsProvider'); } @@ -75,7 +77,7 @@ export class AddonNotificationsProvider { /** * Get notification preferences. * - * @param {string} [siteid] Site ID. If not defined, use current site. + * @param {string} [siteId] Site ID. If not defined, use current site. * @return {Promise} Promise resolved with the notification preferences. */ getNotificationPreferences(siteId?: string): Promise { @@ -141,11 +143,9 @@ export class AddonNotificationsProvider { const notifications = response.messages; this.formatNotificationsData(notifications); if (this.appProvider.isDesktop() && toDisplay && !read && limitFrom === 0) { - /* @todo // Store the last received notification. Don't block the user for this. - $mmEmulatorHelper.storeLastReceivedNotification( - mmaNotificationsPushSimulationComponent, notifications[0], siteId); - */ + this.emulatorHelper.storeLastReceivedNotification( + AddonNotificationsProvider.PUSH_SIMULATION_COMPONENT, notifications[0], siteId); } return notifications; diff --git a/src/addon/pushnotifications/providers/pushnotifications.ts b/src/addon/pushnotifications/providers/pushnotifications.ts index eec83e6ff..98e1e266e 100644 --- a/src/addon/pushnotifications/providers/pushnotifications.ts +++ b/src/addon/pushnotifications/providers/pushnotifications.ts @@ -25,6 +25,7 @@ import { CoreLocalNotificationsProvider } from '@providers/local-notifications'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreConfigProvider } from '@providers/config'; +import { CoreConstants } from '@core/constants'; import { CoreConfigConstants } from '../../../configconstants'; /** @@ -89,8 +90,7 @@ export class AddonPushNotificationsProvider { * @return {Promise} Promise with the push options resolved when done. */ protected getOptions(): Promise { - // @todo: CoreSettingsProvider.NOTIFICATION_SOUND - return this.configProvider.get('CoreSettingsProvider.NOTIFICATION_SOUND', true).then((soundEnabled) => { + return this.configProvider.get(CoreConstants.SETTINGS_NOTIFICATION_SOUND, true).then((soundEnabled) => { return { android: { senderID: CoreConfigConstants.gcmpn, diff --git a/src/core/emulator/emulator.module.ts b/src/core/emulator/emulator.module.ts index 8229d4bec..9afd878fc 100644 --- a/src/core/emulator/emulator.module.ts +++ b/src/core/emulator/emulator.module.ts @@ -35,6 +35,7 @@ import { SQLite } from '@ionic-native/sqlite'; import { Zip } from '@ionic-native/zip'; // Services that Mock Ionic Native in browser an desktop. +import { BadgeMock } from './providers/badge'; import { CameraMock } from './providers/camera'; import { ClipboardMock } from './providers/clipboard'; import { FileMock } from './providers/file'; @@ -44,6 +45,7 @@ import { InAppBrowserMock } from './providers/inappbrowser'; import { LocalNotificationsMock } from './providers/local-notifications'; import { MediaCaptureMock } from './providers/media-capture'; import { NetworkMock } from './providers/network'; +import { PushMock } from './providers/push'; import { ZipMock } from './providers/zip'; import { CoreEmulatorHelperProvider } from './providers/helper'; @@ -89,7 +91,14 @@ export const IONIC_NATIVE_PROVIDERS = [ imports: [ ], providers: [ - Badge, // @todo: Mock + { + provide: Badge, + deps: [CoreAppProvider], + useFactory: (appProvider: CoreAppProvider): Badge => { + // Use platform instead of CoreAppProvider to prevent circular dependencies. + return appProvider.isMobile() ? new Badge() : new BadgeMock(appProvider); + } + }, CoreEmulatorHelperProvider, CoreEmulatorCaptureHelperProvider, { @@ -162,7 +171,14 @@ export const IONIC_NATIVE_PROVIDERS = [ return platform.is('cordova') ? new Network() : new NetworkMock(); } }, - Push, // @todo: Mock + { + provide: Push, + deps: [CoreAppProvider], + useFactory: (appProvider: CoreAppProvider): Push => { + // Use platform instead of CoreAppProvider to prevent circular dependencies. + return appProvider.isMobile() ? new Push() : new PushMock(appProvider); + } + }, SplashScreen, StatusBar, SQLite, diff --git a/src/core/emulator/providers/badge.ts b/src/core/emulator/providers/badge.ts new file mode 100644 index 000000000..713ef67a1 --- /dev/null +++ b/src/core/emulator/providers/badge.ts @@ -0,0 +1,123 @@ +// (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 { Badge } from '@ionic-native/badge'; +import { CoreAppProvider } from '@providers/app'; + +/** + * Emulates the Cordova Push plugin in desktop apps and in browser. + */ +@Injectable() +export class BadgeMock implements Badge { + + constructor(private appProvider: CoreAppProvider) {} + + /** + * Clear the badge of the app icon. + * + * @returns {Promise} + */ + clear(): Promise { + return Promise.reject('clear is only supported in mobile devices'); + } + + /** + * Set the badge of the app icon. + * @param {number} badgeNumber The new badge number. + * @returns {Promise} + */ + set(badgeNumber: number): Promise { + if (!this.appProvider.isDesktop()) { + return Promise.reject('set is not supported in browser'); + } + + try { + const app = require('electron').remote.app; + if (app.setBadgeCount(badgeNumber)) { + return Promise.resolve(); + } else { + return Promise.reject(null); + } + } catch (ex) { + return Promise.reject(ex); + } + } + + /** + * Get the badge of the app icon. + * + * @returns {Promise} + */ + get(): Promise { + if (!this.appProvider.isDesktop()) { + return Promise.reject('get is not supported in browser'); + } + + try { + const app = require('electron').remote.app; + + return Promise.resolve(app.getBadgeCount()); + } catch (ex) { + return Promise.reject(ex); + } + } + + /** + * Increase the badge number. + * + * @param {number} increaseBy Count to add to the current badge number + * @returns {Promise} + */ + increase(increaseBy: number): Promise { + return Promise.reject('increase is only supported in mobile devices'); + } + + /** + * Decrease the badge number. + * + * @param {number} decreaseBy Count to subtract from the current badge number + * @returns {Promise} + */ + decrease(decreaseBy: number): Promise { + return Promise.reject('decrease is only supported in mobile devices'); + } + + /** + * Check support to show badges. + * + * @returns {Promise} + */ + isSupported(): Promise { + return Promise.reject('isSupported is only supported in mobile devices'); + } + + /** + * Determine if the app has permission to show badges. + * + * @returns {Promise} + */ + hasPermission(): Promise { + return Promise.reject('hasPermission is only supported in mobile devices'); + } + + /** + * Register permission to set badge notifications + * + * @returns {Promise} + */ + requestPermission(): Promise { + return Promise.reject('requestPermission is only supported in mobile devices'); + } +} diff --git a/src/core/emulator/providers/helper.ts b/src/core/emulator/providers/helper.ts index 7314ded11..53317da62 100644 --- a/src/core/emulator/providers/helper.ts +++ b/src/core/emulator/providers/helper.ts @@ -17,9 +17,15 @@ 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 { CoreAppProvider } from '@providers/app'; import { CoreInitDelegate, CoreInitHandler } from '@providers/init'; +import { CoreLoggerProvider } from '@providers/logger'; +import { CoreSitesProvider } from '@providers/sites'; +import { CoreLocalNotificationsProvider } from '@providers/local-notifications'; +import { CoreTimeUtilsProvider } from '@providers/utils/time'; import { FileTransferErrorMock } from './file-transfer'; import { CoreEmulatorCaptureHelperProvider } from './capture-helper'; +import { CoreConstants } from '../../constants'; /** * Helper service for the emulator feature. It also acts as an init handler. @@ -30,9 +36,38 @@ export class CoreEmulatorHelperProvider implements CoreInitHandler { priority = CoreInitDelegate.MAX_RECOMMENDED_PRIORITY + 500; blocking = true; + protected logger; + + // Variables for database. + protected LAST_RECEIVED_NOTIFICATION_TABLE = 'core_emulator_last_received_notification'; + protected tablesSchema = [ + { + name: this.LAST_RECEIVED_NOTIFICATION_TABLE, + columns: [ + { + name: 'component', + type: 'TEXT' + }, + { + name: 'id', + type: 'INTEGER', + }, + { + name: 'timecreated', + type: 'INTEGER', + }, + ], + primaryKeys: ['component'] + } + ]; + constructor(private file: File, private fileProvider: CoreFileProvider, private utils: CoreUtilsProvider, - initDelegate: CoreInitDelegate, private localNotif: LocalNotifications, - private captureHelper: CoreEmulatorCaptureHelperProvider) { } + logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private localNotif: LocalNotifications, + private captureHelper: CoreEmulatorCaptureHelperProvider, private timeUtils: CoreTimeUtilsProvider, + private appProvider: CoreAppProvider, private localNotifProvider: CoreLocalNotificationsProvider) { + this.logger = logger.getInstance('CoreEmulatorHelper'); + sitesProvider.createTablesFromSchema(this.tablesSchema); + } /** * Load the Mocks that need it. @@ -52,4 +87,130 @@ export class CoreEmulatorHelperProvider implements CoreInitHandler { return this.utils.allPromises(promises); } + + /** + * Check if there are new notifications, triggering a local notification if found. + * Only for desktop apps since they don't support push notifications. + * + * @param {string} component Component to check. + * @param {Function} fetchFn Function that receives a site ID and returns a Promise resolved with an array of notifications. + * @param {Function} getDataFn Function that receives a notification and returns a promise resolved with the title and text. + * @param {string} [siteId] Site ID to check. If not defined, check all sites. + * @return {Promise} Promise resolved when done. + */ + checkNewNotifications(component: string, fetchFn: Function, getDataFn: Function, siteId?: string): Promise { + if (!this.appProvider.isDesktop() || !this.localNotifProvider.isAvailable()) { + return Promise.resolve(null); + } + + if (!this.appProvider.isOnline()) { + this.logger.debug('Cannot check push notifications because device is offline.'); + + return Promise.reject(null); + } + + let promise: Promise; + if (!siteId) { + // No site ID defined, check all sites. + promise = this.sitesProvider.getSitesIds(); + } else { + promise = Promise.resolve([siteId]); + } + + return promise.then((siteIds) => { + const sitePromises = siteIds.map((siteId) => { + // Check new notifications for each site. + return this.checkNewNotificationsForSite(component, fetchFn, getDataFn, siteId); + }); + + return Promise.all(sitePromises); + }); + } + + /** + * Check if there are new notifications for a certain site, triggering a local notification if found. + * + * @param {string} component Component to check. + * @param {Function} fetchFn Function that receives a site ID and returns a Promise resolved with an array of notifications. + * @param {Function} getDataFn Function that receives a notification and returns a promise resolved with the title and text. + * @param {string} siteId Site ID to check. + * @return {Promise} Promise resolved when done. + */ + protected checkNewNotificationsForSite(component: string, fetchFn: Function, getDataFn: Function, siteId: string) + : Promise { + // Get the last received notification in the app. + return this.getLastReceivedNotification(component, siteId).then((lastNotification) => { + // Now fetch the latest notifications from the server. + return fetchFn(siteId).then((notifications) => { + if (!lastNotification || !notifications.length) { + // No last notification stored (first call) or no new notifications. Stop. + return; + } + + const notification = notifications[0]; + + if (notification.id == lastNotification.id || notification.timecreated <= lastNotification.timecreated || + this.timeUtils.timestamp() - notification.timecreated > CoreConstants.SECONDS_DAY) { + // There are no new notifications or the newest one happened more than a day ago, stop. + return; + } + + // There is a new notification, show it. + return getDataFn(notification).then((titleAndText) => { + const localNotif = { + id: 1, + at: new Date(), + title: titleAndText.title, + text: titleAndText.text, + data: { + notif: notification, + site: siteId + } + }; + + return this.localNotifProvider.schedule(localNotif, component, siteId); + }); + }); + }); + } + + /** + * Get the last notification received in a certain site for a certain component. + * + * @param {string} component Component of the notification to get. + * @param {string} siteId Site ID of the notification. + * @return {Promise} Promise resolved with the notification or false if not found. + */ + getLastReceivedNotification(component: string, siteId: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + return site.getDb().getRecord(this.LAST_RECEIVED_NOTIFICATION_TABLE, {component: component}); + }).catch(() => { + return false; + }); + } + + /** + * Store the last notification received in a certain site. + * + * @param {string} component Component of the notification to store. + * @param {any} notification Notification to store. + * @param {string} siteId Site ID of the notification. + * @return {Promise} Promise resolved when done. + */ + storeLastReceivedNotification(component: string, notification: any, siteId: string): Promise { + if (!notification) { + // No notification, store a fake one. + notification = {id: -1, timecreated: 0}; + } + + return this.sitesProvider.getSite(siteId).then((site) => { + const entry = { + component: component, + id: notification.id, + timecreated: notification.timecreated, + }; + + return site.getDb().insertRecord(this.LAST_RECEIVED_NOTIFICATION_TABLE, entry); + }); + } } diff --git a/src/core/emulator/providers/push.ts b/src/core/emulator/providers/push.ts new file mode 100644 index 000000000..e801b01bf --- /dev/null +++ b/src/core/emulator/providers/push.ts @@ -0,0 +1,184 @@ +// (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 { Observable } from 'rxjs/Observable'; +import { Channel, EventResponse, Push, PushEvent, PushObject, PushOptions } from '@ionic-native/push'; +import { CoreAppProvider } from '@providers/app'; + +/** + * Emulates the Cordova Push plugin in desktop apps and in browser. + */ +@Injectable() +export class PushMock implements Push { + + constructor(private appProvider: CoreAppProvider) { + } + + /** + * Init push notifications + * + * @param {PushOptions} options + * @return {PushObject} + */ + init(options: PushOptions): PushObject { + return new PushObjectMock(this.appProvider); + } + + /** + * Check whether the push notification permission has been granted. + * + * @return {Promise<{isEnabled: boolean}>} Returns a Promise that resolves with an object with one property: isEnabled, a + * boolean that indicates if permission has been granted. + */ + hasPermission(): Promise<{isEnabled: boolean}> { + return Promise.reject('hasPermission is only supported in mobile devices'); + } + + /** + * Create a new notification channel for Android O and above. + * + * @param {Channel} channel + */ + createChannel(channel?: Channel): Promise { + return Promise.reject('createChannel is only supported in mobile devices'); + } + + /** + * Delete a notification channel for Android O and above. + * + * @param {string} id + */ + deleteChannel(id?: string): Promise { + return Promise.reject('deleteChannel is only supported in mobile devices'); + } + + /** + * Returns a list of currently configured channels. + * + * @return {Promise} + */ + listChannels(): Promise { + return Promise.reject('listChannels is only supported in mobile devices'); + } +} + +/** + * Emulates the PushObject class in desktop apps and in browser. + */ +export class PushObjectMock extends PushObject { + + constructor(private appProvider: CoreAppProvider) { + super({}); + } + + /** + * Adds an event listener + * @param event {string} + * @return {Observable} + */ + on(event: PushEvent): Observable { + return Observable.empty(); + } + + /** + * The unregister method is used when the application no longer wants to receive push notifications. + * Beware that this cleans up all event handlers previously registered, + * so you will need to re-register them if you want them to function again without an application reload. + */ + unregister(): Promise { + return Promise.reject('unregister is only supported in mobile devices'); + } + + /** + * Set the badge count visible when the app is not running + * + * The count is an integer indicating what number should show up in the badge. + * Passing 0 will clear the badge. + * Each notification event contains a data.count value which can be used to set the badge to correct number. + * + * @param count + */ + setApplicationIconBadgeNumber(count?: number): Promise { + if (!this.appProvider.isDesktop()) { + return Promise.reject('setApplicationIconBadgeNumber is not supported in browser'); + } + + try { + const app = require('electron').remote.app; + if (app.setBadgeCount(count)) { + return Promise.resolve(); + } else { + return Promise.reject(null); + } + } catch (ex) { + return Promise.reject(ex); + } + } + + /** + * Get the current badge count visible when the app is not running + * successHandler gets called with an integer which is the current badge count + */ + getApplicationIconBadgeNumber(): Promise { + if (!this.appProvider.isDesktop()) { + return Promise.reject('getApplicationIconBadgeNumber is not supported in browser'); + } + + try { + const app = require('electron').remote.app; + + return Promise.resolve(app.getBadgeCount()); + } catch (ex) { + return Promise.reject(ex); + } + } + + /** + * iOS only + * Tells the OS that you are done processing a background push notification. + * successHandler gets called when background push processing is successfully completed. + * @param [id] + */ + finish(id?: string): Promise { + return Promise.reject('finish is only supported in mobile devices'); + } + + /** + * Tells the OS to clear all notifications from the Notification Center + */ + clearAllNotifications(): Promise { + return Promise.reject('clearAllNotifications is only supported in mobile devices'); + } + + /** + * The subscribe method is used when the application wants to subscribe a new topic to receive push notifications. + * @param topic {string} Topic to subscribe to. + * @return {Promise} + */ + subscribe(topic: string): Promise { + return Promise.reject('subscribe is only supported in mobile devices'); + } + + /** + * The unsubscribe method is used when the application no longer wants to receive push notifications from a specific topic but + * continue to receive other push messages. + * + * @param topic {string} Topic to unsubscribe from. + * @return {Promise} + */ + unsubscribe(topic: string): Promise { + return Promise.reject('unsubscribe is only supported in mobile devices'); + } +}