Merge pull request #1804 from dpalou/MOBILE-2831

Mobile 2831
main
Juan Leyva 2019-03-12 16:08:00 +01:00 committed by GitHub
commit 07e4be4391
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 644 additions and 63 deletions

View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>AD_UNIT_ID_FOR_BANNER_TEST</key>
<string>ca-app-pub-3940256099942544/2934735716</string>
<key>AD_UNIT_ID_FOR_INTERSTITIAL_TEST</key>
<string>ca-app-pub-3940256099942544/4411468910</string>
<key>CLIENT_ID</key>
<string>694767596569-c2cjrca92k99f6nkp3363lsb7ljhdgdr.apps.googleusercontent.com</string>
<key>REVERSED_CLIENT_ID</key>
<string>com.googleusercontent.apps.694767596569-c2cjrca92k99f6nkp3363lsb7ljhdgdr</string>
<key>API_KEY</key>
<string>AIzaSyA-77ZjkxII6GV97CC9rdUl83rzdEXu_rM</string>
<key>GCM_SENDER_ID</key>
<string>694767596569</string>
<key>PLIST_VERSION</key>
<string>1</string>
<key>BUNDLE_ID</key>
<string>com.moodle.moodlemobile</string>
<key>PROJECT_ID</key>
<string>moodlemobile-push</string>
<key>STORAGE_BUCKET</key>
<string>moodlemobile-push.appspot.com</string>
<key>IS_ADS_ENABLED</key>
<true></true>
<key>IS_ANALYTICS_ENABLED</key>
<false></false>
<key>IS_APPINVITE_ENABLED</key>
<true></true>
<key>IS_GCM_ENABLED</key>
<true></true>
<key>IS_SIGNIN_ENABLED</key>
<true></true>
<key>GOOGLE_APP_ID</key>
<string>1:694767596569:ios:a4cdad4d168c9d1a</string>
<key>DATABASE_URL</key>
<string>https://moodlemobile-push.firebaseio.com</string>
</dict>
</plist>

View File

@ -41,6 +41,7 @@
<param name="ios-package" onload="true" value="CDVStatusBar" />
</feature>
<platform name="android">
<resource-file src="google-services.json" target="app/google-services.json" />
<splash qualifier="land-ldpi" src="resources/android/splash/drawable-land-ldpi-screen.png" />
<splash qualifier="land-mdpi" src="resources/android/splash/drawable-land-mdpi-screen.png" />
<splash qualifier="land-hdpi" src="resources/android/splash/drawable-land-hdpi-screen.png" />
@ -77,6 +78,7 @@
<resource-file src="resources/android/splash/drawable-port-xxxhdpi-screen.png" target="app/src/main/res/drawable-port-xxxhdpi/screen.png" />
</platform>
<platform name="ios">
<resource-file src="GoogleService-Info.plist" />
<icon height="57" src="resources/ios/icon/icon.png" width="57" />
<icon height="114" src="resources/ios/icon/icon@2x.png" width="114" />
<icon height="40" src="resources/ios/icon/icon-40.png" width="40" />
@ -127,7 +129,7 @@
<plugin name="cordova-plugin-globalization" spec="^1.11.0" />
<plugin name="cordova-plugin-inappbrowser" spec="^3.0.0" />
<plugin name="cordova-plugin-ionic-keyboard" spec="^2.1.3" />
<plugin name="cordova-plugin-local-notification" spec="^0.9.0-beta.2" />
<plugin name="cordova-plugin-local-notification" spec="^0.9.0-beta.3" />
<plugin name="cordova-plugin-media-capture" spec="^3.0.2" />
<plugin name="cordova-plugin-network-information" spec="^2.0.1" />
<plugin name="cordova-plugin-screen-orientation" spec="^3.0.1" />
@ -137,8 +139,9 @@
<plugin name="cordova-plugin-zip" spec="^3.1.0" />
<plugin name="cordova-sqlite-storage" spec="^2.6.0" />
<plugin name="nl.kingsquare.cordova.background-audio" spec="^1.0.1" />
<plugin name="phonegap-plugin-push" spec="https://github.com/moodlemobile/phonegap-plugin-push.git#moodle">
<variable name="SENDER_ID" value="694767596569" />
<plugin name="phonegap-plugin-push" spec="^2.2.3">
<variable name="ANDROID_SUPPORT_V13_VERSION" value="27.+" />
<variable name="FCM_VERSION" value="11.6.2" />
</plugin>
<edit-config file="AndroidManifest.xml" mode="merge" target="/manifest/application/activity[@android:name='MainActivity']">
<activity android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|screenLayout|smallestScreenSize" android:debuggable="true" />

View File

