MOBILE-2328 notifications: Migrate Notifications
parent
f3ea09c7ea
commit
133866598e
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"processorsettingsdesc": "Configure devices"
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
<ion-header>
|
||||
<ion-navbar>
|
||||
<ion-title>{{ 'addon.messageoutput_airnotifier.processorsettingsdesc' | translate }}</ion-title>
|
||||
</ion-navbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<ion-refresher [enabled]="devicesLoaded" (ionRefresh)="refreshDevices($event)">
|
||||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||
</ion-refresher>
|
||||
<core-loading [hideUntil]="devicesLoaded">
|
||||
<ion-list>
|
||||
<ion-item text-wrap *ngFor="let device of devices">
|
||||
<ion-label [class.core-bold]="device.current">
|
||||
{{ device.model }}
|
||||
<span *ngIf="device.current">({{ 'core.currentdevice' | translate }})</span>
|
||||
</ion-label>
|
||||
<ion-spinner *ngIf="device.updating" item-end></ion-spinner>
|
||||
<ion-toggle [disabled]="device.updating" [(ngModel)]="device.enable" (ngModelChange)="enableDevice(device, device.enable)"></ion-toggle>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</core-loading>
|
||||
</ion-content>
|
|
@ -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 {}
|
|
@ -0,0 +1,138 @@
|
|||
// (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 {
|
||||
|
||||
title = '';
|
||||
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<any>} Promise resolved when done.
|
||||
*/
|
||||
protected fetchDevices(): Promise<any> {
|
||||
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
|
||||
* @param {boolean} enable
|
||||
*/
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<any>} Promise resolved if success.
|
||||
*/
|
||||
enableDevice(deviceId: number, enable: boolean, siteId?: string): Promise<any> {
|
||||
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<any>} Promise resolved with the devices.
|
||||
*/
|
||||
getUserDevices(siteId?: string): Promise<any> {
|
||||
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<any>} Promise resolved when data is invalidated.
|
||||
*/
|
||||
invalidateUserDevices(siteId?: string): Promise<any> {
|
||||
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');
|
||||
}
|
||||
}
|
|
@ -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',
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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 {}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
<ion-row *ngIf="actions && actions.length > 0">
|
||||
<ion-col *ngFor="let action of actions">
|
||||
<button ion-button icon-left clear small (click)="action.action()">
|
||||
<ion-icon name="{{action.icon}}"></ion-icon>
|
||||
{{ action.message | translate }}
|
||||
</button>
|
||||
</ion-col>
|
||||
</ion-row>
|
|
@ -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;
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
// (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
|
||||
],
|
||||
entryComponents: [
|
||||
AddonNotificationsActionsComponent
|
||||
]
|
||||
})
|
||||
export class AddonNotificationsComponentsModule {}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"errorgetnotifications": "Error getting notifications.",
|
||||
"notificationpreferences": "Notification preferences",
|
||||
"notifications": "Notifications",
|
||||
"playsound": "Play sound",
|
||||
"therearentnotificationsyet": "There are no notifications."
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
<ion-header>
|
||||
<ion-navbar>
|
||||
<ion-title>{{ 'addon.notifications.notifications' | translate }}</ion-title>
|
||||
</ion-navbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<ion-refresher [enabled]="notificationsLoaded" (ionRefresh)="refreshNotifications($event)">
|
||||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||
</ion-refresher>
|
||||
<core-loading [hideUntil]="notificationsLoaded">
|
||||
<ion-card *ngFor="let notification of notifications">
|
||||
<ion-item>
|
||||
<ion-avatar item-start core-user-link [userId]="notification.useridfrom" [courseId]="notification.courseid">
|
||||
<img [src]="notification.profileimageurlfrom || 'assets/img/user-avatar.png'" core-external-content [alt]="'core.pictureof' | translate:{$a: notification.userfromfullname}" role="presentation">
|
||||
</ion-avatar>
|
||||
<h2>{{notification.userfromfullname}}</h2>
|
||||
<div item-end *ngIf="!notification.timeread"><ion-icon name="record" color=""></ion-icon></div>
|
||||
<p>{{notification.timecreated | coreDateDayOrTime}}</p>
|
||||
</ion-item>
|
||||
<ion-item text-wrap>
|
||||
<p><core-format-text [text]="formatText(notification.mobiletext | coreCreateLinks)"></core-format-text></p>
|
||||
</ion-item>
|
||||
<addon-notifications-actions [contextUrl]="notification.contexturl" [courseId]="notification.courseid"></addon-notifications-actions>
|
||||
</ion-card>
|
||||
<core-empty-box *ngIf="!notifications || notifications.length <= 0" icon="notifications" [message]="'addon.notifications.therearentnotificationsyet' | translate"></core-empty-box>
|
||||
<ion-infinite-scroll [enabled]="canLoadMore" (ionInfinite)="loadMoreNotifications($event)">
|
||||
<ion-infinite-scroll-content></ion-infinite-scroll-content>
|
||||
</ion-infinite-scroll>
|
||||
</core-loading>
|
||||
</ion-content>
|
|
@ -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 {}
|
|
@ -0,0 +1,192 @@
|
|||
// (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
|
||||
* @return {Primise<any>} Resolved when done.
|
||||
*/
|
||||
protected fetchNotifications(refresh?: boolean): Promise<any> {
|
||||
if (refresh) {
|
||||
this.readCount = 0;
|
||||
this.unreadCount = 0;
|
||||
}
|
||||
|
||||
const limit = AddonNotificationsProvider.LIST_LIMIT;
|
||||
|
||||
return this.notificationsProvider.getUnreadNotifications(this.unreadCount, limit).then((unread) => {
|
||||
let promise;
|
||||
/* 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) => {
|
||||
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
|
||||
*/
|
||||
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 {string} text
|
||||
* @return {string} Formatted text.
|
||||
*/
|
||||
formatText(text: string): string {
|
||||
text = text.replace(/-{4,}/ig, '');
|
||||
text = this.textUtils.replaceNewLines(text, '<br>');
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Page destroyed.
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.cronObserver && this.cronObserver.off();
|
||||
this.pushObserver && this.pushObserver.unsubscribe();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
<ion-header>
|
||||
<ion-navbar>
|
||||
<ion-title>{{ 'addon.notifications.notificationpreferences' | translate }}</ion-title>
|
||||
<ion-buttons end>
|
||||
</ion-buttons>
|
||||
</ion-navbar>
|
||||
</ion-header>
|
||||
<core-navbar-buttons>
|
||||
<core-context-menu *ngIf="processorHandlers.length > 0">
|
||||
<core-context-menu-item *ngFor="let handler of processorHandlers" [priority]="handler.priority" [content]="handler.label | translate" (action)="openExtraPreferences(handler)" [iconAction]="handler.icon"></core-context-menu-item>
|
||||
</core-context-menu>
|
||||
</core-navbar-buttons>
|
||||
<ion-content>
|
||||
<ion-refresher [enabled]="preferencesLoaded && notifPrefsEnabled" (ionRefresh)="refreshPreferences($event)">
|
||||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||
</ion-refresher>
|
||||
<core-loading [hideUntil]="preferencesLoaded">
|
||||
<!-- If notification preferences aren't enabled, show only the notification sound setting. -->
|
||||
<ion-item *ngIf="canChangeSound && !notifPrefsEnabled">
|
||||
<ion-label>{{ 'addon.notifications.playsound' | translate }}</ion-label>
|
||||
<ion-toggle [(ngModel)]="notificationSound" (ionChange)="changeNotificationSound(notificationSound)"></ion-toggle>
|
||||
</ion-item>
|
||||
|
||||
<ng-container *ngIf="notifPrefsEnabled">
|
||||
<ion-card>
|
||||
<ion-item text-wrap *ngIf="preferences">
|
||||
<ion-label>{{ 'core.settings.disableall' | translate }}</ion-label>
|
||||
<ion-toggle [(ngModel)]="preferences.disableall" (ionChange)="disableAll(preferences.disableall)"></ion-toggle>
|
||||
</ion-item>
|
||||
<ion-item text-wrap *ngIf="canChangeSound">
|
||||
<ion-label>{{ 'addon.notifications.playsound' | translate }}</ion-label>
|
||||
<ion-toggle [(ngModel)]="notificationSound" (ionChange)="changeNotificationSound(notificationSound)"></ion-toggle>
|
||||
</ion-item>
|
||||
</ion-card>
|
||||
|
||||
<!-- Show processor selector. -->
|
||||
<ion-select *ngIf="preferences && preferences.processors && preferences.processors.length > 0" [ngModel]="currentProcessor.name" (ngModelChange)="changeProcessor($event)" interface="popover">
|
||||
<ion-option *ngFor="let processor of preferences.processors" [value]="processor.name">{{ processor.displayname }}</ion-option>
|
||||
</ion-select>
|
||||
|
||||
<ion-card list *ngFor="let component of components">
|
||||
<ion-item-divider color="light" text-wrap>
|
||||
<ion-row no-padding>
|
||||
<ion-col no-padding>{{ component.displayname }}</ion-col>
|
||||
<ion-col col-2 text-center no-padding class="hidden-phone">{{ 'core.settings.loggedin' | translate }}</ion-col>
|
||||
<ion-col col-2 text-center no-padding class="hidden-phone">{{ 'core.settings.loggedoff' | translate }}</ion-col>
|
||||
</ion-row>
|
||||
</ion-item-divider>
|
||||
<ng-container *ngFor="let notification of component.notifications">
|
||||
<!-- Tablet view -->
|
||||
<ion-row text-wrap class="hidden-phone" align-items-center>
|
||||
<ion-col margin-horizontal>{{ notification.displayname }}</ion-col>
|
||||
<ion-col col-2 text-center *ngFor="let state of ['loggedin', 'loggedoff']">
|
||||
<!-- If notifications not disabled, show toggle. -->
|
||||
<ion-spinner [hidden]="preferences.disableall || !(notification.currentProcessor[state] && notification.currentProcessor[state].updating)"></ion-spinner>
|
||||
<ion-toggle *ngIf="!preferences.disableall" [(ngModel)]="notification.currentProcessor[state].checked" (ionChange)="changePreference(notification, state)" [disabled]="notification.currentProcessor.locked || notification.currentProcessor[state].updating">
|
||||
</ion-toggle>
|
||||
<!-- If notifications are disabled, show "Disabled" instead of toggle. -->
|
||||
<span *ngIf="preferences.disableall">{{ 'core.settings.disabled' | translate }}</span>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
<!-- Phone view -->
|
||||
<ion-list-header text-wrap no-margin class="hidden-tablet">{{ notification.displayname }}</ion-list-header>
|
||||
<!-- If notifications not disabled, show toggles. If notifications are disabled, show "Disabled" instead of toggle. -->
|
||||
<ion-item *ngFor="let state of ['loggedin', 'loggedoff']" text-wrap class="hidden-tablet">
|
||||
<ion-label>{{ 'core.settings.' + state | translate }}</ion-label>
|
||||
<ion-spinner item-end *ngIf="!preferences.disableall && (notification.currentProcessor[state] && notification.currentProcessor[state].updating)"></ion-spinner>
|
||||
<ion-toggle item-end *ngIf="!preferences.disableall" [(ngModel)]="notification.currentProcessor[state].checked" (ionChange)="changePreference(notification, state)" [disabled]="notification.currentProcessor.locked || notification.currentProcessor[state].updating">
|
||||
</ion-toggle>
|
||||
<ion-note item-end *ngIf="preferences.disableall">{{ 'core.settings.disabled' | translate }}</ion-note>
|
||||
</ion-item>
|
||||
</ng-container>
|
||||
</ion-card>
|
||||
</ng-container>
|
||||
</core-loading>
|
||||
</ion-content>
|
|
@ -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 {}
|
|
@ -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<any>} Resolved when done.
|
||||
*/
|
||||
protected fetchPreferences(): Promise<any> {
|
||||
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
|
||||
*/
|
||||
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
|
||||
*/
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
// (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 { 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) {}
|
||||
|
||||
/**
|
||||
* 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<any>} 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<any> {
|
||||
if (this.sitesProvider.isCurrentSite(siteId)) {
|
||||
this.eventsProvider.trigger(AddonNotificationsProvider.READ_CRON_EVENT, {}, this.sitesProvider.getCurrentSiteId());
|
||||
}
|
||||
|
||||
if (this.appProvider.isDesktop() && this.localNotifications.isAvailable()) {
|
||||
/* @todo
|
||||
$mmEmulatorHelper.checkNewNotifications(
|
||||
mmaNotificationsPushSimulationComponent, fetchNotifications, getTitleAndText, siteId);
|
||||
*/
|
||||
}
|
||||
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
}
|
|
@ -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<boolean> {
|
||||
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;
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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';
|
||||
|
||||
/**
|
||||
* 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 = 'AddonMessagesPushSimulation';
|
||||
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) {
|
||||
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<any>} Promise resolved with the notification preferences.
|
||||
*/
|
||||
getNotificationPreferences(siteId?: string): Promise<any> {
|
||||
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<any[]>} Promise resolved with notifications.
|
||||
*/
|
||||
getNotifications(read: boolean, limitFrom: number, limitNumber: number = 0, toDisplay: boolean = true,
|
||||
forceCache?: boolean, ignoreCache?: boolean, siteId?: string): Promise<any[]> {
|
||||
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) {
|
||||
/* @todo
|
||||
// Store the last received notification. Don't block the user for this.
|
||||
$mmEmulatorHelper.storeLastReceivedNotification(
|
||||
mmaNotificationsPushSimulationComponent, 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<any[]>} Promise resolved with notifications.
|
||||
*/
|
||||
getReadNotifications(limitFrom: number, limitNumber: number, toDisplay: boolean = true,
|
||||
forceCache?: boolean, ignoreCache?: boolean, siteId?: string): Promise<any[]> {
|
||||
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<any[]>} Promise resolved with notifications.
|
||||
*/
|
||||
getUnreadNotifications(limitFrom: number, limitNumber: number, toDisplay: boolean = true,
|
||||
forceCache?: boolean, ignoreCache?: boolean, siteId?: string): Promise<any[]> {
|
||||
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<number>} Promise resolved with the message notifications count.
|
||||
*/
|
||||
getUnreadNotificationsCount(userId?: number, siteId?: string): Promise<number> {
|
||||
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<any>} Resolved when done.
|
||||
*/
|
||||
markNotificationRead(notificationId: number): Promise<any> {
|
||||
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<any>} Promise resolved when data is invalidated.
|
||||
*/
|
||||
invalidateNotificationPreferences(siteId?: string): Promise<any> {
|
||||
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<any>} Promise resolved when the list is invalidated.
|
||||
*/
|
||||
invalidateNotificationsList(siteId?: string): Promise<any> {
|
||||
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');
|
||||
}
|
||||
}
|
|
@ -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<boolean>} Whether or not the handler is enabled on a site level.
|
||||
*/
|
||||
isEnabled(): boolean | Promise<boolean> {
|
||||
// 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'
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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';
|
||||
|
||||
|
@ -170,8 +173,11 @@ export const CORE_PROVIDERS: any[] = [
|
|||
AddonModPageModule,
|
||||
AddonModUrlModule,
|
||||
AddonModSurveyModule,
|
||||
AddonMessageOutputModule,
|
||||
AddonMessageOutputAirnotifierModule,
|
||||
AddonMessagesModule,
|
||||
AddonNotesModule,
|
||||
AddonNotificationsModule,
|
||||
AddonPushNotificationsModule,
|
||||
AddonRemoteThemesModule,
|
||||
AddonQbehaviourModule
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
{
|
||||
"about": "About",
|
||||
"disableall": "Disable notifications",
|
||||
"disabled": "Disabled",
|
||||
"general": "General",
|
||||
"loggedin": "Online",
|
||||
"loggedoff": "Offline",
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
<ion-header>
|
||||
<ion-navbar>
|
||||
<ion-title>{{ 'core.settings.settings' | translate}}</ion-title>
|
||||
<ion-buttons end>
|
||||
</ion-buttons>
|
||||
</ion-navbar>
|
||||
</ion-header>
|
||||
<core-split-view>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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 {}
|
||||
|
|
Loading…
Reference in New Issue