From bdffb24c505b30a2b598a8a1acfa703495b93910 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Thu, 26 Oct 2017 15:03:08 +0200 Subject: [PATCH] MOBILE-2261 core: Implement App provider --- package-lock.json | 85 ++++++++++++ package.json | 8 ++ src/app/app.module.ts | 60 ++++++--- src/app/main.ts | 4 + src/providers/app.ts | 303 ++++++++++++++++++++++++++++++++++++++++++ src/providers/db.ts | 2 +- 6 files changed, 442 insertions(+), 20 deletions(-) create mode 100644 src/providers/app.ts diff --git a/package-lock.json b/package-lock.json index e512e2b34..33acaa63c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -59,6 +59,16 @@ "resolved": "https://registry.npmjs.org/@ionic-native/core/-/core-4.3.0.tgz", "integrity": "sha512-Pf0qCzqlVFmIpZpvo35Kl0e+1K8GUgPMcKBnN57gWh+5Ecj3dPcb+MbP4murJo/dnFsIYPYdXRZRf74hjo6gtw==" }, + "@ionic-native/keyboard": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@ionic-native/keyboard/-/keyboard-4.3.2.tgz", + "integrity": "sha512-iTvFCONbE26+dXNdJAHmNxkfLbtN+CmiuJQUy56kvjzVvOTFI5gIZ0aE5nlbYpB1GY4DFQVw/+E+xc0Bpw5mKw==" + }, + "@ionic-native/network": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@ionic-native/network/-/network-4.3.2.tgz", + "integrity": "sha512-X8RXbOIwz3TGBsEmRjMRdo+y4VjBCkm69tHhWFPExtHWyl8fOGqXcNB2UX+R3dnEi1B95svr7TEi34dmQzEqYA==" + }, "@ionic-native/splash-screen": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/@ionic-native/splash-screen/-/splash-screen-4.3.0.tgz", @@ -85,11 +95,36 @@ "resolved": "https://registry.npmjs.org/@ionic/storage/-/storage-2.0.1.tgz", "integrity": "sha1-uxqMJ2AH0AjXrN2kJrVgZbD9PAs=" }, + "@ngx-translate/core": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@ngx-translate/core/-/core-8.0.0.tgz", + "integrity": "sha1-dR/WtRLYDzp0jS3o38lt/vopr+A=" + }, + "@ngx-translate/http-loader": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@ngx-translate/http-loader/-/http-loader-2.0.0.tgz", + "integrity": "sha1-nBbQfNBwxnraJwoulAKB64JrP0M=" + }, + "@types/cordova": { + "version": "0.0.34", + "resolved": "https://registry.npmjs.org/@types/cordova/-/cordova-0.0.34.tgz", + "integrity": "sha1-6nrd907Ow9dimCegw54smt3HPQQ=" + }, + "@types/cordova-plugin-network-information": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@types/cordova-plugin-network-information/-/cordova-plugin-network-information-0.0.3.tgz", + "integrity": "sha1-+iycaufkxX8Tt39pXaTtuzr6oBY=" + }, "@types/localforage": { "version": "0.0.30", "resolved": "https://registry.npmjs.org/@types/localforage/-/localforage-0.0.30.tgz", "integrity": "sha1-PWCmv23aOOP4pGlhFZg3nx9klQk=" }, + "@types/promise.prototype.finally": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@types/promise.prototype.finally/-/promise.prototype.finally-2.0.2.tgz", + "integrity": "sha512-Fs99h+iFQZ4ZY2vO3+uJCrx+5KQnJ4FPerZ3oT/1L5aA7vnmK/d7Z/Ml1yHtNCh9UQcjFTR4Xo/Jss2f39Fgtw==" + }, "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -826,6 +861,11 @@ "integrity": "sha1-LO8fER4cV4cNi7uK8mUOWHzS9bQ=", "dev": true }, + "define-properties": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.2.tgz", + "integrity": "sha1-g6c/L+pWmJj7c3GTyPhzyvbUXJQ=" + }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -949,6 +989,16 @@ "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", "dev": true }, + "es-abstract": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.9.0.tgz", + "integrity": "sha512-kk3IJoKo7A3pWJc0OV8yZ/VEX2oSUytfekrJiqoxBlKJMFAJVJVpGdHClCCTdv+Fn2zHfpDHHIelMFhZVfef3Q==" + }, + "es-to-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.1.1.tgz", + "integrity": "sha1-RTVSSKiJeQNLZ5Lhm7gfK3l13Q0=" + }, "es3ify": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/es3ify/-/es3ify-0.1.4.tgz", @@ -1881,6 +1931,11 @@ "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=", "dev": true }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, "fwd-stream": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/fwd-stream/-/fwd-stream-1.0.4.tgz", @@ -1971,6 +2026,11 @@ "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", "dev": true }, + "has": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.1.tgz", + "integrity": "sha1-hGFzP1OLCDfJNh45qauelwTcLyg=" + }, "has-ansi": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", @@ -2158,6 +2218,16 @@ "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", "dev": true }, + "is-callable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.3.tgz", + "integrity": "sha1-hut1OSgF3cM69xySoO7fdO52BLI=" + }, + "is-date-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=" + }, "is-dotfile": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", @@ -2230,12 +2300,22 @@ "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=", "dev": true }, + "is-regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", + "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=" + }, "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", "dev": true }, + "is-symbol": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.1.tgz", + "integrity": "sha1-PMWfAAJRlLarLjjbrmaJJWtmBXI=" + }, "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", @@ -3136,6 +3216,11 @@ "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", "dev": true }, + "promise.prototype.finally": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/promise.prototype.finally/-/promise.prototype.finally-3.0.1.tgz", + "integrity": "sha512-5o2ue0smn+Wn6tdkqN4wxB27lCu84UrladpMRQ3fm1tEkewOrhAGSOHwj15SRU+oE/ParFTou6XG3Jhgyb9sjw==" + }, "proxy-addr": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-1.1.5.tgz", diff --git a/package.json b/package.json index 07810796c..89e9c0cf8 100644 --- a/package.json +++ b/package.json @@ -34,15 +34,23 @@ "@angular/platform-browser": "4.4.3", "@angular/platform-browser-dynamic": "4.4.3", "@ionic-native/core": "4.3.0", + "@ionic-native/keyboard": "^4.3.2", + "@ionic-native/network": "^4.3.2", "@ionic-native/splash-screen": "4.3.0", "@ionic-native/sqlite": "^4.3.2", "@ionic-native/status-bar": "4.3.0", "@ionic/storage": "2.0.1", + "@ngx-translate/core": "^8.0.0", + "@ngx-translate/http-loader": "^2.0.0", + "@types/cordova": "0.0.34", + "@types/cordova-plugin-network-information": "0.0.3", + "@types/promise.prototype.finally": "^2.0.2", "electron-builder-squirrel-windows": "^19.3.0", "electron-windows-notifications": "^1.1.13", "ionic-angular": "3.7.1", "ionicons": "3.0.0", "moment": "^2.19.1", + "promise.prototype.finally": "^3.0.1", "rxjs": "5.4.3", "sw-toolbox": "3.6.0", "zone.js": "0.8.18" diff --git a/src/app/app.module.ts b/src/app/app.module.ts index ba1bccd2f..12655b0e0 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -1,33 +1,55 @@ import { BrowserModule } from '@angular/platform-browser'; import { ErrorHandler, NgModule } from '@angular/core'; import { IonicApp, IonicErrorHandler, IonicModule } from 'ionic-angular'; +import { HttpClient } from '@angular/common/http'; + import { SplashScreen } from '@ionic-native/splash-screen'; import { StatusBar } from '@ionic-native/status-bar'; import { SQLite } from '@ionic-native/sqlite'; +import { Keyboard } from '@ionic-native/keyboard'; +import { Network } from '@ionic-native/network'; +import { TranslateModule, TranslateLoader } from '@ngx-translate/core'; +import { TranslateHttpLoader } from '@ngx-translate/http-loader'; import { MyApp } from './app.component'; import { CoreLoggerProvider } from '../providers/logger'; import { CoreDbProvider } from '../providers/db'; +import { CoreAppProvider } from '../providers/app'; + +// For translate loader. AoT requires an exported function for factories. +export function createTranslateLoader(http: HttpClient) { + return new TranslateHttpLoader(http, './build/lang/', '.json'); +} @NgModule({ - declarations: [ - MyApp - ], - imports: [ - BrowserModule, - IonicModule.forRoot(MyApp) - ], - bootstrap: [IonicApp], - entryComponents: [ - MyApp - ], - providers: [ - StatusBar, - SplashScreen, - SQLite, - {provide: ErrorHandler, useClass: IonicErrorHandler}, - CoreLoggerProvider, - CoreDbProvider - ] + declarations: [ + MyApp + ], + imports: [ + BrowserModule, + IonicModule.forRoot(MyApp), + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useFactory: (createTranslateLoader), + deps: [HttpClient] + } + }) + ], + bootstrap: [IonicApp], + entryComponents: [ + MyApp + ], + providers: [ + StatusBar, + SplashScreen, + SQLite, + Keyboard, + Network, + {provide: ErrorHandler, useClass: IonicErrorHandler}, + CoreLoggerProvider, + CoreDbProvider, + CoreAppProvider, + ] }) export class AppModule {} diff --git a/src/app/main.ts b/src/app/main.ts index 6af7a5b2a..70e0b3a7a 100644 --- a/src/app/main.ts +++ b/src/app/main.ts @@ -2,4 +2,8 @@ import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { AppModule } from './app.module'; +import { shim } from 'promise.prototype.finally'; + +shim(); // Support promise.finally. + platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/src/providers/app.ts b/src/providers/app.ts new file mode 100644 index 000000000..7160bea79 --- /dev/null +++ b/src/providers/app.ts @@ -0,0 +1,303 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Injectable } from '@angular/core'; +import { Platform } from 'ionic-angular'; +import { Keyboard } from '@ionic-native/keyboard'; +import { Network } from '@ionic-native/network'; + +import { CoreDbProvider } from './db'; +import { CoreLoggerProvider } from './logger'; + +/** + * Factory to provide some global functionalities, like access to the global app database. + * @description + * Each service or component should be responsible of creating their own database tables. Example: + * + * constructor(appProvider: CoreAppProvider) { + * this.appDB = appProvider.getDB(); + * this.appDB.createTableFromSchema(this.tableSchema); + * } + */ +@Injectable() +export class CoreAppProvider { + DBNAME = 'MoodleMobile'; + db; + logger; + ssoAuthenticationPromise : Promise; + isKeyboardShown: boolean = false; + + constructor(private dbProvider: CoreDbProvider, private platform: Platform, private keyboard: Keyboard, + private network: Network, logger: CoreLoggerProvider) { + this.logger = logger.getInstance('CoreAppProvider'); + + this.keyboard.onKeyboardShow().subscribe((data) => { + this.isKeyboardShown = true; + + }); + this.keyboard.onKeyboardHide().subscribe((data) => { + this.isKeyboardShown = false; + }); + } + + /** + * Check if the browser supports mediaDevices.getUserMedia. + * + * @return {boolean} Whether the function is supported. + */ + canGetUserMedia() : boolean { + return !!(navigator && navigator.mediaDevices && navigator.mediaDevices.getUserMedia); + } + + /** + * Check if the browser supports MediaRecorder. + * + * @return {boolean} Whether the function is supported. + */ + canRecordMedia() : boolean { + return !!(window).MediaRecorder; + }; + + /** + * Closes the keyboard. + */ + closeKeyboard() : void { + if (this.isMobile()) { + this.keyboard.close(); + } + }; + + /** + * Get the application global database. + * + * @return {any} App's DB. + */ + getDB() : any { + if (typeof this.db == 'undefined') { + this.db = this.dbProvider.getDB(this.DBNAME); + } + + return this.db; + }; + + /** + * Core init process for the app. + * + * @description + * This should be the first init process of all, no other process should run until we are certain that the cordova plugins + * are loaded, which is what platform.ready tells us. + * + * Reserved for core use, do not call directly. + * + * @protected + * @return Promise resolved when ready. + */ + initProcess() : Promise { + return this.platform.ready(); + }; + + /** + * Checks if the app is running in a desktop environment (not browser). + * + * @return {boolean} Whether the app is running in a desktop environment (not browser). + */ + isDesktop() : boolean { + let process = (window).process; + return !!(process && process.versions && typeof process.versions.electron != 'undefined'); + }; + + /** + * Check if the keyboard is visible. + * + * @return {boolean} Whether keyboard is visible. + */ + isKeyboardVisible() : boolean { + return this.isKeyboardShown; + }; + + /** + * Checks if the app is running in a mobile or tablet device (Cordova). + * + * @return {boolean} Whether the app is running in a mobile or tablet device. + */ + isMobile() : boolean { + return this.platform.is('cordova'); + }; + + /** + * Returns whether we are online. + * + * @return {boolean} Whether the app is online. + */ + isOnline() : boolean { + let online = this.network.type !== null && this.network.type != Connection.NONE && this.network.type != Connection.UNKNOWN; + // Double check we are not online because we cannot rely 100% in Cordova APIs. Also, check it in browser. + if (!online && navigator.onLine) { + online = true; + } + return online; + }; + + /* + * Check if device uses a limited connection. + * + * @return {boolean} Whether the device uses a limited connection. + */ + isNetworkAccessLimited() : boolean { + let type = this.network.type; + if (type === null) { + // Plugin not defined, probably in browser. + return false; + } + + let limited = [Connection.CELL_2G, Connection.CELL_3G, Connection.CELL_4G, Connection.CELL]; + return limited.indexOf(type) > -1; + }; + + /** + * Instantly returns if the app is ready. + * + * To be notified when the app is ready, refer to {@link $mmApp#ready}. + * + * @return {Boolean} True when it is, false when not. + * @todo + */ + isReady() { + // var promise = $injector.get('$mmInitDelegate').ready(); + // return promise.$$state.status === 1; + }; + + /** + * Open the keyboard. + */ + openKeyboard() : void { + // Open keyboard is not supported in desktop and in iOS. + if (this.isMobile() && !this.platform.is('ios')) { + this.keyboard.show(); + } + }; + + /** + * Resolves when the app is ready. + * + * Usage: + * + * $mmApp.ready().then(function() { + * // What you want to do. + * }); + * + * @return {Promise} Resolved when the app is initialised. Never rejected. + * @todo + */ + ready() { + // Injects to prevent circular dependencies. + // return $injector.get('$mmInitDelegate').ready(); + }; + + /** + * Start an SSO authentication process. + * Please notice that this function should be called when the app receives the new token from the browser, + * NOT when the browser is opened. + */ + startSSOAuthentication() : void { + this.ssoAuthenticationPromise = new Promise((resolve, reject) => { + // Store the resolve function in the promise itself. + (this.ssoAuthenticationPromise).resolve = resolve; + + // Resolve it automatically after 10 seconds (it should never take that long). + let cancel = setTimeout(() => { + this.finishSSOAuthentication(); + }, 10000); + + // If the promise is resolved because finishSSOAuthentication is called, stop the cancel promise. + this.ssoAuthenticationPromise.then(() => { + clearTimeout(cancel); + }); + }); + }; + + /** + * Finish an SSO authentication process. + */ + finishSSOAuthentication() : void { + if (this.ssoAuthenticationPromise) { + (this.ssoAuthenticationPromise).resolve && (this.ssoAuthenticationPromise).resolve(); + this.ssoAuthenticationPromise = undefined; + } + }; + + /** + * Check if there's an ongoing SSO authentication process. + * + * @return {boolean} Whether there's a SSO authentication ongoing. + */ + isSSOAuthenticationOngoing() : boolean { + return !!this.ssoAuthenticationPromise; + }; + + /** + * Returns a promise that will be resolved once SSO authentication finishes. + * + * @return {Promise} Promise resolved once SSO authentication finishes. + */ + waitForSSOAuthentication() : Promise { + return this.ssoAuthenticationPromise || Promise.resolve(); + }; + + /** + * Retrieve redirect data. + * + * @return {object} Object with siteid, state, params and timemodified. + */ + getRedirect() : object { + if (localStorage && localStorage.getItem) { + try { + let data = { + siteid: localStorage.getItem('mmCoreRedirectSiteId'), + state: localStorage.getItem('mmCoreRedirectState'), + params: localStorage.getItem('mmCoreRedirectParams'), + timemodified: localStorage.getItem('mmCoreRedirectTime') + }; + + if (data.params) { + data.params = JSON.parse(data.params); + } + + return data; + } catch(ex) { + this.logger.error('Error loading redirect data:', ex); + } + } + + return {}; + }; + + /** + * Store redirect params. + * + * @param {string} siteId Site ID. + * @param {string} page Page to go. + * @param {object} params Page params. + */ + storeRedirect(siteId: string, page: string, params: object) : void { + if (localStorage && localStorage.setItem) { + try { + localStorage.setItem('mmCoreRedirectSiteId', siteId); + localStorage.setItem('mmCoreRedirectState', page); + localStorage.setItem('mmCoreRedirectParams', JSON.stringify(params)); + localStorage.setItem('mmCoreRedirectTime', String(Date.now())); + } catch(ex) {} + } + }; +} diff --git a/src/providers/db.ts b/src/providers/db.ts index 2c10e2f76..4e807a9de 100644 --- a/src/providers/db.ts +++ b/src/providers/db.ts @@ -36,7 +36,7 @@ export class CoreDbProvider { * @param {boolean} forceNew True if it should always create a new instance. * @return {SQLiteDB} DB. */ - getDB(name: string, forceNew: boolean) : SQLiteDB { + getDB(name: string, forceNew?: boolean) : SQLiteDB { if (typeof this.dbInstances[name] === 'undefined' || forceNew) { this.dbInstances[name] = new SQLiteDB(name, this.sqlite, this.platform); }