@ -0,0 +1,45 @@
{
"project_info": {
"project_number": "694767596569",
"firebase_url": "https://moodlemobile-push.firebaseio.com",
"project_id": "moodlemobile-push",
"storage_bucket": "moodlemobile-push.appspot.com"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:694767596569:android:a4cdad4d168c9d1a",
"android_client_info": {
"package_name": "com.moodle.moodlemobile"
}
},
"oauth_client": [
{
"client_id": "694767596569-icveqqa2n56oh44l6ev1dr2oh67nh8il.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyCb2zogu0P_aZ2LNgdwzshWExITPKTXJyk"
},
{
"current_key": "AIzaSyDRT1HwT0gSsTty0whOVtoNKAh8SPrJXLE"
}
],
"services": {
"analytics_service": {
"status": 1
},
"appinvite_service": {
"status": 1,
"other_platform_oauth_client": []
},
"ads_service": {
"status": 2
}
}
}
],
"configuration_version": "1"
}

41
package-lock.json generated
View File

@ -161,9 +161,9 @@
"integrity": "sha512-2BHO1bV4mehWZNfdsWQ/uojxYFNvk4I6u0KYnNb61RiJRY83joCEw3oFkOMRGLZthPf6TN1cueZUIAGMHXA3nA=="
},
"@ionic-native/local-notifications": {
"version": "4.17.0",
"resolved": "https://registry.npmjs.org/@ionic-native/local-notifications/-/local-notifications-4.17.0.tgz",
"integrity": "sha512-NGLGtGRduRU3f/4N2nv4hF550+NkJ9CP7mOS9vlZcZJBzlIup9X67u1M2j/+KFOpWqzS2avZ1gvZbxOmCjPNPw=="
"version": "4.20.0",
"resolved": "https://registry.npmjs.org/@ionic-native/local-notifications/-/local-notifications-4.20.0.tgz",
"integrity": "sha512-Ht/0zau8/2+G/bH/okXXhhWB6YrkCNL2QxVJHQ2dophXFGxQPOZAN3CKWhuQSjfbr76fa2nvQXF6jsXLpIR/ng=="
},
"@ionic-native/media-capture": {
"version": "4.17.0",
@ -1716,6 +1716,11 @@
}
}
},
"babel-plugin-add-header-comment": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/babel-plugin-add-header-comment/-/babel-plugin-add-header-comment-1.0.3.tgz",
"integrity": "sha1-URxJAQYmQNWkgLSsPt1pRBlYUOw="
},
"bach": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/bach/-/bach-1.2.0.tgz",
@ -3168,9 +3173,9 @@
"integrity": "sha512-6ucQ6FdlLdBm8kJfFnzozmBTjru/0xekHP/dAhjoCZggkGRlgs8TsUJFkxa/bV+qi7Dlo50JjmpE4UMWAO+aOQ=="
},
"cordova-plugin-local-notification": {
"version": "0.9.0-beta.2",
"resolved": "https://registry.npmjs.org/cordova-plugin-local-notification/-/cordova-plugin-local-notification-0.9.0-beta.2.tgz",
"integrity": "sha512-63n77K1pt8dnbWnNR8QWETi9Glezi1bvNHvHWmGNIOv0xCb0phZnm+Ku49BQ+omwe8Z5voMvrA4I03SYPpv38w=="
"version": "0.9.0-beta.3",
"resolved": "https://registry.npmjs.org/cordova-plugin-local-notification/-/cordova-plugin-local-notification-0.9.0-beta.3.tgz",
"integrity": "sha512-L3Z1velxrkm9nHFcvLnMgBPZjKFt6hwM6hn1lA+JFwIR26Yw6UF72z+/lRMBclAcOxBIDYCqeaLgvezmajjuEg=="
},
"cordova-plugin-media-capture": {
"version": "3.0.2",
@ -3220,6 +3225,11 @@
"resolved": "https://registry.npmjs.org/cordova-sqlite-storage-dependencies/-/cordova-sqlite-storage-dependencies-1.2.1.tgz",
"integrity": "sha512-4ihQApBGVKR1QZ4oOSGctKFfthtCfiWMTcIIfxe97vKxlvGr9NyXOvYG9vXU9S7yVR7Ua+Rj47hkE7pQIKvQTg=="
},
"cordova-support-google-services": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/cordova-support-google-services/-/cordova-support-google-services-1.1.0.tgz",
"integrity": "sha1-RjTFIgD4cGDReV6yhw6ZRC12Lm0="
},
"core-js": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.3.0.tgz",
@ -6314,6 +6324,11 @@
"integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==",
"dev": true
},
"install": {
"version": "0.8.9",
"resolved": "https://registry.npmjs.org/install/-/install-0.8.9.tgz",
"integrity": "sha1-n0tcDRhR74cunfheT3Fi1OXc2+0="
},
"interpret": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz",
@ -8554,9 +8569,19 @@
"integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=",
"dev": true
},
"phonegap-plugin-multidex": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/phonegap-plugin-multidex/-/phonegap-plugin-multidex-1.0.0.tgz",
"integrity": "sha512-1wvc3iQOQpEBaQbXgLxA2JUiLSQ2azdF/bF29ghXDiQJWSpQ1BF8gSuqttM8WZoj081Ps8OKL0gYxdDBkFNPqA=="
},
"phonegap-plugin-push": {
"version": "git+https://github.com/moodlemobile/phonegap-plugin-push.git#cf2ed2075d9d2d58a4c4f79543f669ed6366c148",
"from": "git+https://github.com/moodlemobile/phonegap-plugin-push.git#moodle"
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/phonegap-plugin-push/-/phonegap-plugin-push-2.2.3.tgz",
"integrity": "sha512-5mjT0G1vfRhXVnZFLwjfzcFwYjVRMibgYDCfYvEujGsP8YwwrIIzcf+xBYAjQV/W2JCjzuNaYd7xJ0yVQaPeig==",
"requires": {
"babel-plugin-add-header-comment": "^1.0.3",
"install": "^0.8.2"
}
},
"pify": {
"version": "2.3.0",

View File

@ -93,7 +93,7 @@
"cordova-plugin-globalization": "^1.11.0",
"cordova-plugin-inappbrowser": "^3.0.0",
"cordova-plugin-ionic-keyboard": "^2.1.3",
"cordova-plugin-local-notification": "^0.9.0-beta.2",
"cordova-plugin-local-notification": "^0.9.0-beta.3",
"cordova-plugin-media-capture": "^3.0.2",
"cordova-plugin-network-information": "^2.0.1",
"cordova-plugin-screen-orientation": "^3.0.1",
@ -102,6 +102,7 @@
"cordova-plugin-whitelist": "^1.3.3",
"cordova-plugin-zip": "^3.1.0",
"cordova-sqlite-storage": "^2.6.0",
"cordova-support-google-services": "^1.1.0",
"es6-promise-plugin": "^4.2.2",
"font-awesome": "^4.7.0",
"ionic-angular": "3.9.3",
@ -109,7 +110,8 @@
"jszip": "^3.1.5",
"moment": "^2.22.2",
"nl.kingsquare.cordova.background-audio": "^1.0.1",
"phonegap-plugin-push": "git+https://github.com/moodlemobile/phonegap-plugin-push.git#moodle",
"phonegap-plugin-multidex": "^1.0.0",
"phonegap-plugin-push": "^2.2.3",
"promise.prototype.finally": "^3.1.0",
"rxjs": "^5.5.11",
"sw-toolbox": "^3.6.0",
@ -169,7 +171,8 @@
"cordova-sqlite-storage": {},
"nl.kingsquare.cordova.background-audio": {},
"phonegap-plugin-push": {
"SENDER_ID": "694767596569"
"ANDROID_SUPPORT_V13_VERSION": "27.+",
"FCM_VERSION": "11.6.2"
}
}
},

