diff --git a/src/addon/messageoutput/airnotifier/airnotifier.module.ts b/src/addon/messageoutput/airnotifier/airnotifier.module.ts
new file mode 100644
index 000000000..1a060c0a4
--- /dev/null
+++ b/src/addon/messageoutput/airnotifier/airnotifier.module.ts
@@ -0,0 +1,34 @@
+// (C) Copyright 2015 Martin Dougiamas
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import { NgModule } from '@angular/core';
+import { AddonMessageOutputDelegate } from '@addon/messageoutput/providers/delegate';
+import { AddonMessageOutputAirnotifierProvider } from './providers/airnotifier';
+import { AddonMessageOutputAirnotifierHandler } from './providers/handler';
+
+@NgModule({
+ declarations: [
+ ],
+ imports: [
+ ],
+ providers: [
+ AddonMessageOutputAirnotifierProvider,
+ AddonMessageOutputAirnotifierHandler,
+ ]
+})
+export class AddonMessageOutputAirnotifierModule {
+ constructor(messageOutputDelegate: AddonMessageOutputDelegate, airnotifierHandler: AddonMessageOutputAirnotifierHandler) {
+ messageOutputDelegate.registerHandler(airnotifierHandler);
+ }
+}
diff --git a/src/addon/messageoutput/airnotifier/lang/en.json b/src/addon/messageoutput/airnotifier/lang/en.json
new file mode 100644
index 000000000..a6f460bbb
--- /dev/null
+++ b/src/addon/messageoutput/airnotifier/lang/en.json
@@ -0,0 +1,3 @@
+{
+ "processorsettingsdesc": "Configure devices"
+}
\ No newline at end of file
diff --git a/src/addon/messageoutput/airnotifier/pages/devices/devices.html b/src/addon/messageoutput/airnotifier/pages/devices/devices.html
new file mode 100644
index 000000000..16a4876cd
--- /dev/null
+++ b/src/addon/messageoutput/airnotifier/pages/devices/devices.html
@@ -0,0 +1,22 @@
+
+
+ {{ 'addon.messageoutput_airnotifier.processorsettingsdesc' | translate }}
+
+
+
+
+
+
+
+
+
+
+ {{ device.model }}
+ ({{ 'core.currentdevice' | translate }})
+
+
+
+
+
+
+
diff --git a/src/addon/messageoutput/airnotifier/pages/devices/devices.module.ts b/src/addon/messageoutput/airnotifier/pages/devices/devices.module.ts
new file mode 100644
index 000000000..8f16f9e4c
--- /dev/null
+++ b/src/addon/messageoutput/airnotifier/pages/devices/devices.module.ts
@@ -0,0 +1,31 @@
+// (C) Copyright 2015 Martin Dougiamas
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import { NgModule } from '@angular/core';
+import { IonicPageModule } from 'ionic-angular';
+import { TranslateModule } from '@ngx-translate/core';
+import { CoreComponentsModule } from '@components/components.module';
+import { AddonMessageOutputAirnotifierDevicesPage } from './devices';
+
+@NgModule({
+ declarations: [
+ AddonMessageOutputAirnotifierDevicesPage,
+ ],
+ imports: [
+ CoreComponentsModule,
+ IonicPageModule.forChild(AddonMessageOutputAirnotifierDevicesPage),
+ TranslateModule.forChild()
+ ],
+})
+export class AddonMessageOutputAirnotifierDevicesPageModule {}
diff --git a/src/addon/messageoutput/airnotifier/pages/devices/devices.ts b/src/addon/messageoutput/airnotifier/pages/devices/devices.ts
new file mode 100644
index 000000000..0bea819b4
--- /dev/null
+++ b/src/addon/messageoutput/airnotifier/pages/devices/devices.ts
@@ -0,0 +1,137 @@
+// (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 { Component, OnDestroy } from '@angular/core';
+import { IonicPage } from 'ionic-angular';
+import { CoreDomUtilsProvider } from '@providers/utils/dom';
+import { AddonPushNotificationsProvider } from '@addon/pushnotifications/providers/pushnotifications';
+import { AddonMessageOutputAirnotifierProvider } from '../../providers/airnotifier';
+
+/**
+ * Page that displays the list of devices.
+ */
+@IonicPage({ segment: 'addon-message-output-airnotifier-devices' })
+@Component({
+ selector: 'page-addon-message-output-airnotifier-devices',
+ templateUrl: 'devices.html',
+})
+export class AddonMessageOutputAirnotifierDevicesPage implements OnDestroy {
+
+ devices = [];
+ devicesLoaded = false;
+
+ protected updateTimeout: any;
+
+ constructor(private domUtils: CoreDomUtilsProvider, private airnotifierProivder: AddonMessageOutputAirnotifierProvider,
+ private pushNotificationsProvider: AddonPushNotificationsProvider ) {
+ }
+
+ /**
+ * View loaded.
+ */
+ ionViewDidLoad(): void {
+ this.fetchDevices();
+ }
+
+ /**
+ * Fetches the list of devices.
+ *
+ * @return {Promise} Promise resolved when done.
+ */
+ protected fetchDevices(): Promise {
+ return this.airnotifierProivder.getUserDevices().then((devices) => {
+ const pushId = this.pushNotificationsProvider.getPushId();
+
+ // Convert enabled to boolean and search current device.
+ devices.forEach((device) => {
+ device.enable = !!device.enable;
+ device.current = pushId && pushId == device.pushid;
+ });
+
+ this.devices = devices;
+ }).catch((message) => {
+ this.domUtils.showErrorModal(message);
+ }).finally(() => {
+ this.devicesLoaded = true;
+ });
+ }
+
+ /**
+ * Update list of devices after a certain time. The purpose is to store the updated data, it won't be reflected in the view.
+ */
+ protected updateDevicesAfterDelay(): void {
+ // Cancel pending updates.
+ if (this.updateTimeout) {
+ clearTimeout(this.updateTimeout);
+ }
+
+ this.updateTimeout = setTimeout(() => {
+ this.updateTimeout = null;
+ this.updateDevices();
+ }, 5000);
+ }
+
+ /**
+ * Fetch devices. The purpose is to store the updated data, it won't be reflected in the view.
+ */
+ protected updateDevices(): void {
+ this.airnotifierProivder.invalidateUserDevices().finally(() => {
+ this.airnotifierProivder.getUserDevices();
+ });
+ }
+
+ /**
+ * Refresh the list of devices.
+ *
+ * @param {any} refresher Refresher.
+ */
+ refreshDevices(refresher: any): void {
+ this.airnotifierProivder.invalidateUserDevices().finally(() => {
+ this.fetchDevices().finally(() => {
+ refresher.complete();
+ });
+ });
+ }
+
+ /**
+ * Enable or disable a certain device.
+ *
+ * @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;
+ this.airnotifierProivder.enableDevice(device.id, enable).then(() => {
+ // Update the list of devices since it was modified.
+ this.updateDevicesAfterDelay();
+ }).catch((message) => {
+ // Show error and revert change.
+ this.domUtils.showErrorModal(message);
+ device.enable = !device.enable;
+ }).finally(() => {
+ device.updating = false;
+ });
+ }
+
+ /**
+ * Page destroyed.
+ */
+ ngOnDestroy(): void {
+ // If there is a pending action to update devices, execute it right now.
+ if (this.updateTimeout) {
+ clearTimeout(this.updateTimeout);
+ this.updateDevices();
+ }
+ }
+}
diff --git a/src/addon/messageoutput/airnotifier/providers/airnotifier.ts b/src/addon/messageoutput/airnotifier/providers/airnotifier.ts
new file mode 100644
index 000000000..f8ca5be71
--- /dev/null
+++ b/src/addon/messageoutput/airnotifier/providers/airnotifier.ts
@@ -0,0 +1,115 @@
+// (C) Copyright 2015 Martin Dougiamas
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import { Injectable } from '@angular/core';
+import { CoreLoggerProvider } from '@providers/logger';
+import { CoreSitesProvider } from '@providers/sites';
+import { CoreConfigConstants } from '../../../../configconstants';
+
+/**
+ * Service to handle Airnotifier message output.
+ */
+@Injectable()
+export class AddonMessageOutputAirnotifierProvider {
+
+ protected ROOT_CACHE_KEY = 'mmaMessageOutputAirnotifier:';
+ protected logger: any;
+
+ constructor(loggerProvider: CoreLoggerProvider, private sitesProvider: CoreSitesProvider) {
+ this.logger = loggerProvider.getInstance('AddonMessageOutputAirnotifier');
+ }
+
+ /**
+ * Enables or disables a device.
+ *
+ * @param {number} deviceId Device ID.
+ * @param {boolean} enable True to enable, false to disable.
+ * @param {string} [siteId] Site ID. If not defined, current site.
+ * @return {Promise} Promise resolved if success.
+ */
+ enableDevice(deviceId: number, enable: boolean, siteId?: string): Promise {
+ return this.sitesProvider.getSite(siteId).then((site) => {
+ const data = {
+ deviceid: deviceId,
+ enable: enable ? 1 : 0
+ };
+
+ return site.write('message_airnotifier_enable_device', data).then((result) => {
+ if (!result.success) {
+ // Fail. Reject with warning message if any.
+ if (result.warnings && result.warnings.length) {
+ return Promise.reject(result.warnings[0].message);
+ }
+
+ return Promise.reject(null);
+ }
+ });
+ });
+ }
+
+ /**
+ * Get the cache key for the get user devices call.
+ *
+ * @return {string} Cache key.
+ */
+ protected getUserDevicesCacheKey(): string {
+ return this.ROOT_CACHE_KEY + 'userDevices';
+ }
+
+ /**
+ * Get user devices.
+ *
+ * @param {string} [siteId] Site ID. If not defined, use current site.
+ * @return {Promise} Promise resolved with the devices.
+ */
+ getUserDevices(siteId?: string): Promise {
+ this.logger.debug('Get user devices');
+
+ return this.sitesProvider.getSite(siteId).then((site) => {
+ const data = {
+ appid: CoreConfigConstants.app_id
+ };
+ const preSets = {
+ cacheKey: this.getUserDevicesCacheKey()
+ };
+
+ return site.read('message_airnotifier_get_user_devices', data, preSets).then((data) => {
+ return data.devices;
+ });
+ });
+ }
+
+ /**
+ * Invalidate get user devices.
+ *
+ * @param {string} [siteId] Site ID. If not defined, current site.
+ * @return {Promise} Promise resolved when data is invalidated.
+ */
+ invalidateUserDevices(siteId?: string): Promise {
+ return this.sitesProvider.getSite(siteId).then((site) => {
+ return site.invalidateWsCacheForKey(this.getUserDevicesCacheKey());
+ });
+ }
+
+ /**
+ * Returns whether or not the plugin is enabled for the current site.
+ *
+ * @return {boolean} True if enabled, false otherwise.
+ * @since 3.2
+ */
+ isEnabled(): boolean {
+ return this.sitesProvider.wsAvailableInCurrentSite('message_airnotifier_enable_device') &&
+ this.sitesProvider.wsAvailableInCurrentSite('message_airnotifier_get_user_devices');
+ }
+}
diff --git a/src/addon/messageoutput/airnotifier/providers/handler.ts b/src/addon/messageoutput/airnotifier/providers/handler.ts
new file mode 100644
index 000000000..8d5cc15ae
--- /dev/null
+++ b/src/addon/messageoutput/airnotifier/providers/handler.ts
@@ -0,0 +1,52 @@
+// (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 { AddonMessageOutputHandler, AddonMessageOutputHandlerData } from '@addon/messageoutput/providers/delegate';
+import { AddonMessageOutputAirnotifierProvider } from './airnotifier';
+
+/**
+ * Airnotifier message output handler.
+ */
+@Injectable()
+export class AddonMessageOutputAirnotifierHandler implements AddonMessageOutputHandler {
+ name = 'AddonMessageOutputAirnotifier';
+ processorName = 'airnotifier';
+
+ constructor(private airnotifierProvider: AddonMessageOutputAirnotifierProvider) {}
+
+ /**
+ * Whether or not the module is enabled for the site.
+ *
+ * @return {boolean} True if enabled, false otherwise.
+ */
+ isEnabled(): boolean {
+ return this.airnotifierProvider.isEnabled();
+ }
+
+ /**
+ * Returns the data needed to render the handler.
+ *
+ * @param {any} processor The processor object.
+ * @return {CoreMainMenuHandlerData} Data.
+ */
+ getDisplayData(processor: any): AddonMessageOutputHandlerData {
+ return {
+ priority: 600,
+ label: 'addon.messageoutput_airnotifier.processorsettingsdesc',
+ icon: 'settings',
+ page: 'AddonMessageOutputAirnotifierDevicesPage',
+ };
+ }
+}
diff --git a/src/addon/messageoutput/messageoutput.module.ts b/src/addon/messageoutput/messageoutput.module.ts
new file mode 100644
index 000000000..87552dc1d
--- /dev/null
+++ b/src/addon/messageoutput/messageoutput.module.ts
@@ -0,0 +1,27 @@
+// (C) Copyright 2015 Martin Dougiamas
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import { NgModule } from '@angular/core';
+import { AddonMessageOutputDelegate } from './providers/delegate';
+
+@NgModule({
+ declarations: [
+ ],
+ imports: [
+ ],
+ providers: [
+ AddonMessageOutputDelegate
+ ]
+})
+export class AddonMessageOutputModule {}
diff --git a/src/addon/messageoutput/providers/delegate.ts b/src/addon/messageoutput/providers/delegate.ts
new file mode 100644
index 000000000..cd1f96698
--- /dev/null
+++ b/src/addon/messageoutput/providers/delegate.ts
@@ -0,0 +1,97 @@
+// (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 { CoreDelegate, CoreDelegateHandler } from '@classes/delegate';
+import { CoreEventsProvider } from '@providers/events';
+import { CoreLoggerProvider } from '@providers/logger';
+import { CoreSitesProvider } from '@providers/sites';
+
+/**
+ * Interface that all message output handlers must implement.
+ */
+export interface AddonMessageOutputHandler extends CoreDelegateHandler {
+ /**
+ * The name of the processor. E.g. 'airnotifier'.
+ * @type {string}
+ */
+ processorName: string;
+
+ /**
+ * Returns the data needed to render the handler.
+ *
+ * @param {any} processor The processor object.
+ * @return {CoreMainMenuHandlerData} Data.
+ */
+ getDisplayData(processor: any): AddonMessageOutputHandlerData;
+}
+
+/**
+ * Data needed to render a message output handler. It's returned by the handler.
+ */
+export interface AddonMessageOutputHandlerData {
+ /**
+ * Handler's priority.
+ * @type {number}
+ */
+ priority: number;
+
+ /**
+ * Name of the page to load for the handler.
+ * @type {string}
+ */
+ page: string;
+
+ /**
+ * Label to display for the handler.
+ * @type {string}
+ */
+ label: string;
+
+ /**
+ * Name of the icon to display for the handler.
+ * @type {string}
+ */
+ icon: string;
+
+ /**
+ * Params to pass to the page.
+ * @type {any}
+ */
+ pageParams?: any;
+}
+
+/**
+ * Delegate to register processors (message/output) to be used in places like notification preferences.
+ */
+ @Injectable()
+ export class AddonMessageOutputDelegate extends CoreDelegate {
+
+ protected handlerNameProperty = 'processorName';
+
+ constructor(protected loggerProvider: CoreLoggerProvider, protected sitesProvider: CoreSitesProvider,
+ protected eventsProvider: CoreEventsProvider) {
+ super('CoreSettingsDelegate', loggerProvider, sitesProvider, eventsProvider);
+ }
+
+ /**
+ * Get the display data of the handler.
+ *
+ * @param {string} processor The processor object.
+ * @return {AddonMessageOutputHandlerData} Data.
+ */
+ getDisplayData(processor: any): AddonMessageOutputHandlerData {
+ return this.executeFunctionOnEnabled(processor.name, 'getDisplayData', processor);
+ }
+}
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/actions/actions.html b/src/addon/notifications/components/actions/actions.html
new file mode 100644
index 000000000..7d69e94bd
--- /dev/null
+++ b/src/addon/notifications/components/actions/actions.html
@@ -0,0 +1,8 @@
+ 0">
+
+
+
+
diff --git a/src/addon/notifications/components/actions/actions.ts b/src/addon/notifications/components/actions/actions.ts
new file mode 100644
index 000000000..b72a529c9
--- /dev/null
+++ b/src/addon/notifications/components/actions/actions.ts
@@ -0,0 +1,41 @@
+// (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 { Component, Input, OnInit } from '@angular/core';
+import { CoreContentLinksDelegate, CoreContentLinksAction } from '@core/contentlinks/providers/delegate';
+
+/**
+ * Component that displays the actions for a notification.
+ */
+@Component({
+ selector: 'addon-notifications-actions',
+ templateUrl: 'actions.html',
+})
+export class AddonNotificationsActionsComponent implements OnInit {
+ @Input() contextUrl: string;
+ @Input() courseId: number;
+
+ actions: CoreContentLinksAction[] = [];
+
+ constructor(private contentLinksDelegate: CoreContentLinksDelegate) {}
+
+ /**
+ * Component being initialized.
+ */
+ ngOnInit(): void {
+ this.contentLinksDelegate.getActionsFor(this.contextUrl, this.courseId).then((actions) => {
+ this.actions = actions;
+ });
+ }
+}
diff --git a/src/addon/notifications/components/components.module.ts b/src/addon/notifications/components/components.module.ts
new file mode 100644
index 000000000..a1eaa049a
--- /dev/null
+++ b/src/addon/notifications/components/components.module.ts
@@ -0,0 +1,36 @@
+// (C) Copyright 2015 Martin Dougiamas
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { IonicModule } from 'ionic-angular';
+import { TranslateModule } from '@ngx-translate/core';
+import { AddonNotificationsActionsComponent } from './actions/actions';
+
+@NgModule({
+ declarations: [
+ AddonNotificationsActionsComponent
+ ],
+ imports: [
+ CommonModule,
+ IonicModule,
+ TranslateModule.forChild(),
+ ],
+ providers: [
+ ],
+ exports: [
+ AddonNotificationsActionsComponent
+ ],
+})
+export class AddonNotificationsComponentsModule {}
diff --git a/src/addon/notifications/lang/en.json b/src/addon/notifications/lang/en.json
new file mode 100644
index 000000000..b1582bf1a
--- /dev/null
+++ b/src/addon/notifications/lang/en.json
@@ -0,0 +1,7 @@
+{
+ "errorgetnotifications": "Error getting notifications.",
+ "notificationpreferences": "Notification preferences",
+ "notifications": "Notifications",
+ "playsound": "Play sound",
+ "therearentnotificationsyet": "There are no notifications."
+}
\ No newline at end of file
diff --git a/src/addon/notifications/notifications.module.ts b/src/addon/notifications/notifications.module.ts
new file mode 100644
index 000000000..9591fef71
--- /dev/null
+++ b/src/addon/notifications/notifications.module.ts
@@ -0,0 +1,80 @@
+// (C) Copyright 2015 Martin Dougiamas
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import { NgModule } from '@angular/core';
+import { AddonNotificationsProvider } from './providers/notifications';
+import { AddonNotificationsMainMenuHandler } from './providers/mainmenu-handler';
+import { AddonNotificationsSettingsHandler } from './providers/settings-handler';
+import { AddonNotificationsCronHandler } from './providers/cron-handler';
+import { CoreAppProvider } from '@providers/app';
+import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper';
+import { CoreMainMenuDelegate } from '@core/mainmenu/providers/delegate';
+import { CoreSettingsDelegate } from '@core/settings/providers/delegate';
+import { CoreCronDelegate } from '@providers/cron';
+import { CoreLocalNotificationsProvider } from '@providers/local-notifications';
+import { CoreSitesProvider } from '@providers/sites';
+import { CoreUtilsProvider } from '@providers/utils/utils';
+import { AddonPushNotificationsDelegate } from '@addon/pushnotifications/providers/delegate';
+
+@NgModule({
+ declarations: [
+ ],
+ imports: [
+ ],
+ providers: [
+ AddonNotificationsProvider,
+ AddonNotificationsMainMenuHandler,
+ AddonNotificationsSettingsHandler,
+ AddonNotificationsCronHandler,
+ ]
+})
+export class AddonNotificationsModule {
+ constructor(mainMenuDelegate: CoreMainMenuDelegate, mainMenuHandler: AddonNotificationsMainMenuHandler,
+ settingsDelegate: CoreSettingsDelegate, settingsHandler: AddonNotificationsSettingsHandler,
+ cronDelegate: CoreCronDelegate, cronHandler: AddonNotificationsCronHandler,
+ appProvider: CoreAppProvider, utils: CoreUtilsProvider, sitesProvider: CoreSitesProvider,
+ notificationsProvider: AddonNotificationsProvider, localNotifications: CoreLocalNotificationsProvider,
+ linkHelper: CoreContentLinksHelperProvider, pushNotificationsDelegate: AddonPushNotificationsDelegate) {
+ mainMenuDelegate.registerHandler(mainMenuHandler);
+ settingsDelegate.registerHandler(settingsHandler);
+ cronDelegate.register(cronHandler);
+
+ const notificationClicked = (notification: any): void => {
+ sitesProvider.isFeatureDisabled('CoreMainMenuDelegate_AddonNotifications', notification.site).then((disabled) => {
+ if (disabled) {
+ // Notifications are disabled, stop.
+ return;
+ }
+
+ notificationsProvider.invalidateNotificationsList().finally(() => {
+ linkHelper.goInSite(undefined, 'AddonNotificationsListPage', undefined, notification.site);
+ });
+ });
+ };
+
+ if (appProvider.isDesktop()) {
+ // Listen for clicks in simulated push notifications.
+ localNotifications.registerClick(AddonNotificationsProvider.PUSH_SIMULATION_COMPONENT, notificationClicked);
+ }
+
+ // Register push notification clicks.
+ pushNotificationsDelegate.on('click').subscribe((notification) => {
+ if (utils.isTrueOrOne(notification.notif)) {
+ notificationClicked(notification);
+
+ return true;
+ }
+ });
+ }
+}
diff --git a/src/addon/notifications/pages/list/list.html b/src/addon/notifications/pages/list/list.html
new file mode 100644
index 000000000..9d53f2f02
--- /dev/null
+++ b/src/addon/notifications/pages/list/list.html
@@ -0,0 +1,30 @@
+
+
+ {{ 'addon.notifications.notifications' | translate }}
+
+
+
+
+
+
+
+
+
+
+
+
+
{{notification.userfromfullname}}
+
+
{{notification.timecreated | coreDateDayOrTime}}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/addon/notifications/pages/list/list.module.ts b/src/addon/notifications/pages/list/list.module.ts
new file mode 100644
index 000000000..56369935d
--- /dev/null
+++ b/src/addon/notifications/pages/list/list.module.ts
@@ -0,0 +1,37 @@
+// (C) Copyright 2015 Martin Dougiamas
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import { NgModule } from '@angular/core';
+import { IonicPageModule } from 'ionic-angular';
+import { TranslateModule } from '@ngx-translate/core';
+import { CoreComponentsModule } from '@components/components.module';
+import { CoreDirectivesModule } from '@directives/directives.module';
+import { CorePipesModule } from '@pipes/pipes.module';
+import { AddonNotificationsComponentsModule } from '../../components/components.module';
+import { AddonNotificationsListPage } from './list';
+
+@NgModule({
+ declarations: [
+ AddonNotificationsListPage,
+ ],
+ imports: [
+ CoreComponentsModule,
+ CoreDirectivesModule,
+ CorePipesModule,
+ IonicPageModule.forChild(AddonNotificationsListPage),
+ TranslateModule.forChild(),
+ AddonNotificationsComponentsModule,
+ ],
+})
+export class AddonNotificationsListPageModule {}
diff --git a/src/addon/notifications/pages/list/list.ts b/src/addon/notifications/pages/list/list.ts
new file mode 100644
index 000000000..8bcf66f5d
--- /dev/null
+++ b/src/addon/notifications/pages/list/list.ts
@@ -0,0 +1,194 @@
+// (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 { Component } from '@angular/core';
+import { IonicPage, NavParams } from 'ionic-angular';
+import { Subscription } from 'rxjs';
+import { CoreDomUtilsProvider } from '@providers/utils/dom';
+import { CoreTextUtilsProvider } from '@providers/utils/text';
+import { CoreEventsProvider, CoreEventObserver } from '@providers/events';
+import { CoreSitesProvider } from '@providers/sites';
+import { CoreUtilsProvider } from '@providers/utils/utils';
+import { AddonNotificationsProvider } from '../../providers/notifications';
+import { AddonPushNotificationsDelegate } from '@addon/pushnotifications/providers/delegate';
+
+/**
+ * Page that displays the list of notifications.
+ */
+@IonicPage({ segment: 'addon-notifications-list' })
+@Component({
+ selector: 'page-addon-notifications-list',
+ templateUrl: 'list.html',
+})
+export class AddonNotificationsListPage {
+
+ notifications = [];
+ notificationsLoaded = false;
+ canLoadMore = false;
+
+ protected readCount = 0;
+ protected unreadCount = 0;
+ protected cronObserver: CoreEventObserver;
+ protected pushObserver: Subscription;
+
+ constructor(navParams: NavParams, private domUtils: CoreDomUtilsProvider, private eventsProvider: CoreEventsProvider,
+ private sitesProvider: CoreSitesProvider, private textUtils: CoreTextUtilsProvider,
+ private utils: CoreUtilsProvider, private notificationsProvider: AddonNotificationsProvider,
+ private pushNotificationsDelegate: AddonPushNotificationsDelegate) {
+ }
+
+ /**
+ * View loaded.
+ */
+ ionViewDidLoad(): void {
+ this.fetchNotifications().finally(() => {
+ this.notificationsLoaded = true;
+ });
+
+ this.cronObserver = this.eventsProvider.on(AddonNotificationsProvider.READ_CRON_EVENT, () => this.refreshNotifications(),
+ this.sitesProvider.getCurrentSiteId());
+
+ this.pushObserver = this.pushNotificationsDelegate.on('receive').subscribe((notification) => {
+ // New notification received. If it's from current site, refresh the data.
+ if (this.utils.isTrueOrOne(notification.notif) && this.sitesProvider.isCurrentSite(notification.site)) {
+ this.refreshNotifications();
+ }
+ });
+ }
+
+ /**
+ * Convenience function to get notifications. Gets unread notifications first.
+ *
+ * @param {boolean} refreh Whether we're refreshing data.
+ * @return {Promise} Resolved when done.
+ */
+ protected fetchNotifications(refresh?: boolean): Promise {
+ if (refresh) {
+ this.readCount = 0;
+ this.unreadCount = 0;
+ }
+
+ const limit = AddonNotificationsProvider.LIST_LIMIT;
+
+ 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;
+
+ if (unread.length < limit) {
+ // 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);
+ } else {
+ this.notifications = this.notifications.concat(unread, read);
+ }
+ this.canLoadMore = read.length >= readLimit;
+ }).catch((error) => {
+ if (unread.length == 0) {
+ this.domUtils.showErrorModalDefault(error, 'addon.notifications.errorgetnotifications', true);
+ this.canLoadMore = false; // Set to false to prevent infinite calls with infinite-loading.
+ }
+ });
+ } else {
+ promise = Promise.resolve();
+ if (refresh) {
+ this.notifications = unread;
+ } else {
+ this.notifications = this.notifications.concat(unread);
+ }
+ this.canLoadMore = true;
+ }
+
+ return promise.then(() => {
+ // Mark retrieved notifications as read if they are not.
+ this.markNotificationsAsRead(unread);
+ });
+ }).catch((error) => {
+ this.domUtils.showErrorModalDefault(error, 'addon.notifications.errorgetnotifications', true);
+ this.canLoadMore = false; // Set to false to prevent infinite calls with infinite-loading.
+ });
+ }
+
+ /**
+ * Mark notifications as read.
+ *
+ * @param {any[]} notifications Array of notification objects.
+ */
+ protected markNotificationsAsRead(notifications: any[]): void {
+ if (notifications.length > 0) {
+ const promises = notifications.map((notification) => {
+ return this.notificationsProvider.markNotificationRead(notification.id);
+ });
+
+ Promise.all(promises).finally(() => {
+ this.notificationsProvider.invalidateNotificationsList().finally(() => {
+ const siteId = this.sitesProvider.getCurrentSiteId();
+ this.eventsProvider.trigger(AddonNotificationsProvider.READ_CHANGED_EVENT, null, siteId);
+ });
+ });
+ }
+ }
+
+ /**
+ * Refresh notifications.
+ *
+ * @param {any} [refresher] Refresher.
+ */
+ refreshNotifications(refresher?: any): void {
+ this.notificationsProvider.invalidateNotificationsList().finally(() => {
+ return this.fetchNotifications(true).finally(() => {
+ if (refresher) {
+ refresher.complete();
+ }
+ });
+ });
+ }
+
+ /**
+ * Load more results.
+ *
+ * @param {any} infiniteScroll The infinit scroll instance.
+ */
+ loadMoreNotifications(infiniteScroll: any): void {
+ this.fetchNotifications().finally(() => {
+ infiniteScroll.complete();
+ });
+ }
+
+ /**
+ * Formats the text of a notification.
+ *
+ * @param {any} notification The notification object.
+ */
+ protected formatText(notification: any): void {
+ const text = notification.mobiletext.replace(/-{4,}/ig, '');
+ notification.mobiletext = this.textUtils.replaceNewLines(text, ' ');
+ }
+
+ /**
+ * Page destroyed.
+ */
+ ngOnDestroy(): void {
+ this.cronObserver && this.cronObserver.off();
+ this.pushObserver && this.pushObserver.unsubscribe();
+ }
+}
diff --git a/src/addon/notifications/pages/settings/settings.html b/src/addon/notifications/pages/settings/settings.html
new file mode 100644
index 000000000..9bf7a66f0
--- /dev/null
+++ b/src/addon/notifications/pages/settings/settings.html
@@ -0,0 +1,76 @@
+
+
+ {{ 'addon.notifications.notificationpreferences' | translate }}
+
+
+
+
+
+ 0">
+
+
+
+
+
+
+
+
+
+
+ {{ 'addon.notifications.playsound' | translate }}
+
+
+
+
+
+
+ {{ 'core.settings.disableall' | translate }}
+
+
+
+ {{ 'addon.notifications.playsound' | translate }}
+
+
+
+
+
+ 0" [ngModel]="currentProcessor.name" (ngModelChange)="changeProcessor($event)" interface="popover">
+ {{ processor.displayname }}
+
+
+
+
+
+ {{ component.displayname }}
+ {{ 'core.settings.loggedin' | translate }}
+ {{ 'core.settings.loggedoff' | translate }}
+
+
+
+
+
+ {{ notification.displayname }}
+
+
+
+
+
+
+ {{ 'core.settings.disabled' | translate }}
+
+
+
+ {{ notification.displayname }}
+
+
+ {{ 'core.settings.' + state | translate }}
+
+
+
+ {{ 'core.settings.disabled' | translate }}
+
+
+
+
+
+
diff --git a/src/addon/notifications/pages/settings/settings.module.ts b/src/addon/notifications/pages/settings/settings.module.ts
new file mode 100644
index 000000000..2b6e9e500
--- /dev/null
+++ b/src/addon/notifications/pages/settings/settings.module.ts
@@ -0,0 +1,33 @@
+// (C) Copyright 2015 Martin Dougiamas
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import { NgModule } from '@angular/core';
+import { IonicPageModule } from 'ionic-angular';
+import { TranslateModule } from '@ngx-translate/core';
+import { AddonNotificationsSettingsPage } from './settings';
+import { CoreComponentsModule } from '@components/components.module';
+import { CoreDirectivesModule } from '@directives/directives.module';
+
+@NgModule({
+ declarations: [
+ AddonNotificationsSettingsPage,
+ ],
+ imports: [
+ CoreComponentsModule,
+ CoreDirectivesModule,
+ IonicPageModule.forChild(AddonNotificationsSettingsPage),
+ TranslateModule.forChild()
+ ],
+})
+export class AddonNotificationsSettingsPageModule {}
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
new file mode 100644
index 000000000..cf1d19ee3
--- /dev/null
+++ b/src/addon/notifications/pages/settings/settings.ts
@@ -0,0 +1,265 @@
+// (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 { Component, OnDestroy, Optional } from '@angular/core';
+import { IonicPage, NavController } from 'ionic-angular';
+import { AddonNotificationsProvider } from '../../providers/notifications';
+import { CoreUserProvider } from '@core/user/providers/user';
+import { CoreDomUtilsProvider } from '@providers/utils/dom';
+import { CoreSettingsHelper } from '@core/settings/providers/helper';
+import { AddonMessageOutputDelegate, AddonMessageOutputHandlerData } from '@addon/messageoutput/providers/delegate';
+import { CoreLocalNotificationsProvider } from '@providers/local-notifications';
+import { CoreConfigProvider } from '@providers/config';
+import { CoreAppProvider } from '@providers/app';
+import { CoreConstants } from '@core/constants';
+import { CoreEventsProvider } from '@providers/events';
+import { CoreSitesProvider } from '@providers/sites';
+import { CoreSplitViewComponent } from '@components/split-view/split-view';
+
+/**
+ * Page that displays notifications settings.
+ */
+@IonicPage({ segment: 'addon-notifications-settings' })
+@Component({
+ selector: 'page-addon-notifications-settings',
+ templateUrl: 'settings.html',
+})
+export class AddonNotificationsSettingsPage implements OnDestroy {
+ protected updateTimeout: any;
+
+ components: any[];
+ preferences: any;
+ preferencesLoaded: boolean;
+ currentProcessor: any;
+ notifPrefsEnabled: boolean;
+ canChangeSound: boolean;
+ notificationSound: boolean;
+ processorHandlers = [];
+
+ constructor(private notificationsProvider: AddonNotificationsProvider, private domUtils: CoreDomUtilsProvider,
+ private settingsHelper: CoreSettingsHelper, private userProvider: CoreUserProvider,
+ private navCtrl: NavController, private messageOutputDelegate: AddonMessageOutputDelegate,
+ appProvider: CoreAppProvider, private configProvider: CoreConfigProvider, private eventsProvider: CoreEventsProvider,
+ private localNotificationsProvider: CoreLocalNotificationsProvider, private sitesProvider: CoreSitesProvider,
+ @Optional() private svComponent: CoreSplitViewComponent) {
+
+ this.notifPrefsEnabled = notificationsProvider.isNotificationPreferencesEnabled();
+ this.canChangeSound = localNotificationsProvider.isAvailable() && !appProvider.isDesktop();
+ if (this.canChangeSound) {
+ configProvider.get(CoreConstants.SETTINGS_NOTIFICATION_SOUND, true).then((enabled) => {
+ this.notificationSound = enabled;
+ });
+ }
+ }
+
+ /**
+ * View loaded.
+ */
+ ionViewDidLoad(): void {
+ if (this.notifPrefsEnabled) {
+ this.fetchPreferences();
+ } else {
+ this.preferencesLoaded = true;
+ }
+ }
+
+ /**
+ * Fetches preference data.
+ *
+ * @return {Promise} Resolved when done.
+ */
+ protected fetchPreferences(): Promise {
+ return this.notificationsProvider.getNotificationPreferences().then((preferences) => {
+ if (!this.currentProcessor) {
+ // Initialize current processor. Load "Mobile" (airnotifier) if available.
+ this.currentProcessor = this.settingsHelper.getProcessor(preferences.processors, 'airnotifier');
+ }
+
+ if (!this.currentProcessor) {
+ // Shouldn't happen.
+ return Promise.reject('No processor found');
+ }
+
+ preferences.disableall = !!preferences.disableall; // Convert to boolean.
+ this.preferences = preferences;
+ this.loadProcessor(this.currentProcessor);
+
+ // Get display data of message output handlers (thery are displayed in the context menu),
+ this.processorHandlers = [];
+ if (preferences.processors) {
+ preferences.processors.forEach((processor) => {
+ processor.supported = this.messageOutputDelegate.hasHandler(processor.name, true);
+ if (processor.hassettings && processor.supported) {
+ this.processorHandlers.push(this.messageOutputDelegate.getDisplayData(processor));
+ }
+ });
+ }
+ }).catch((message) => {
+ this.domUtils.showErrorModal(message);
+ }).finally(() => {
+ this.preferencesLoaded = true;
+ });
+ }
+
+ /**
+ * Load a processor.
+ *
+ * @param {any} processor Processor object.
+ */
+ protected loadProcessor(processor: any): void {
+ if (!processor) {
+ return;
+ }
+ this.currentProcessor = processor;
+ this.components = this.settingsHelper.getProcessorComponents(processor.name, this.preferences.components);
+ }
+
+ /**
+ * Update preferences after a certain time. The purpose is to store the updated data, it won't be reflected in the view.
+ */
+ protected updatePreferencesAfterDelay(): void {
+ // Cancel pending updates.
+ clearTimeout(this.updateTimeout);
+
+ this.updateTimeout = setTimeout(() => {
+ this.updateTimeout = null;
+ this.updatePreferences();
+ }, 5000);
+ }
+
+ /**
+ * Update preferences. The purpose is to store the updated data, it won't be reflected in the view.
+ */
+ protected updatePreferences(): void {
+ this.notificationsProvider.invalidateNotificationPreferences().finally(() => {
+ this.notificationsProvider.getNotificationPreferences();
+ });
+ }
+
+ /**
+ * The selected processor was changed.
+ *
+ * @param {string} name Name of the selected processor.
+ */
+ changeProcessor(name: string): void {
+ this.preferences.processors.forEach((processor) => {
+ if (processor.name == name) {
+ this.loadProcessor(processor);
+ }
+ });
+ }
+
+ /**
+ * Refresh the list of preferences.
+ *
+ * @param {any} [refresher] Refresher.
+ */
+ refreshPreferences(refresher?: any): void {
+ this.notificationsProvider.invalidateNotificationPreferences().finally(() => {
+ this.fetchPreferences().finally(() => {
+ refresher && refresher.complete();
+ });
+ });
+ }
+
+ /**
+ * Open extra preferences.
+ *
+ * @param {AddonMessageOutputHandlerData} handlerData
+ */
+ openExtraPreferences(handlerData: AddonMessageOutputHandlerData): void {
+ // Decide which navCtrl to use. If this page is inside a split view, use the split view's master nav.
+ const navCtrl = this.svComponent ? this.svComponent.getMasterNav() : this.navCtrl;
+ navCtrl.push(handlerData.page, handlerData.pageParams);
+ }
+
+ /**
+ * Change the value of a certain preference.
+ *
+ * @param {any} notification Notification object.
+ * @param {string} state State name, ['loggedin', 'loggedoff'].
+ */
+ changePreference(notification: any, state: string): void {
+ const processorState = notification.currentProcessor[state];
+ const preferenceName = notification.preferencekey + '_' + processorState.name;
+ let value;
+
+ notification.processors.forEach((processor) => {
+ if (processor[state].checked) {
+ if (!value) {
+ value = processor.name;
+ } else {
+ value += ',' + processor.name;
+ }
+ }
+ });
+
+ if (!value) {
+ value = 'none';
+ }
+
+ processorState.updating = true;
+ this.userProvider.updateUserPreference(preferenceName, value).then(() => {
+ // Update the preferences since they were modified.
+ this.updatePreferencesAfterDelay();
+ }).catch((message) => {
+ // Show error and revert change.
+ this.domUtils.showErrorModal(message);
+ notification.currentProcessor[state].checked = !notification.currentProcessor[state].checked;
+ }).finally(() => {
+ processorState.updating = false;
+ });
+ }
+
+ /**
+ * Disable all notifications changed.
+ */
+ disableAll(disable: boolean): void {
+ const modal = this.domUtils.showModalLoading('core.sending', true);
+ this.userProvider.updateUserPreferences([], disable).then(() => {
+ // Update the preferences since they were modified.
+ this.updatePreferencesAfterDelay();
+ }).catch((message) => {
+ // Show error and revert change.
+ this.domUtils.showErrorModal(message);
+ this.preferences.disableall = !this.preferences.disableall;
+ }).finally(() => {
+ modal.dismiss();
+ });
+ }
+
+ /**
+ * Change the notification sound setting.
+ *
+ * @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(() => {
+ const siteId = this.sitesProvider.getCurrentSiteId();
+ this.eventsProvider.trigger(CoreEventsProvider.NOTIFICATION_SOUND_CHANGED, {enabled}, siteId);
+ this.localNotificationsProvider.rescheduleAll();
+ });
+ }
+
+ /**
+ * Page destroyed.
+ */
+ ngOnDestroy(): void {
+ // If there is a pending action to update preferences, execute it right now.
+ if (this.updateTimeout) {
+ clearTimeout(this.updateTimeout);
+ this.updatePreferences();
+ }
+ }
+}
diff --git a/src/addon/notifications/providers/cron-handler.ts b/src/addon/notifications/providers/cron-handler.ts
new file mode 100644
index 000000000..78f317c41
--- /dev/null
+++ b/src/addon/notifications/providers/cron-handler.ts
@@ -0,0 +1,112 @@
+// (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 { CoreAppProvider } from '@providers/app';
+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';
+
+/**
+ * Notifications cron handler.
+ */
+@Injectable()
+export class AddonNotificationsCronHandler implements CoreCronHandler {
+ name = 'AddonNotificationsCronHandler';
+
+ constructor(private appProvider: CoreAppProvider, private eventsProvider: CoreEventsProvider,
+ private sitesProvider: CoreSitesProvider, private localNotifications: CoreLocalNotificationsProvider,
+ private notificationsProvider: AddonNotificationsProvider, private textUtils: CoreTextUtilsProvider,
+ private emulatorHelper: CoreEmulatorHelperProvider) {}
+
+ /**
+ * Get the time between consecutive executions.
+ *
+ * @return {number} Time between consecutive executions (in ms).
+ */
+ getInterval(): number {
+ return this.appProvider.isDesktop() ? 60000 : 600000; // 1 or 10 minutes.
+ }
+
+ /**
+ * Check whether it's a synchronization process or not. True if not defined.
+ *
+ * @return {boolean} Whether it's a synchronization process or not.
+ */
+ isSync(): boolean {
+ // This is done to use only wifi if using the fallback function.
+ // In desktop it is always sync, since it fetches notification to see if there's a new one.
+ return !this.notificationsProvider.isPreciseNotificationCountEnabled() || this.appProvider.isDesktop();
+ }
+
+ /**
+ * Check whether the sync can be executed manually. Call isSync if not defined.
+ *
+ * @return {boolean} Whether the sync can be executed manually.
+ */
+ canManualSync(): boolean {
+ return true;
+ }
+
+ /**
+ * Execute the process.
+ *
+ * @param {string} [siteId] ID of the site affected. If not defined, all sites.
+ * @return {Promise} Promise resolved when done. If the promise is rejected, this function will be called again often,
+ * it shouldn't be abused.
+ */
+ execute(siteId?: string): Promise {
+ if (this.sitesProvider.isCurrentSite(siteId)) {
+ this.eventsProvider.trigger(AddonNotificationsProvider.READ_CRON_EVENT, {}, this.sitesProvider.getCurrentSiteId());
+ }
+
+ if (this.appProvider.isDesktop() && this.localNotifications.isAvailable()) {
+ 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
new file mode 100644
index 000000000..8866d14a8
--- /dev/null
+++ b/src/addon/notifications/providers/mainmenu-handler.ts
@@ -0,0 +1,115 @@
+// (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 { CoreEventsProvider } from '@providers/events';
+import { CoreSitesProvider } from '@providers/sites';
+import { CoreUtilsProvider } from '@providers/utils/utils';
+import { CoreMainMenuHandler, CoreMainMenuHandlerData } from '@core/mainmenu/providers/delegate';
+import { AddonNotificationsProvider } from './notifications';
+import { AddonPushNotificationsProvider } from '@addon/pushnotifications/providers/pushnotifications';
+import { AddonPushNotificationsDelegate } from '@addon/pushnotifications/providers/delegate';
+
+/**
+ * Handler to inject an option into main menu.
+ */
+@Injectable()
+export class AddonNotificationsMainMenuHandler implements CoreMainMenuHandler {
+ name = 'AddonNotifications';
+ priority = 700;
+
+ protected handler: CoreMainMenuHandlerData = {
+ icon: 'notifications',
+ title: 'addon.notifications.notifications',
+ page: 'AddonNotificationsListPage',
+ class: 'addon-notifications-handler',
+ showBadge: true,
+ badge: '',
+ loading: true,
+ };
+
+ constructor(eventsProvider: CoreEventsProvider, private sitesProvider: CoreSitesProvider,
+ utils: CoreUtilsProvider, private notificationsProvider: AddonNotificationsProvider,
+ private pushNotificationsProvider: AddonPushNotificationsProvider,
+ pushNotificationsDelegate: AddonPushNotificationsDelegate) {
+
+ eventsProvider.on(AddonNotificationsProvider.READ_CHANGED_EVENT, (data) => {
+ this.updateBadge(data.siteId);
+ });
+
+ eventsProvider.on(AddonNotificationsProvider.READ_CRON_EVENT, (data) => {
+ this.updateBadge(data.siteId);
+ });
+
+ // Reset info on logout.
+ eventsProvider.on(CoreEventsProvider.LOGOUT, (data) => {
+ this.handler.badge = '';
+ this.handler.loading = true;
+ });
+
+ // If a push notification is received, refresh the count.
+ pushNotificationsDelegate.on('receive').subscribe((notification) => {
+ // New notification received. If it's from current site, refresh the data.
+ if (utils.isTrueOrOne(notification.notif) && this.sitesProvider.isCurrentSite(notification.site)) {
+ this.updateBadge(notification.site);
+ }
+ });
+
+ // Register Badge counter.
+ pushNotificationsDelegate.registerCounterHandler('AddonNotifications');
+ }
+
+ /**
+ * Check if the handler is enabled on a site level.
+ *
+ * @return {boolean} Whether or not the handler is enabled on a site level.
+ */
+ isEnabled(): boolean | Promise {
+ return true;
+ }
+
+ /**
+ * Returns the data needed to render the handler.
+ *
+ * @return {CoreMainMenuHandlerData} Data needed to render the handler.
+ */
+ getDisplayData(): CoreMainMenuHandlerData {
+ if (this.handler.loading) {
+ this.updateBadge();
+ }
+
+ return this.handler;
+ }
+
+ /**
+ * 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.
+ */
+ updateBadge(siteId?: string): void {
+ siteId = siteId || this.sitesProvider.getCurrentSiteId();
+ if (!siteId) {
+ return;
+ }
+
+ this.notificationsProvider.getUnreadNotificationsCount(null, siteId).then((unread) => {
+ this.handler.badge = unread > 0 ? String(unread) : '';
+ this.pushNotificationsProvider.updateAddonCounter('AddonNotifications', unread, siteId);
+ }).catch(() => {
+ this.handler.badge = '';
+ }).finally(() => {
+ this.handler.loading = false;
+ });
+ }
+}
diff --git a/src/addon/notifications/providers/notifications.ts b/src/addon/notifications/providers/notifications.ts
new file mode 100644
index 000000000..3fefc469c
--- /dev/null
+++ b/src/addon/notifications/providers/notifications.ts
@@ -0,0 +1,291 @@
+// (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 { CoreAppProvider } from '@providers/app';
+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.
+ */
+@Injectable()
+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 = '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 emulatorHelper: CoreEmulatorHelperProvider) {
+ this.logger = logger.getInstance('AddonNotificationsProvider');
+ }
+
+ /**
+ * Function to format notification data.
+ *
+ * @param {any[]} notifications List of notifications.
+ */
+ protected formatNotificationsData(notifications: any[]): void {
+ notifications.forEach((notification) => {
+ // Set message to show.
+ if (notification.contexturl && notification.contexturl.indexOf('/mod/forum/')) {
+ notification.mobiletext = notification.smallmessage;
+ } else {
+ notification.mobiletext = notification.fullmessage;
+ }
+ // Try to set courseid the notification belongs to.
+ const cid = notification.fullmessagehtml.match(/course\/view\.php\?id=([^"]*)/);
+ if (cid && cid[1]) {
+ notification.courseid = cid[1];
+ }
+ // Try to get the profile picture of the user.
+ this.userProvider.getProfile(notification.useridfrom, notification.courseid, true).then((user) => {
+ notification.profileimageurlfrom = user.profileimageurl;
+ });
+ });
+ }
+
+ /**
+ * Get the cache key for the get notification preferences call.
+ *
+ * @return {string} Cache key.
+ */
+ protected getNotificationPreferencesCacheKey(): string {
+ return this.ROOT_CACHE_KEY + 'notificationPreferences';
+ }
+
+ /**
+ * Get notification preferences.
+ *
+ * @param {string} [siteId] Site ID. If not defined, use current site.
+ * @return {Promise} Promise resolved with the notification preferences.
+ */
+ getNotificationPreferences(siteId?: string): Promise {
+ this.logger.debug('Get notification preferences');
+
+ return this.sitesProvider.getSite(siteId).then((site) => {
+ const preSets = {
+ cacheKey: this.getNotificationPreferencesCacheKey()
+ };
+
+ return site.read('core_message_get_user_notification_preferences', {}, preSets).then((data) => {
+ return data.preferences;
+ });
+ });
+ }
+
+ /**
+ * Get cache key for notification list WS calls.
+ *
+ * @return {string} Cache key.
+ */
+ protected getNotificationsCacheKey(): string {
+ return this.ROOT_CACHE_KEY + 'list';
+ }
+
+ /**
+ * Get notifications from site.
+ *
+ * @param {boolean} read True if should get read notifications, false otherwise.
+ * @param {number} limitFrom Position of the first notification to get.
+ * @param {number} limitNumber Number of notifications to get or 0 to use the default limit.
+ * @param {boolean} [toDisplay=true] True if notifications will be displayed to the user, either in view or in a notification.
+ * @param {boolean} [forceCache] True if it should return cached data. Has priority over ignoreCache.
+ * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down).
+ * @param {string} [siteId] Site ID. If not defined, use current site.
+ * @return {Promise} Promise resolved with notifications.
+ */
+ getNotifications(read: boolean, limitFrom: number, limitNumber: number = 0, toDisplay: boolean = true,
+ forceCache?: boolean, ignoreCache?: boolean, siteId?: string): Promise {
+ limitNumber = limitNumber || AddonNotificationsProvider.LIST_LIMIT;
+ this.logger.debug('Get ' + (read ? 'read' : 'unread') + ' notifications from ' + limitFrom + '. Limit: ' + limitNumber);
+
+ return this.sitesProvider.getSite(siteId).then((site) => {
+ const data = {
+ useridto: site.getUserId(),
+ useridfrom: 0,
+ type: 'notifications',
+ read: read ? 1 : 0,
+ newestfirst: 1,
+ limitfrom: limitFrom,
+ limitnum: limitNumber
+ };
+ const preSets: object = {
+ cacheKey: this.getNotificationsCacheKey(),
+ omitExpires: forceCache,
+ getFromCache: forceCache || !ignoreCache,
+ emergencyCache: forceCache || !ignoreCache,
+ };
+
+ // Get unread notifications.
+ return site.read('core_message_get_messages', data, preSets).then((response) => {
+ if (response.messages) {
+ const notifications = response.messages;
+ this.formatNotificationsData(notifications);
+ if (this.appProvider.isDesktop() && toDisplay && !read && limitFrom === 0) {
+ // Store the last received notification. Don't block the user for this.
+ this.emulatorHelper.storeLastReceivedNotification(
+ AddonNotificationsProvider.PUSH_SIMULATION_COMPONENT, notifications[0], siteId);
+ }
+
+ return notifications;
+ } else {
+ return Promise.reject(null);
+ }
+ });
+ });
+ }
+
+ /**
+ * Get read notifications from site.
+ *
+ * @param {number} limitFrom Position of the first notification to get.
+ * @param {number} limitNumber Number of notifications to get.
+ * @param {boolean} [toDisplay=true] True if notifications will be displayed to the user, either in view or in a notification.
+ * @param {boolean} [forceCache] True if it should return cached data. Has priority over ignoreCache.
+ * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down).
+ * @param {string} [siteId] Site ID. If not defined, use current site.
+ * @return {Promise} Promise resolved with notifications.
+ */
+ getReadNotifications(limitFrom: number, limitNumber: number, toDisplay: boolean = true,
+ forceCache?: boolean, ignoreCache?: boolean, siteId?: string): Promise {
+ return this.getNotifications(true, limitFrom, limitNumber, toDisplay, forceCache, ignoreCache, siteId);
+ }
+
+ /**
+ * Get unread notifications from site.
+ *
+ * @param {number} limitFrom Position of the first notification to get.
+ * @param {number} limitNumber Number of notifications to get.
+ * @param {boolean} [toDisplay=true] True if notifications will be displayed to the user, either in view or in a notification.
+ * @param {boolean} [forceCache] True if it should return cached data. Has priority over ignoreCache.
+ * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down).
+ * @param {string} [siteId] Site ID. If not defined, use current site.
+ * @return {Promise} Promise resolved with notifications.
+ */
+ getUnreadNotifications(limitFrom: number, limitNumber: number, toDisplay: boolean = true,
+ forceCache?: boolean, ignoreCache?: boolean, siteId?: string): Promise {
+ return this.getNotifications(false, limitFrom, limitNumber, toDisplay, forceCache, ignoreCache, siteId);
+ }
+
+ /**
+ * Get unread notifications count. Do not cache calls.
+ *
+ * @param {number} [userId] The user id who received the notification. If not defined, use current user.
+ * @param {string} [siteId] Site ID. If not defined, use current site.
+ * @return {Promise} Promise resolved with the message notifications count.
+ */
+ getUnreadNotificationsCount(userId?: number, siteId?: string): Promise {
+ return this.sitesProvider.getSite(siteId).then((site) => {
+ // @since 3.2
+ if (site.wsAvailable('message_popup_get_unread_popup_notification_count')) {
+ userId = userId || site.getUserId();
+ const params = {
+ useridto: userId
+ };
+ const preSets = {
+ getFromCache: false,
+ emergencyCache: false,
+ saveToCache: false,
+ typeExpected: 'number'
+ };
+
+ return site.read('message_popup_get_unread_popup_notification_count', params, preSets).catch(() => {
+ // Return no messages if the call fails.
+ return 0;
+ });
+ }
+
+ // Fallback call.
+ const limit = AddonNotificationsProvider.LIST_LIMIT + 1;
+
+ return this.getUnreadNotifications(0, limit, false, false, false, siteId).then((unread) => {
+ // Add + sign if there are more than the limit reachable.
+ return (unread.length > AddonNotificationsProvider.LIST_LIMIT) ?
+ AddonNotificationsProvider.LIST_LIMIT + '+' : unread.length;
+ }).catch(() => {
+ // Return no messages if the call fails.
+ return 0;
+ });
+ });
+ }
+
+ /**
+ * Mark message notification as read.
+ *
+ * @param {number} notificationId ID of notification to mark as read
+ * @returns {Promise} Resolved when done.
+ */
+ markNotificationRead(notificationId: number): Promise {
+ const params = {
+ messageid: notificationId,
+ timeread: this.timeUtils.timestamp()
+ };
+
+ return this.sitesProvider.getCurrentSite().write('core_message_mark_message_read', params);
+ }
+
+ /**
+ * Invalidate get notification preferences.
+ *
+ * @param {string} [siteId] Site ID. If not defined, current site.
+ * @return {Promise} Promise resolved when data is invalidated.
+ */
+ invalidateNotificationPreferences(siteId?: string): Promise {
+ return this.sitesProvider.getSite(siteId).then((site) => {
+ return site.invalidateWsCacheForKey(this.getNotificationPreferencesCacheKey());
+ });
+ }
+
+ /**
+ * Invalidates notifications list WS calls.
+ *
+ * @param {string} [siteId] Site ID. If not defined, current site.
+ * @return {Promise} Promise resolved when the list is invalidated.
+ */
+ invalidateNotificationsList(siteId?: string): Promise {
+ return this.sitesProvider.getSite(siteId).then((site) => {
+ return site.invalidateWsCacheForKey(this.getNotificationsCacheKey());
+ });
+ }
+
+ /**
+ * Returns whether or not we can count unread notifications precisely.
+ *
+ * @return {boolean} True if enabled, false otherwise.
+ * @since 3.2
+ */
+ isPreciseNotificationCountEnabled(): boolean {
+ return this.sitesProvider.wsAvailableInCurrentSite('message_popup_get_unread_popup_notification_count');
+ }
+
+ /**
+ * Returns whether or not the notification preferences are enabled for the current site.
+ *
+ * @return {boolean} True if enabled, false otherwise.
+ * @since 3.2
+ */
+ isNotificationPreferencesEnabled(): boolean {
+ return this.sitesProvider.wsAvailableInCurrentSite('core_message_get_user_notification_preferences');
+ }
+}
diff --git a/src/addon/notifications/providers/settings-handler.ts b/src/addon/notifications/providers/settings-handler.ts
new file mode 100644
index 000000000..1bb8002f7
--- /dev/null
+++ b/src/addon/notifications/providers/settings-handler.ts
@@ -0,0 +1,57 @@
+// (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 { AddonNotificationsProvider } from './notifications';
+import { CoreAppProvider } from '@providers/app';
+import { CoreLocalNotificationsProvider } from '@providers/local-notifications';
+import { CoreSettingsHandler, CoreSettingsHandlerData } from '@core/settings/providers/delegate';
+
+/**
+ * Notifications settings handler.
+ */
+@Injectable()
+export class AddonNotificationsSettingsHandler implements CoreSettingsHandler {
+ name = 'AddonNotifications';
+ priority = 500;
+
+ constructor(private appProvider: CoreAppProvider, private localNotificationsProvider: CoreLocalNotificationsProvider,
+ private notificationsProvider: AddonNotificationsProvider) {
+ }
+
+ /**
+ * Check if the handler is enabled on a site level.
+ *
+ * @return {boolean | Promise} Whether or not the handler is enabled on a site level.
+ */
+ isEnabled(): boolean | Promise {
+ // Preferences or notification sound setting available.
+ return (this.notificationsProvider.isNotificationPreferencesEnabled() ||
+ this.localNotificationsProvider.isAvailable() && !this.appProvider.isDesktop());
+ }
+
+ /**
+ * Returns the data needed to render the handler.
+ *
+ * @return {CoreSettingsHandlerData} Data needed to render the handler.
+ */
+ getDisplayData(): CoreSettingsHandlerData {
+ return {
+ icon: 'notifications',
+ title: 'addon.notifications.notificationpreferences',
+ page: 'AddonNotificationsSettingsPage',
+ class: 'addon-notifications-settings-handler'
+ };
+ }
+}
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/app/app.module.ts b/src/app/app.module.ts
index 03f660c6a..fc46e8adf 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -82,9 +82,12 @@ import { AddonModFolderModule } from '@addon/mod/folder/folder.module';
import { AddonModPageModule } from '@addon/mod/page/page.module';
import { AddonModUrlModule } from '@addon/mod/url/url.module';
import { AddonModSurveyModule } from '@addon/mod/survey/survey.module';
+import { AddonMessageOutputModule } from '@addon/messageoutput/messageoutput.module';
+import { AddonMessageOutputAirnotifierModule } from '@addon/messageoutput/airnotifier/airnotifier.module';
import { AddonMessagesModule } from '@addon/messages/messages.module';
import { AddonNotesModule } from '../addon/notes/notes.module';
import { AddonPushNotificationsModule } from '@addon/pushnotifications/pushnotifications.module';
+import { AddonNotificationsModule } from '@addon/notifications/notifications.module';
import { AddonRemoteThemesModule } from '@addon/remotethemes/remotethemes.module';
import { AddonQbehaviourModule } from '@addon/qbehaviour/qbehaviour.module';
import { AddonQtypeModule } from '@addon/qtype/qtype.module';
@@ -171,8 +174,11 @@ export const CORE_PROVIDERS: any[] = [
AddonModPageModule,
AddonModUrlModule,
AddonModSurveyModule,
+ AddonMessageOutputModule,
+ AddonMessageOutputAirnotifierModule,
AddonMessagesModule,
AddonNotesModule,
+ AddonNotificationsModule,
AddonPushNotificationsModule,
AddonRemoteThemesModule,
AddonQbehaviourModule,
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');
+ }
+}
diff --git a/src/core/settings/lang/en.json b/src/core/settings/lang/en.json
index 6e7ad59e0..0999dce8c 100644
--- a/src/core/settings/lang/en.json
+++ b/src/core/settings/lang/en.json
@@ -1,5 +1,7 @@
{
"about": "About",
+ "disableall": "Disable notifications",
+ "disabled": "Disabled",
"general": "General",
"loggedin": "Online",
"loggedoff": "Offline",
@@ -7,4 +9,4 @@
"sites": "Sites",
"spaceusage": "Space usage",
"synchronization": "Synchronisation"
-}
\ No newline at end of file
+}
diff --git a/src/core/settings/pages/list/list.html b/src/core/settings/pages/list/list.html
index b26591fe8..d24df5cb8 100644
--- a/src/core/settings/pages/list/list.html
+++ b/src/core/settings/pages/list/list.html
@@ -1,6 +1,8 @@
{{ 'core.settings.settings' | translate}}
+
+
diff --git a/src/core/settings/providers/helper.ts b/src/core/settings/providers/helper.ts
new file mode 100644
index 000000000..c4fb77b71
--- /dev/null
+++ b/src/core/settings/providers/helper.ts
@@ -0,0 +1,94 @@
+// (C) Copyright 2015 Martin Dougiamas
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import { Injectable } from '@angular/core';
+import { CoreLoggerProvider } from '@providers/logger';
+import { CoreUtilsProvider } from '@providers/utils/utils';
+
+/**
+ * Settings helper service.
+ */
+@Injectable()
+export class CoreSettingsHelper {
+ protected logger;
+
+ constructor(loggerProvider: CoreLoggerProvider, private utils: CoreUtilsProvider) {
+ this.logger = loggerProvider.getInstance('CoreSettingsHelper');
+ }
+
+ /**
+ * Get a certain processor from a list of processors.
+ *
+ * @param {any[]} processors List of processors.
+ * @param {string} name Name of the processor to get.
+ * @param {boolean} [fallback=true] True to return first processor if not found, false to not return any. Defaults to true.
+ * @return {any} Processor.
+ */
+ getProcessor(processors: any[], name: string, fallback: boolean = true): any {
+ if (!processors || !processors.length) {
+ return;
+ }
+ for (let i = 0; i < processors.length; i++) {
+ if (processors[i].name == name) {
+ return processors[i];
+ }
+ }
+
+ // Processor not found, return first if requested.
+ if (fallback) {
+ return processors[0];
+ }
+ }
+
+ /**
+ * Return the components and notifications that have a certain processor.
+ *
+ * @param {string} processor Name of the processor to filter.
+ * @param {any[]} components Array of components.
+ * @return {any[]} Filtered components.
+ */
+ getProcessorComponents(processor: string, components: any[]): any[] {
+ const result = [];
+
+ components.forEach((component) => {
+ // Create a copy of the component with an empty list of notifications.
+ const componentCopy = this.utils.clone(component);
+ componentCopy.notifications = [];
+
+ component.notifications.forEach((notification) => {
+ let hasProcessor = false;
+ for (let i = 0; i < notification.processors.length; i++) {
+ const proc = notification.processors[i];
+ if (proc.name == processor) {
+ hasProcessor = true;
+ notification.currentProcessor = proc;
+ break;
+ }
+ }
+
+ if (hasProcessor) {
+ // Add the notification.
+ componentCopy.notifications.push(notification);
+ }
+ });
+
+ if (componentCopy.notifications.length) {
+ // At least 1 notification added, add the component to the result.
+ result.push(componentCopy);
+ }
+ });
+
+ return result;
+ }
+}
diff --git a/src/core/settings/settings.module.ts b/src/core/settings/settings.module.ts
index 91efeb1b1..e658b90f5 100644
--- a/src/core/settings/settings.module.ts
+++ b/src/core/settings/settings.module.ts
@@ -14,6 +14,7 @@
import { NgModule } from '@angular/core';
import { CoreSettingsDelegate } from './providers/delegate';
+import { CoreSettingsHelper } from './providers/helper';
@NgModule({
declarations: [
@@ -21,7 +22,8 @@ import { CoreSettingsDelegate } from './providers/delegate';
imports: [
],
providers: [
- CoreSettingsDelegate
+ CoreSettingsDelegate,
+ CoreSettingsHelper
]
})
export class CoreSettingsModule {}