View File

@ -68,6 +68,7 @@
"addon.blog.siteblogheading": "blog",
"addon.calendar.calendar": "calendar",
"addon.calendar.calendarevents": "local_moodlemobileapp",
"addon.calendar.calendarreminders": "local_moodlemobileapp",
"addon.calendar.defaultnotificationtime": "local_moodlemobileapp",
"addon.calendar.errorloadevent": "local_moodlemobileapp",
"addon.calendar.errorloadevents": "local_moodlemobileapp",
@ -1508,6 +1509,7 @@
"core.maxsizeandattachments": "moodle",
"core.min": "moodle",
"core.mins": "moodle",
"core.misc": "admin",
"core.mod_assign": "assign/pluginname",
"core.mod_assignment": "assignment/pluginname",
"core.mod_book": "book/pluginname",
@ -1660,6 +1662,7 @@
"core.settings.navigatoruseragent": "local_moodlemobileapp",
"core.settings.networkstatus": "local_moodlemobileapp",
"core.settings.privacypolicy": "local_moodlemobileapp",
"core.settings.pushid": "local_moodlemobileapp",
"core.settings.reportinbackground": "local_moodlemobileapp",
"core.settings.settings": "moodle",
"core.settings.showdownloadoptions": "local_moodlemobileapp",

View File

@ -1,6 +1,7 @@
{
"calendar": "Calendar",
"calendarevents": "Calendar events",
"calendarreminders": "Calendar reminders",
"defaultnotificationtime": "Default notification time",
"errorloadevent": "Error loading event.",
"errorloadevents": "Error loading events.",

View File

@ -13,13 +13,16 @@
// limitations under the License.
import { Injectable, NgZone } from '@angular/core';
import { Platform } from 'ionic-angular';
import { Badge } from '@ionic-native/badge';
import { Push, PushObject, PushOptions } from '@ionic-native/push';
import { Device } from '@ionic-native/device';
import { TranslateService } from '@ngx-translate/core';
import { CoreAppProvider } from '@providers/app';
import { CoreInitDelegate } from '@providers/init';
import { CoreLoggerProvider } from '@providers/logger';
import { CoreSitesProvider } from '@providers/sites';
import { CoreSitesProvider, CoreSiteSchema } from '@providers/sites';
import { CoreSitesFactoryProvider } from '@providers/sites-factory';
import { AddonPushNotificationsDelegate } from './delegate';
import { CoreLocalNotificationsProvider } from '@providers/local-notifications';
import { CoreUtilsProvider } from '@providers/utils/utils';
@ -28,7 +31,55 @@ import { CoreConfigProvider } from '@providers/config';
import { CoreConstants } from '@core/constants';
import { CoreConfigConstants } from '../../../configconstants';
import { ILocalNotification } from '@ionic-native/local-notifications';
import { SQLiteDBTableSchema } from '@classes/sqlitedb';
import { SQLiteDB, SQLiteDBTableSchema } from '@classes/sqlitedb';
import { CoreSite } from '@classes/site';
/**
* Data needed to register a device in a Moodle site.
*/
export interface AddonPushNotificationsRegisterData {
/**
* App ID.
* @type {string}
*/
appid: string;
/**
* Device UUID.
* @type {string}
*/
uuid: string;
/**
* Device name.
* @type {string}
*/
name: string;
/**
* Device model.
* @type {string}
*/
model: string;
/**
* Device platform.
* @type {string}
*/
platform: string;
/**
* Device version.
* @type {string}
*/
version: string;
/**
* Push ID.
* @type {string}
*/
pushid: string;
}
/**
* Service to handle push notifications.
@ -37,12 +88,14 @@ import { SQLiteDBTableSchema } from '@classes/sqlitedb';
export class AddonPushNotificationsProvider {
protected logger;
protected pushID: string;
protected appDB: any;
protected appDB: SQLiteDB;
static COMPONENT = 'AddonPushNotificationsProvider';
// Variables for database.
static BADGE_TABLE = 'addon_pushnotifications_badge';
protected tablesSchema: SQLiteDBTableSchema[] = [
static PENDING_UNREGISTER_TABLE = 'addon_pushnotifications_pending_unregister';
static REGISTERED_DEVICES_TABLE = 'addon_pushnotifications_registered_devices';
protected appTablesSchema: SQLiteDBTableSchema[] = [
{
name: AddonPushNotificationsProvider.BADGE_TABLE,
columns: [
@ -60,17 +113,91 @@ export class AddonPushNotificationsProvider {
}
],
primaryKeys: ['siteid', 'addon']
},
{
name: AddonPushNotificationsProvider.PENDING_UNREGISTER_TABLE,
columns: [
{
name: 'siteid',
type: 'TEXT',
primaryKey: true
},
{
name: 'siteurl',
type: 'TEXT'
},
{
name: 'token',
type: 'TEXT'
},
{
name: 'info',
type: 'TEXT'
}
]
}
];
protected siteSchema: CoreSiteSchema = {
name: 'AddonPushNotificationsProvider',
version: 1,
tables: [
{
name: AddonPushNotificationsProvider.REGISTERED_DEVICES_TABLE,
columns: [
{
name: 'appid',
type: 'TEXT',
},
{
name: 'uuid',
type: 'TEXT'
},
{
name: 'name',
type: 'TEXT'
},
{
name: 'model',
type: 'TEXT'
},
{
name: 'platform',
type: 'TEXT'
},
{
name: 'version',
type: 'TEXT'
},
{
name: 'pushid',
type: 'TEXT'
},
],
primaryKeys: ['appid', 'uuid']
}
],
};
constructor(logger: CoreLoggerProvider, protected appProvider: CoreAppProvider, private initDelegate: CoreInitDelegate,
protected pushNotificationsDelegate: AddonPushNotificationsDelegate, protected sitesProvider: CoreSitesProvider,
private badge: Badge, private localNotificationsProvider: CoreLocalNotificationsProvider,
private utils: CoreUtilsProvider, private textUtils: CoreTextUtilsProvider, private push: Push,
private configProvider: CoreConfigProvider, private device: Device, private zone: NgZone) {
private configProvider: CoreConfigProvider, private device: Device, private zone: NgZone,
private translate: TranslateService, private platform: Platform, private sitesFactory: CoreSitesFactoryProvider) {
this.logger = logger.getInstance('AddonPushNotificationsProvider');
this.appDB = appProvider.getDB();
this.appDB.createTablesFromSchema(this.tablesSchema);
this.appDB.createTablesFromSchema(this.appTablesSchema);
this.sitesProvider.registerSiteSchema(this.siteSchema);
platform.ready().then(() => {
// Create the default channel.
this.createDefaultChannel();
translate.onLangChange.subscribe((event: any) => {
// Update the channel name.
this.createDefaultChannel();
});
});
}
/**
@ -85,6 +212,25 @@ export class AddonPushNotificationsProvider {
});
}
/**
* Create the default push channel. It is used to change the name.
*
* @return {Promise<any>} Promise resolved when done.
*/
protected createDefaultChannel(): Promise<any> {
if (!this.platform.is('android')) {
return Promise.resolve();
}
return this.push.createChannel({
id: 'PushPluginChannel',
description: this.translate.instant('core.misc'),
importance: 4
}).catch((error) => {
this.logger.error('Error changing push channel name', error);
});
}
/**
* Returns options for push notifications based on device.
*
@ -94,7 +240,6 @@ export class AddonPushNotificationsProvider {
return this.configProvider.get(CoreConstants.SETTINGS_NOTIFICATION_SOUND, true).then((soundEnabled) => {
return {
android: {
senderID: CoreConfigConstants.gcmpn,
sound: !!soundEnabled,
icon: 'smallicon'
},
@ -119,6 +264,23 @@ export class AddonPushNotificationsProvider {
return this.pushID;
}
/**
* Get data to register the device in Moodle.
*
* @return {AddonPushNotificationsRegisterData} Data.
*/
protected getRegisterData(): AddonPushNotificationsRegisterData {
return {
appid: CoreConfigConstants.app_id,
name: this.device.manufacturer || '',
model: this.device.model,
platform: this.device.platform + '-fcm',
version: this.device.version,
pushid: this.pushID,
uuid: this.device.uuid
};
}
/**
* Get Sitebadge counter from the database.
*
@ -156,13 +318,7 @@ export class AddonPushNotificationsProvider {
if (this.localNotificationsProvider.isAvailable()) {
const localNotif: ILocalNotification = {
id: 1,
trigger: {
at: new Date()
},
data: {
notif: data.notif,
site: data.site
},
data: data,
title: '',
text: ''
},
@ -194,9 +350,6 @@ export class AddonPushNotificationsProvider {
});
} else {
// The notification was clicked.
// For compatibility with old push plugin implementation we'll merge all the notification data in a single object.
data.title = notification.title;
data.message = notification.message;
this.notificationClicked(data);
}
});
@ -205,10 +358,10 @@ export class AddonPushNotificationsProvider {
/**
* Unregisters a device from a certain Moodle site.
*
* @param {any} site Site to unregister from.
* @return {Promise<any>} Promise resolved when device is unregistered.
* @param {CoreSite} site Site to unregister from.
* @return {Promise<any>} Promise resolved when device is unregistered.
*/
unregisterDeviceOnMoodle(site: any): Promise<any> {
unregisterDeviceOnMoodle(site: CoreSite): Promise<any> {
if (!site || !this.appProvider.isMobile()) {
return Promise.reject(null);
}
@ -224,6 +377,34 @@ export class AddonPushNotificationsProvider {
if (!response || !response.removed) {
return Promise.reject(null);
}
const promises = [];
// Remove the device from the local DB.
promises.push(site.getDb().deleteRecords(AddonPushNotificationsProvider.REGISTERED_DEVICES_TABLE,
this.getRegisterData()));
// Remove pending unregisters for this site.
promises.push(this.appDB.deleteRecords(AddonPushNotificationsProvider.PENDING_UNREGISTER_TABLE, {siteid: site.id}));
return Promise.all(promises).catch(() => {
// Ignore errors.
});
}).catch((error) => {
if (this.utils.isWebServiceError(error)) {
// It's a WebService error, can't unregister.
return Promise.reject(error);
}
// Store the pending unregister so it's retried again later.
return this.appDB.insertRecord(AddonPushNotificationsProvider.PENDING_UNREGISTER_TABLE, {
siteid: site.id,
siteurl: site.getURL(),
token: site.getToken(),
info: JSON.stringify(site.getInfo())
}).then(() => {
return Promise.reject(error);
});
});
}
@ -366,28 +547,58 @@ export class AddonPushNotificationsProvider {
}
/**
* Registers a device on current Moodle site.
* Registers a device on a Moodle site if needed.
*
* @return {Promise<any>} Promise resolved when device is registered.
* @param {string} [siteId] Site ID. If not defined, current site.
* @param {boolean} [forceUnregister] Whether to force unregister and register.
* @return {Promise<any>} Promise resolved when device is registered.
*/
registerDeviceOnMoodle(): Promise<any> {
registerDeviceOnMoodle(siteId?: string, forceUnregister?: boolean): Promise<any> {
this.logger.debug('Register device on Moodle.');
if (!this.sitesProvider.isLoggedIn() || !this.pushID || !this.appProvider.isMobile()) {
if (!this.pushID || !this.appProvider.isMobile()) {
return Promise.reject(null);
}
const data = {
appid: CoreConfigConstants.app_id,
name: this.device.manufacturer || '',
model: this.device.model,
platform: this.device.platform,
version: this.device.version,
pushid: this.pushID,
uuid: this.device.uuid
};
const data = this.getRegisterData();
let result,
site: CoreSite;
return this.sitesProvider.getCurrentSite().write('core_user_add_user_device', data);
return this.sitesProvider.getSite(siteId).then((s) => {
site = s;
if (forceUnregister) {
return {unregister: true, register: true};
} else {
// Check if the device is already registered.
return this.shouldRegister(data, site);
}
}).then((res) => {
result = res;
if (result.unregister) {
// Unregister the device first.
return this.unregisterDeviceOnMoodle(site).catch(() => {
// Ignore errors.
});
}
}).then(() => {
if (result.register) {
// Now register the device.
return site.write('core_user_add_user_device', this.utils.clone(data)).then((response) => {
// Insert the device in the local DB.
return site.getDb().insertRecord(AddonPushNotificationsProvider.REGISTERED_DEVICES_TABLE, data)
.catch((error) => {
// Ignore errors.
});
});
}
}).finally(() => {
// Remove pending unregisters for this site.
this.appDB.deleteRecords(AddonPushNotificationsProvider.PENDING_UNREGISTER_TABLE, {siteid: site.id}).catch(() => {
// Ignore errors.
});
});
}
/**
@ -405,6 +616,38 @@ export class AddonPushNotificationsProvider {
});
}
/**
* Retry pending unregisters.
*
* @param {string} [siteId] If defined, retry only for that site if needed. Otherwise, retry all pending unregisters.
* @return {Promise<any>} Promise resolved when done.
*/
retryUnregisters(siteId?: string): Promise<any> {
let promise;
if (siteId) {
// Check if the site has a pending unregister.
promise = this.appDB.getRecords(AddonPushNotificationsProvider.REGISTERED_DEVICES_TABLE, {siteid: siteId});
} else {
// Get all pending unregisters.
promise = this.appDB.getAllRecords(AddonPushNotificationsProvider.PENDING_UNREGISTER_TABLE);
}
return promise.then((results) => {
const promises = [];
results.forEach((result) => {
// Create a temporary site to unregister.
const tmpSite = this.sitesFactory.makeSite(result.siteid, result.siteurl, result.token,
this.textUtils.parseJSON(result.info, {}));
promises.push(this.unregisterDeviceOnMoodle(tmpSite));
});
return Promise.all(promises);
});
}
/**
* Save the addon/site badgecounter on the database.
*
@ -426,4 +669,58 @@ export class AddonPushNotificationsProvider {
return value;
});
}
/**
* Check if device should be registered (and unregistered first).
*
* @param {AddonPushNotificationsRegisterData} data Data of the device.
* @param {CoreSite} site Site to use.
* @return {Promise<{register: boolean, unregister: boolean}>} Promise resolved with booleans: whether to register/unregister.
*/
protected shouldRegister(data: AddonPushNotificationsRegisterData, site: CoreSite)
: Promise<{register: boolean, unregister: boolean}> {
// Check if the device is already registered.
return site.getDb().getRecords(AddonPushNotificationsProvider.REGISTERED_DEVICES_TABLE, {
appid: data.appid,
uuid: data.uuid
}).catch(() => {
// Ignore errors.
return [];
}).then((records: AddonPushNotificationsRegisterData[]) => {
let isStored = false,
versionOrPushChanged = false;
records.forEach((record) => {
if (record.name == data.name && record.model == data.model && record.platform == data.platform) {
if (record.version == data.version && record.pushid == data.pushid) {
// The device is already stored.
isStored = true;
} else {
// The version or pushid has changed.
versionOrPushChanged = true;
}
}
});
if (isStored) {
// The device has already been registered, no need to register it again.
return {
register: false,
unregister: false
};
} else if (versionOrPushChanged) {
// This data can be updated by calling register WS, no need to call unregister.
return {
register: true,
unregister: false
};
} else {
return {
register: true,
unregister: true
};
}
});
}
}

View File

@ -0,0 +1,71 @@
// (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 { CoreCronHandler } from '@providers/cron';
import { AddonPushNotificationsProvider } from './pushnotifications';
/**
* Cron handler to force a register on a Moodle site when a site is manually synchronized.
*/
@Injectable()
export class AddonPushNotificationsRegisterCronHandler implements CoreCronHandler {
name = 'AddonPushNotificationsRegisterCronHandler';
constructor(private pushNotificationsProvider: AddonPushNotificationsProvider) {}
/**
* 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 handler when the site is manually synchronized.
}
/**
* Execute the process.
* Receives the ID of the site affected, undefined for all sites.
*
* @param {string} [siteId] ID of the site affected, undefined for all sites.
* @return {Promise<any>} Promise resolved when done, rejected if failure.
*/
execute(siteId?: string): Promise<any> {
if (!siteId) {
// It's not a specific site, don't do anything.
return Promise.resolve();
}
// Register the device again.
return this.pushNotificationsProvider.registerDeviceOnMoodle(siteId, true);
}
/**
* Get the time between consecutive executions.
*
* @return {number} Time between consecutive executions (in ms).
*/
getInterval(): number {
return 86400000; // 1 day. We won't do anything with automatic execution, so use a big number.
}
/**
* 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 {
return false;
}
}

View File

@ -0,0 +1,47 @@
// (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 { CoreCronHandler } from '@providers/cron';
import { AddonPushNotificationsProvider } from './pushnotifications';
/**
* Cron handler to retry pending unregisters.
*/
@Injectable()
export class AddonPushNotificationsUnregisterCronHandler implements CoreCronHandler {
name = 'AddonPushNotificationsUnregisterCronHandler';
constructor(private pushNotificationsProvider: AddonPushNotificationsProvider) {}
/**
* Execute the process.
* Receives the ID of the site affected, undefined for all sites.
*
* @param {string} [siteId] ID of the site affected, undefined for all sites.
* @return {Promise<any>} Promise resolved when done, rejected if failure.
*/
execute(siteId?: string): Promise<any> {
return this.pushNotificationsProvider.retryUnregisters(siteId);
}
/**
* Get the time between consecutive executions.
*
* @return {number} Time between consecutive executions (in ms).
*/
getInterval(): number {
return 300000;
}
}

View File

@ -16,6 +16,9 @@ import { NgModule } from '@angular/core';
import { Platform } from 'ionic-angular';
import { AddonPushNotificationsProvider } from './providers/pushnotifications';
import { AddonPushNotificationsDelegate } from './providers/delegate';
import { AddonPushNotificationsRegisterCronHandler } from './providers/register-cron-handler';
import { AddonPushNotificationsUnregisterCronHandler } from './providers/unregister-cron-handler';
import { CoreCronDelegate } from '@providers/cron';
import { CoreEventsProvider } from '@providers/events';
import { CoreLoggerProvider } from '@providers/logger';
import { CoreLocalNotificationsProvider } from '@providers/local-notifications';
@ -34,16 +37,24 @@ export const ADDON_PUSHNOTIFICATIONS_PROVIDERS: any[] = [
],
providers: [
AddonPushNotificationsProvider,
AddonPushNotificationsDelegate
AddonPushNotificationsDelegate,
AddonPushNotificationsRegisterCronHandler,
AddonPushNotificationsUnregisterCronHandler
]
})
export class AddonPushNotificationsModule {
constructor(platform: Platform, pushNotificationsProvider: AddonPushNotificationsProvider, eventsProvider: CoreEventsProvider,
localNotificationsProvider: CoreLocalNotificationsProvider, loggerProvider: CoreLoggerProvider,
updateManager: CoreUpdateManagerProvider) {
updateManager: CoreUpdateManagerProvider, cronDelegate: CoreCronDelegate,
registerCronHandler: AddonPushNotificationsRegisterCronHandler,
unregisterCronHandler: AddonPushNotificationsUnregisterCronHandler) {
const logger = loggerProvider.getInstance('AddonPushNotificationsModule');
// Register the handlers.
cronDelegate.register(registerCronHandler);
cronDelegate.register(unregisterCronHandler);
// Register device on GCM or APNS server.
platform.ready().then(() => {
pushNotificationsProvider.registerDevice();

View File

@ -68,6 +68,7 @@
"addon.blog.siteblogheading": "Site blog",
"addon.calendar.calendar": "Calendar",
"addon.calendar.calendarevents": "Calendar events",
"addon.calendar.calendarreminders": "Calendar reminders",
"addon.calendar.defaultnotificationtime": "Default notification time",
"addon.calendar.errorloadevent": "Error loading event.",
"addon.calendar.errorloadevents": "Error loading events.",
@ -1508,6 +1509,7 @@
"core.maxsizeandattachments": "Maximum size for new files: {{$a.size}}, maximum attachments: {{$a.attachments}}",
"core.min": "min",
"core.mins": "mins",
"core.misc": "Miscellaneous",
"core.mod_assign": "Assignment",
"core.mod_assignment": "Assignment 2.2 (Disabled)",
"core.mod_book": "Book",
@ -1660,6 +1662,7 @@
"core.settings.navigatoruseragent": "Navigator userAgent",
"core.settings.networkstatus": "Internet connection status",
"core.settings.privacypolicy": "Privacy policy",
"core.settings.pushid": "Push notifications ID",
"core.settings.reportinbackground": "Report errors automatically",
"core.settings.settings": "Settings",
"core.settings.showdownloadoptions": "Show download options",

View File

@ -64,7 +64,6 @@
"password": "moodle"
}
},
"gcmpn": "694767596569",
"customurlscheme": "moodlemobile",
"siteurl": "",
"multisitesdisplay": "",

View File

@ -939,6 +939,8 @@ export class LocalNotificationsMock extends LocalNotifications {
/**
* Schedules or updates a single or multiple notifications.
* We only support using the "at" property to trigger the notification. Other properties like "in" or "every"
* aren't supported yet.
*
* @param {ILocalNotification | Array<ILocalNotification>} [options] Notification or notifications.
* @param {string} [eventName] Name of the event: schedule or update.
@ -978,15 +980,6 @@ export class LocalNotificationsMock extends LocalNotifications {
// Launch the trigger event.
this.fireEvent('trigger', notification);
if (notification.trigger.every && this.scheduled[notification.id] &&
!this.scheduled[notification.id].interval) {
const interval = this.parseInterval(notification.trigger.every);
if (interval > 0) {
this.scheduled[notification.id].interval = setInterval(trigger, interval);
}
}
};
this.scheduled[notification.id].timeout = setTimeout(trigger, toTriggerTime);

View File

@ -40,6 +40,7 @@
"navigatoruseragent": "Navigator userAgent",
"networkstatus": "Internet connection status",
"privacypolicy": "Privacy policy",
"pushid": "Push notifications ID",
"reportinbackground": "Report errors automatically",
"settings": "Settings",
"showdownloadoptions": "Show download options",

View File

@ -106,6 +106,10 @@
<h2>{{ 'core.settings.cordovadeviceuuid' | translate}}</h2>
<p>{{ device.uuid }}</p>
</ion-item>
<ion-item text-wrap *ngIf="pushId">
<h2>{{ 'core.settings.pushid' | translate}}</h2>
<p>{{ pushId }}</p>
</ion-item>
<ion-item text-wrap *ngIf="localNotifAvailable">
<h2>{{ 'core.settings.localnotifavailable' | translate}}</h2>
<p>{{ localNotifAvailable | translate }}</p>

View File

@ -22,6 +22,7 @@ import { CoreLangProvider } from '@providers/lang';
import { CoreLocalNotificationsProvider } from '@providers/local-notifications';
import { CoreSitesProvider } from '@providers/sites';
import { CoreConfigConstants } from '../../../../configconstants';
import { AddonPushNotificationsProvider } from '@addon/pushnotifications/providers/pushnotifications';
/**
* Page that displays the about settings.
@ -53,10 +54,11 @@ export class CoreSettingsAboutPage {
fsClickable: boolean;
storageType: string;
localNotifAvailable: string;
pushId: string;
constructor(platform: Platform, device: Device, appProvider: CoreAppProvider, fileProvider: CoreFileProvider,
initDelegate: CoreInitDelegate, langProvider: CoreLangProvider, sitesProvider: CoreSitesProvider,
localNotificationsProvider: CoreLocalNotificationsProvider) {
localNotificationsProvider: CoreLocalNotificationsProvider, pushNotificationsProvider: AddonPushNotificationsProvider) {
const currentSite = sitesProvider.getCurrentSite();
@ -111,5 +113,6 @@ export class CoreSettingsAboutPage {
}
this.localNotifAvailable = localNotificationsProvider.isAvailable() ? 'core.yes' : 'core.no';
this.pushId = pushNotificationsProvider.getPushId();
}
}

View File

@ -126,6 +126,7 @@
"maxsizeandattachments": "Maximum size for new files: {{$a.size}}, maximum attachments: {{$a.attachments}}",
"min": "min",
"mins": "mins",
"misc": "Miscellaneous",
"mod_assign": "Assignment",
"mod_assignment": "Assignment 2.2 (Disabled)",
"mod_book": "Book",

View File

@ -15,6 +15,7 @@
import { Injectable } from '@angular/core';
import { Platform, Alert, AlertController } from 'ionic-angular';
import { LocalNotifications, ILocalNotification } from '@ionic-native/local-notifications';
import { Push } from '@ionic-native/push';
import { TranslateService } from '@ngx-translate/core';
import { CoreAppProvider } from './app';
import { CoreConfigProvider } from './config';
@ -104,7 +105,7 @@ export class CoreLocalNotificationsProvider {
constructor(logger: CoreLoggerProvider, private localNotifications: LocalNotifications, private platform: Platform,
private appProvider: CoreAppProvider, private utils: CoreUtilsProvider, private configProvider: CoreConfigProvider,
private textUtils: CoreTextUtilsProvider, private translate: TranslateService, private alertCtrl: AlertController,
eventsProvider: CoreEventsProvider) {
eventsProvider: CoreEventsProvider, private push: Push) {
this.logger = logger.getInstance('CoreLocalNotificationsProvider');
this.appDB = appProvider.getDB();
@ -122,6 +123,14 @@ export class CoreLocalNotificationsProvider {
this.notifyClick(notification.data);
}
});
// Create the default channel for local notifications.
this.createDefaultChannel();
translate.onLangChange.subscribe((event: any) => {
// Update the channel name.
this.createDefaultChannel();
});
});
eventsProvider.on(CoreEventsProvider.SITE_DELETED, (site) => {
@ -176,6 +185,25 @@ export class CoreLocalNotificationsProvider {
});
}
/**
* Create the default channel. It is used to change the name.
*
* @return {Promise<any>} Promise resolved when done.
*/
protected createDefaultChannel(): Promise<any> {
if (!this.platform.is('android')) {
return Promise.resolve();
}
return this.push.createChannel({
id: 'default-channel-id',
description: this.translate.instant('addon.calendar.calendarreminders'),
importance: 4
}).catch((error) => {
this.logger.error('Error changing channel name', error);
});
}
/**
* Get a code to create unique notifications. If there's no code assigned, create a new one.
*
@ -269,7 +297,8 @@ export class CoreLocalNotificationsProvider {
isAvailable(): boolean {
const win = <any> window;
return this.appProvider.isDesktop() || !!(win.plugin && win.plugin.notification && win.plugin.notification.local);
return this.appProvider.isDesktop() || !!(win.cordova && win.cordova.plugins && win.cordova.plugins.notification &&
win.cordova.plugins.notification.local);
}
/**
@ -482,6 +511,8 @@ export class CoreLocalNotificationsProvider {
delete notification.sound; // Use default value.
}
notification.foreground = true;
// Remove from triggered, since the notification could be in there with a different time.
this.removeTriggered(notification.id);
this.localNotifications.schedule(notification);