From f1bc354be60b6d57e29b182db8ccd0b8320f7934 Mon Sep 17 00:00:00 2001 From: Noel De Martin Date: Tue, 1 Dec 2020 18:35:07 +0100 Subject: [PATCH] MOBILE-3320 core: Refactor initializers --- package-lock.json | 6 + package.json | 1 + src/core/classes/application-init-status.ts | 3 +- src/core/core.module.ts | 47 +---- src/core/features/emulator/emulator.module.ts | 31 ++- .../emulator/services/emulator-helper.ts | 11 +- src/core/features/login/pages/init/init.ts | 5 +- .../features/login/tests/pages/init.test.ts | 7 +- src/core/guards/auth.ts | 4 +- src/core/initializers/clear-tmp-folder.ts | 19 ++ src/core/initializers/index.ts | 33 ++++ src/core/initializers/load-update-manager.ts | 19 ++ src/core/initializers/override-window-open.ts | 25 +++ .../initializers/prepare-automated-tests.ts | 38 ++++ src/core/initializers/restore-session.ts | 19 ++ .../subscribe-to-keyboard-events.ts | 28 +++ .../initializers/wait-for-platform-ready.ts | 19 ++ src/core/services/app.ts | 93 ++++----- src/core/services/cron.ts | 14 +- src/core/services/filepool.ts | 5 +- src/core/services/init.ts | 179 ------------------ src/core/services/update-manager.ts | 10 +- src/core/services/utils/utils.ts | 18 +- src/core/singletons/index.ts | 4 +- tsconfig.app.json | 3 +- tsconfig.json | 3 +- 26 files changed, 289 insertions(+), 355 deletions(-) create mode 100644 src/core/initializers/clear-tmp-folder.ts create mode 100644 src/core/initializers/index.ts create mode 100644 src/core/initializers/load-update-manager.ts create mode 100644 src/core/initializers/override-window-open.ts create mode 100644 src/core/initializers/prepare-automated-tests.ts create mode 100644 src/core/initializers/restore-session.ts create mode 100644 src/core/initializers/subscribe-to-keyboard-events.ts create mode 100644 src/core/initializers/wait-for-platform-ready.ts delete mode 100644 src/core/services/init.ts diff --git a/package-lock.json b/package-lock.json index cb5a21026..fdc609117 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3312,6 +3312,12 @@ "integrity": "sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw==", "dev": true }, + "@types/webpack-env": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/@types/webpack-env/-/webpack-env-1.16.0.tgz", + "integrity": "sha512-Fx+NpfOO0CpeYX2g9bkvX8O5qh9wrU1sOF4g8sft4Mu7z+qfe387YlyY8w8daDyDsKY5vUxM0yxkAYnbkRbZEw==", + "dev": true + }, "@types/webpack-sources": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-0.1.8.tgz", diff --git a/package.json b/package.json index d85aa2a26..553d0de6d 100644 --- a/package.json +++ b/package.json @@ -131,6 +131,7 @@ "@ionic/angular-toolkit": "^2.3.0", "@types/faker": "^5.1.3", "@types/node": "^12.12.64", + "@types/webpack-env": "^1.16.0", "@typescript-eslint/eslint-plugin": "4.3.0", "@typescript-eslint/parser": "4.3.0", "eslint": "^7.6.0", diff --git a/src/core/classes/application-init-status.ts b/src/core/classes/application-init-status.ts index c0e8850c8..f98420f81 100644 --- a/src/core/classes/application-init-status.ts +++ b/src/core/classes/application-init-status.ts @@ -12,9 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { ApplicationInitStatus, APP_INITIALIZER, Injector } from '@angular/core'; +import { ApplicationInitStatus, APP_INITIALIZER, Injectable, Injector } from '@angular/core'; import { setSingletonsInjector } from '@singletons'; +@Injectable() export class CoreApplicationInitStatus extends ApplicationInitStatus { constructor(injector: Injector) { diff --git a/src/core/core.module.ts b/src/core/core.module.ts index 27d39f8b2..df3bcffb0 100644 --- a/src/core/core.module.ts +++ b/src/core/core.module.ts @@ -15,18 +15,14 @@ import { HTTP_INTERCEPTORS } from '@angular/common/http'; import { ApplicationInitStatus, Injector, NgModule } from '@angular/core'; -import { Platform } from '@ionic/angular'; - import { CoreApplicationInitStatus } from './classes/application-init-status'; import { CoreFeaturesModule } from './features/features.module'; -import { CoreFile } from './services/file'; -import { CoreInit, CoreInitDelegate } from './services/init'; import { CoreInterceptor } from './classes/interceptor'; -import { CoreSites, CORE_SITE_SCHEMAS } from './services/sites'; -import { CoreUpdateManager } from './services/update-manager'; +import { CORE_SITE_SCHEMAS } from './services/sites'; import { SITE_SCHEMA as FILEPOOL_SITE_SCHEMA } from './services/db/filepool'; import { SITE_SCHEMA as SITES_SITE_SCHEMA } from './services/db/sites'; import { SITE_SCHEMA as SYNC_SITE_SCHEMA } from './services/db/sync'; +import { getInitializerProviders } from './initializers'; @NgModule({ imports: [ @@ -44,42 +40,7 @@ import { SITE_SCHEMA as SYNC_SITE_SCHEMA } from './services/db/sync'; ], multi: true, }, + ...getInitializerProviders(), ], }) -export class CoreModule { - - constructor(platform: Platform) { - // Register a handler for platform ready. - CoreInit.instance.registerProcess({ - name: 'CorePlatformReady', - priority: CoreInitDelegate.MAX_RECOMMENDED_PRIORITY + 400, - blocking: true, - load: async () => { - await platform.ready(); - }, - }); - - // Register the update manager as an init process. - CoreInit.instance.registerProcess(CoreUpdateManager.instance); - - // Restore the user's session during the init process. - CoreInit.instance.registerProcess({ - name: 'CoreRestoreSession', - priority: CoreInitDelegate.MAX_RECOMMENDED_PRIORITY + 200, - blocking: false, - load: CoreSites.instance.restoreSession.bind(CoreSites.instance), - }); - - // Register clear app tmp folder. - CoreInit.instance.registerProcess({ - name: 'CoreClearTmpFolder', - priority: CoreInitDelegate.MAX_RECOMMENDED_PRIORITY + 150, - blocking: false, - load: CoreFile.instance.clearTmpFolder.bind(CoreFile.instance), - }); - - // Execute the init processes. - CoreInit.instance.executeInitProcesses(); - } - -} +export class CoreModule {} diff --git a/src/core/features/emulator/emulator.module.ts b/src/core/features/emulator/emulator.module.ts index da27adf03..c43fa19ec 100644 --- a/src/core/features/emulator/emulator.module.ts +++ b/src/core/features/emulator/emulator.module.ts @@ -12,10 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { NgModule } from '@angular/core'; +import { APP_INITIALIZER, NgModule } from '@angular/core'; import { Platform } from '@ionic/angular'; -import { CoreInitDelegate } from '@services/init'; import { CoreEmulatorHelperProvider } from './services/emulator-helper'; import { CoreEmulatorComponentsModule } from './components/components.module'; @@ -141,20 +140,18 @@ import { ZipMock } from './services/zip'; deps: [Platform, File], useFactory: (platform: Platform, file: File): Zip => platform.is('cordova') ? new Zip() : new ZipMock(file), }, + { + provide: APP_INITIALIZER, + deps: [Platform, CoreEmulatorHelperProvider], + useFactory: (platform: Platform, helperProvider: CoreEmulatorHelperProvider) => () => { + if (platform.is('cordova')) { + return; + } + + return helperProvider.load(); + }, + multi: true, + }, ], }) -export class CoreEmulatorModule { - - constructor( - platform: Platform, - initDelegate: CoreInitDelegate, - helper: CoreEmulatorHelperProvider, - ) { - - if (!platform.is('cordova')) { - // Register an init process to load the Mocks that need it. - initDelegate.registerProcess(helper); - } - } - -} +export class CoreEmulatorModule {} diff --git a/src/core/features/emulator/services/emulator-helper.ts b/src/core/features/emulator/services/emulator-helper.ts index 3e3f97ef5..618bc896a 100644 --- a/src/core/features/emulator/services/emulator-helper.ts +++ b/src/core/features/emulator/services/emulator-helper.ts @@ -16,7 +16,6 @@ import { Injectable } from '@angular/core'; import { File } from '@ionic-native/file/ngx'; import { CoreFile } from '@services/file'; -import { CoreInitDelegate, CoreInitHandler } from '@services/init'; import { CoreUtils } from '@services/utils/utils'; import { CoreLogger } from '@singletons/logger'; import { FileMock } from './file'; @@ -25,14 +24,8 @@ import { FileTransferErrorMock } from './file-transfer'; /** * Helper service for the emulator feature. It also acts as an init handler. */ -@Injectable({ - providedIn: 'root', -}) -export class CoreEmulatorHelperProvider implements CoreInitHandler { - - name = 'CoreEmulator'; - priority = CoreInitDelegate.MAX_RECOMMENDED_PRIORITY + 500; - blocking = true; +@Injectable({ providedIn: 'root' }) +export class CoreEmulatorHelperProvider { protected logger: CoreLogger; diff --git a/src/core/features/login/pages/init/init.ts b/src/core/features/login/pages/init/init.ts index 92688ca08..a1f726fac 100644 --- a/src/core/features/login/pages/init/init.ts +++ b/src/core/features/login/pages/init/init.ts @@ -16,8 +16,7 @@ import { Component, OnInit } from '@angular/core'; import { NavController } from '@ionic/angular'; import { CoreApp, CoreRedirectData } from '@services/app'; -import { CoreInit } from '@services/init'; -import { SplashScreen } from '@singletons'; +import { ApplicationInit, SplashScreen } from '@singletons'; import { CoreConstants } from '@/core/constants'; import { CoreSites } from '@services/sites'; import { CoreLoginHelper } from '@features/login/services/login-helper'; @@ -39,7 +38,7 @@ export class CoreLoginInitPage implements OnInit { */ async ngOnInit(): Promise { // Wait for the app to be ready. - await CoreInit.instance.ready(); + await ApplicationInit.instance.donePromise; // Check if there was a pending redirect. const redirectData = CoreApp.instance.getRedirect(); diff --git a/src/core/features/login/tests/pages/init.test.ts b/src/core/features/login/tests/pages/init.test.ts index f2c64a6c1..fd135d94c 100644 --- a/src/core/features/login/tests/pages/init.test.ts +++ b/src/core/features/login/tests/pages/init.test.ts @@ -15,10 +15,9 @@ import { NavController } from '@ionic/angular'; import { CoreApp } from '@services/app'; -import { CoreInit } from '@services/init'; import { CoreLoginInitPage } from '@features/login/pages/init/init'; import { CoreSites } from '@services/sites'; -import { SplashScreen } from '@singletons'; +import { ApplicationInit, SplashScreen } from '@singletons'; import { mock, mockSingleton, renderComponent, RenderConfig } from '@/testing/utils'; @@ -29,7 +28,7 @@ describe('CoreLoginInitPage', () => { beforeEach(() => { mockSingleton(CoreApp, { getRedirect: () => ({}) }); - mockSingleton(CoreInit, { ready: () => Promise.resolve() }); + mockSingleton(ApplicationInit, { donePromise: Promise.resolve() }); mockSingleton(CoreSites, { isLoggedIn: () => false }); mockSingleton(SplashScreen, ['hide']); @@ -52,7 +51,7 @@ describe('CoreLoginInitPage', () => { const fixture = await renderComponent(CoreLoginInitPage, config); fixture.componentInstance.ngOnInit(); - await CoreInit.instance.ready(); + await ApplicationInit.instance.donePromise; expect(navController.navigateRoot).toHaveBeenCalledWith('/login/sites'); }); diff --git a/src/core/guards/auth.ts b/src/core/guards/auth.ts index 9319a4b36..3c90cfff4 100644 --- a/src/core/guards/auth.ts +++ b/src/core/guards/auth.ts @@ -15,8 +15,8 @@ import { Injectable } from '@angular/core'; import { Router, CanLoad, CanActivate, UrlTree } from '@angular/router'; -import { CoreInit } from '@services/init'; import { CoreSites } from '@services/sites'; +import { ApplicationInit } from '@singletons'; @Injectable({ providedIn: 'root' }) export class AuthGuard implements CanLoad, CanActivate { @@ -32,7 +32,7 @@ export class AuthGuard implements CanLoad, CanActivate { } private async guard(): Promise { - await CoreInit.instance.ready(); + await ApplicationInit.instance.donePromise; return CoreSites.instance.isLoggedIn() || this.router.parseUrl('/login'); } diff --git a/src/core/initializers/clear-tmp-folder.ts b/src/core/initializers/clear-tmp-folder.ts new file mode 100644 index 000000000..1d420c298 --- /dev/null +++ b/src/core/initializers/clear-tmp-folder.ts @@ -0,0 +1,19 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// 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 { CoreFile } from '@services/file'; + +export default async function(): Promise { + await CoreFile.instance.clearTmpFolder(); +} diff --git a/src/core/initializers/index.ts b/src/core/initializers/index.ts new file mode 100644 index 000000000..f9f60ea14 --- /dev/null +++ b/src/core/initializers/index.ts @@ -0,0 +1,33 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// 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 { APP_INITIALIZER, Provider } from '@angular/core'; + +export function getInitializerProviders(): Provider[] { + const context = require.context('./', false, /\.ts$/); + + return context.keys().reduce((providers, fileName) => { + const name = (fileName.match(/^(?:\.\/)?(.+)\.ts$/) || [])[1]; + + if (typeof name !== 'undefined' && name !== 'index') { + providers.push({ + provide: APP_INITIALIZER, + useValue: context(fileName).default, + multi: true, + }); + } + + return providers; + }, [] as Provider[]); +} diff --git a/src/core/initializers/load-update-manager.ts b/src/core/initializers/load-update-manager.ts new file mode 100644 index 000000000..d5a530d2b --- /dev/null +++ b/src/core/initializers/load-update-manager.ts @@ -0,0 +1,19 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// 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 { CoreUpdateManager } from '@services/update-manager'; + +export default async function(): Promise { + await CoreUpdateManager.instance.load(); +} diff --git a/src/core/initializers/override-window-open.ts b/src/core/initializers/override-window-open.ts new file mode 100644 index 000000000..78e7c0bf5 --- /dev/null +++ b/src/core/initializers/override-window-open.ts @@ -0,0 +1,25 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// 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 { Platform } from '@singletons'; + +export default async function(): Promise { + await Platform.instance.ready(); + + if (!window.cordova?.InAppBrowser) { + return; + } + + window.open = window.cordova.InAppBrowser.open; +} diff --git a/src/core/initializers/prepare-automated-tests.ts b/src/core/initializers/prepare-automated-tests.ts new file mode 100644 index 000000000..68b3298f5 --- /dev/null +++ b/src/core/initializers/prepare-automated-tests.ts @@ -0,0 +1,38 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// 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 { ApplicationRef } from '@angular/core'; +import { CoreApp, CoreAppProvider } from '@services/app'; +import { CoreCron, CoreCronDelegate } from '@services/cron'; +import { Application } from '@singletons'; + +type AutomatedTestsWindow = Window & { + appRef?: ApplicationRef; + appProvider?: CoreAppProvider; + cronProvider?: CoreCronDelegate; +}; + +function initializeAutomatedTestsWindow(window: AutomatedTestsWindow) { + window.appRef = Application.instance; + window.appProvider = CoreApp.instance; + window.cronProvider = CoreCron.instance; +} + +export default function(): void { + if (!CoreAppProvider.isAutomated()) { + return; + } + + initializeAutomatedTestsWindow(window); +} diff --git a/src/core/initializers/restore-session.ts b/src/core/initializers/restore-session.ts new file mode 100644 index 000000000..55a6b1ad7 --- /dev/null +++ b/src/core/initializers/restore-session.ts @@ -0,0 +1,19 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// 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 { CoreSites } from '@services/sites'; + +export default async function(): Promise { + await CoreSites.instance.restoreSession(); +} diff --git a/src/core/initializers/subscribe-to-keyboard-events.ts b/src/core/initializers/subscribe-to-keyboard-events.ts new file mode 100644 index 000000000..9bb7deb14 --- /dev/null +++ b/src/core/initializers/subscribe-to-keyboard-events.ts @@ -0,0 +1,28 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// 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 { CoreApp } from '@services/app'; +import { NgZone, Keyboard } from '@singletons'; + +export default function(): void { + const zone = NgZone.instance; + const app = CoreApp.instance; + const keyboard = Keyboard.instance; + + // Execute callbacks in the Angular zone, so change detection doesn't stop working. + keyboard.onKeyboardShow().subscribe(data => zone.run(() => app.onKeyboardShow(data.keyboardHeight))); + keyboard.onKeyboardHide().subscribe(() => zone.run(() => app.onKeyboardHide())); + keyboard.onKeyboardWillShow().subscribe(() => zone.run(() => app.onKeyboardWillShow())); + keyboard.onKeyboardWillHide().subscribe(() => zone.run(() => app.onKeyboardWillHide())); +} diff --git a/src/core/initializers/wait-for-platform-ready.ts b/src/core/initializers/wait-for-platform-ready.ts new file mode 100644 index 000000000..d0ad780c2 --- /dev/null +++ b/src/core/initializers/wait-for-platform-ready.ts @@ -0,0 +1,19 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// 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 { Platform } from '@singletons'; + +export default async function(): Promise { + await Platform.instance.ready(); +} diff --git a/src/core/services/app.ts b/src/core/services/app.ts index 4c334be1c..f6ea7a4fb 100644 --- a/src/core/services/app.ts +++ b/src/core/services/app.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Injectable, NgZone, ApplicationRef } from '@angular/core'; +import { Injectable } from '@angular/core'; import { Params, Router } from '@angular/router'; import { Connection } from '@ionic-native/network/ngx'; @@ -57,60 +57,17 @@ export class CoreAppProvider { // Variables for DB. protected createVersionsTableReady: Promise; - constructor( - appRef: ApplicationRef, - zone: NgZone, - protected router: Router, - ) { + constructor(protected router: Router) { this.logger = CoreLogger.getInstance('CoreAppProvider'); this.db = CoreDB.instance.getDB(DBNAME); // Create the schema versions table. this.createVersionsTableReady = this.db.createTableFromSchema(SCHEMA_VERSIONS_TABLE_SCHEMA); - Keyboard.instance.onKeyboardShow().subscribe((data) => { - // Execute the callback in the Angular zone, so change detection doesn't stop working. - zone.run(() => { - document.body.classList.add('keyboard-is-open'); - this.setKeyboardShown(true); - // Error on iOS calculating size. - // More info: https://github.com/ionic-team/ionic-plugin-keyboard/issues/276 . - CoreEvents.trigger(CoreEvents.KEYBOARD_CHANGE, data.keyboardHeight); - }); - }); - Keyboard.instance.onKeyboardHide().subscribe(() => { - // Execute the callback in the Angular zone, so change detection doesn't stop working. - zone.run(() => { - document.body.classList.remove('keyboard-is-open'); - this.setKeyboardShown(false); - CoreEvents.trigger(CoreEvents.KEYBOARD_CHANGE, 0); - }); - }); - Keyboard.instance.onKeyboardWillShow().subscribe(() => { - // Execute the callback in the Angular zone, so change detection doesn't stop working. - zone.run(() => { - this.keyboardOpening = true; - this.keyboardClosing = false; - }); - }); - Keyboard.instance.onKeyboardWillHide().subscribe(() => { - // Execute the callback in the Angular zone, so change detection doesn't stop working. - zone.run(() => { - this.keyboardOpening = false; - this.keyboardClosing = true; - }); - }); - // @todo // this.platform.registerBackButtonAction(() => { // this.backButtonAction(); // }, 100); - - // Export the app provider and appRef to control the application in Behat tests. - if (CoreAppProvider.isAutomated()) { - ( window).appProvider = this; - ( window).appRef = appRef; - } } /** @@ -429,6 +386,44 @@ export class CoreAppProvider { } } + /** + * Notify that Keyboard has been shown. + * + * @param keyboardHeight Keyboard height. + */ + onKeyboardShow(keyboardHeight: number): void { + document.body.classList.add('keyboard-is-open'); + this.setKeyboardShown(true); + // Error on iOS calculating size. + // More info: https://github.com/ionic-team/ionic-plugin-keyboard/issues/276 . + CoreEvents.trigger(CoreEvents.KEYBOARD_CHANGE, keyboardHeight); + } + + /** + * Notify that Keyboard has been hidden. + */ + onKeyboardHide(): void { + document.body.classList.remove('keyboard-is-open'); + this.setKeyboardShown(false); + CoreEvents.trigger(CoreEvents.KEYBOARD_CHANGE, 0); + } + + /** + * Notify that Keyboard is about to be shown. + */ + onKeyboardWillShow(): void { + this.keyboardOpening = true; + this.keyboardClosing = false; + } + + /** + * Notify that Keyboard is about to be hidden. + */ + onKeyboardWillHide(): void { + this.keyboardOpening = false; + this.keyboardClosing = true; + } + /** * Set keyboard shown or hidden. * @@ -735,11 +730,3 @@ export type CoreAppSchema = { */ migrate?(db: SQLiteDB, oldVersion: number): Promise; }; - -/** - * Extended window type for automated tests. - */ -export type WindowForAutomatedTests = Window & { - appProvider?: CoreAppProvider; - appRef?: ApplicationRef; -}; diff --git a/src/core/services/cron.ts b/src/core/services/cron.ts index 8cc5ec81e..705b80cd7 100644 --- a/src/core/services/cron.ts +++ b/src/core/services/cron.ts @@ -14,7 +14,7 @@ import { Injectable, NgZone } from '@angular/core'; -import { CoreApp, CoreAppProvider } from '@services/app'; +import { CoreApp } from '@services/app'; import { CoreConfig } from '@services/config'; import { CoreUtils } from '@services/utils/utils'; import { CoreConstants } from '@/core/constants'; @@ -57,11 +57,6 @@ export class CoreCronDelegate { this.startNetworkHandlers(); }); }); - - // Export the sync provider so Behat tests can trigger cron tasks without waiting. - if (CoreAppProvider.isAutomated()) { - ( window).cronProvider = this; - } } /** @@ -532,10 +527,3 @@ export interface CoreCronHandler { */ execute?(siteId?: string, force?: boolean): Promise; } - -/** - * Extended window type for automated tests. - */ -export type WindowForAutomatedTests = Window & { - cronProvider?: CoreCronDelegate; -}; diff --git a/src/core/services/filepool.ts b/src/core/services/filepool.ts index 28b441509..9ab08dcb2 100644 --- a/src/core/services/filepool.ts +++ b/src/core/services/filepool.ts @@ -18,7 +18,6 @@ import { Md5 } from 'ts-md5/dist/md5'; import { CoreApp } from '@services/app'; import { CoreEvents } from '@singletons/events'; import { CoreFile } from '@services/file'; -import { CoreInit } from '@services/init'; import { CorePluginFile } from '@services/plugin-file-delegate'; import { CoreSites } from '@services/sites'; import { CoreWS, CoreWSExternalFile } from '@services/ws'; @@ -31,7 +30,7 @@ import { CoreUtils, PromiseDefer } from '@services/utils/utils'; import { SQLiteDB } from '@classes/sqlitedb'; import { CoreError } from '@classes/errors/error'; import { CoreConstants } from '@/core/constants'; -import { makeSingleton, Network, NgZone, Translate } from '@singletons'; +import { ApplicationInit, makeSingleton, Network, NgZone, Translate } from '@singletons'; import { CoreLogger } from '@singletons/logger'; import { APP_SCHEMA, @@ -108,7 +107,7 @@ export class CoreFilepoolProvider { */ protected async init(): Promise { // Waiting for the app to be ready to start processing the queue. - await CoreInit.instance.ready(); + await ApplicationInit.instance.donePromise; this.checkQueueProcessing(); diff --git a/src/core/services/init.ts b/src/core/services/init.ts deleted file mode 100644 index e391fc6f4..000000000 --- a/src/core/services/init.ts +++ /dev/null @@ -1,179 +0,0 @@ -// (C) Copyright 2015 Moodle Pty Ltd. -// -// 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 { CoreUtils, PromiseDefer, OrderedPromiseData } from '@services/utils/utils'; -import { CoreLogger } from '@singletons/logger'; -import { makeSingleton } from '@singletons'; - -/** - * Interface that all init handlers must implement. - */ -export type CoreInitHandler = { - /** - * A name to identify the handler. - */ - name: string; - - /** - * The highest priority is executed first. You should use values lower than MAX_RECOMMENDED_PRIORITY. - */ - priority?: number; - - /** - * Set this to true when this process should be resolved before any following one. - */ - blocking?: boolean; - - /** - * Function to execute during the init process. - * - * @return Promise resolved when done. - */ - load(): Promise; -}; - -/* - * Provider for initialisation mechanisms. - */ -@Injectable({ providedIn: 'root' }) -export class CoreInitDelegate { - - static readonly DEFAULT_PRIORITY = 100; // Default priority for init processes. - static readonly MAX_RECOMMENDED_PRIORITY = 600; - - protected initProcesses: { [s: string]: CoreInitHandler } = {}; - protected logger: CoreLogger; - protected readiness?: CoreInitReadinessPromiseDefer; - - constructor() { - this.logger = CoreLogger.getInstance('CoreInitDelegate'); - } - - /** - * Executes the registered init processes. - * - * Reserved for core use, do not call directly. - */ - executeInitProcesses(): void { - const ordered: CoreInitHandler[] = []; - - if (typeof this.readiness == 'undefined') { - this.initReadiness(); - } - - // Re-ordering by priority. - for (const name in this.initProcesses) { - ordered.push(this.initProcesses[name]); - } - ordered.sort((a, b) => (b.priority || 0) - (a.priority || 0)); - - const orderedPromises: OrderedPromiseData[] = ordered.map((data: CoreInitHandler) => ({ - function: this.prepareProcess.bind(this, data), - blocking: !!data.blocking, - })); - - // Execute all the processes in order to solve dependencies. - CoreUtils.instance.executeOrderedPromises(orderedPromises).finally(this.readiness!.resolve); - } - - /** - * Init the readiness promise. - */ - protected initReadiness(): void { - this.readiness = CoreUtils.instance.promiseDefer(); - - // eslint-disable-next-line promise/catch-or-return - this.readiness.promise.then(() => this.readiness!.resolved = true); - } - - /** - * Instantly returns if the app is ready. - * - * @return Whether it's ready. - */ - isReady(): boolean { - return this.readiness?.resolved || false; - } - - /** - * Convenience function to return a function that executes the process. - * - * @param data The data of the process. - * @return Promise of the process. - */ - protected async prepareProcess(data: CoreInitHandler): Promise { - this.logger.debug(`Executing init process '${data.name}'`); - - try { - await data.load(); - } catch (e) { - this.logger.error('Error while calling the init process \'' + data.name + '\'. ' + e); - } - } - - /** - * Notifies when the app is ready. This returns a promise that is resolved when the app is initialised. - * - * @return Resolved when the app is initialised. Never rejected. - */ - async ready(): Promise { - if (typeof this.readiness == 'undefined') { - // Prevent race conditions if this is called before executeInitProcesses. - this.initReadiness(); - } - - await this.readiness!.promise; - } - - /** - * Registers an initialisation process. - * - * @description - * Init processes can be used to add initialisation logic to the app. Anything that should block the user interface while - * some processes are done should be an init process. It is recommended to use a priority lower than MAX_RECOMMENDED_PRIORITY - * to make sure that your process does not happen before some essential other core processes. - * - * An init process should never change state or prompt user interaction. - * - * This delegate cannot be used by site plugins. - * - * @param instance The instance of the handler. - */ - registerProcess(handler: CoreInitHandler): void { - if (typeof handler.priority == 'undefined') { - handler.priority = CoreInitDelegate.DEFAULT_PRIORITY; - } - - if (typeof this.initProcesses[handler.name] != 'undefined') { - this.logger.log(`Process '${handler.name}' already registered.`); - - return; - } - - this.logger.log(`Registering process '${handler.name}'.`); - this.initProcesses[handler.name] = handler; - } - -} - -export class CoreInit extends makeSingleton(CoreInitDelegate) {} - -/** - * Deferred promise for init readiness. - */ -type CoreInitReadinessPromiseDefer = PromiseDefer & { - resolved?: boolean; // If true, readiness have been resolved. -}; diff --git a/src/core/services/update-manager.ts b/src/core/services/update-manager.ts index 453a79777..3963db5d4 100644 --- a/src/core/services/update-manager.ts +++ b/src/core/services/update-manager.ts @@ -15,10 +15,9 @@ import { Injectable } from '@angular/core'; import { CoreConfig } from '@services/config'; -import { CoreInitHandler, CoreInitDelegate } from '@services/init'; import { CoreConstants } from '@/core/constants'; -import { makeSingleton } from '@singletons'; import { CoreLogger } from '@singletons/logger'; +import { makeSingleton } from '@singletons'; const VERSION_APPLIED = 'version_applied'; @@ -28,12 +27,7 @@ const VERSION_APPLIED = 'version_applied'; * This service handles processes that need to be run when updating the app, like migrate Ionic 1 database data to Ionic 3. */ @Injectable({ providedIn: 'root' }) -export class CoreUpdateManagerProvider implements CoreInitHandler { - - // Data for init delegate. - name = 'CoreUpdateManager'; - priority = CoreInitDelegate.MAX_RECOMMENDED_PRIORITY + 300; - blocking = true; +export class CoreUpdateManagerProvider { protected logger: CoreLogger; diff --git a/src/core/services/utils/utils.ts b/src/core/services/utils/utils.ts index 6c376419d..b554e0999 100644 --- a/src/core/services/utils/utils.ts +++ b/src/core/services/utils/utils.ts @@ -26,9 +26,7 @@ import { CoreDomUtils } from '@services/utils/dom'; import { CoreMimetypeUtils } from '@services/utils/mimetype'; import { CoreTextUtils } from '@services/utils/text'; import { CoreWSError } from '@classes/errors/wserror'; -import { - makeSingleton, Clipboard, InAppBrowser, Platform, FileOpener, WebIntent, QRScanner, Translate, -} from '@singletons'; +import { makeSingleton, Clipboard, InAppBrowser, FileOpener, WebIntent, QRScanner, Translate } from '@singletons'; import { CoreLogger } from '@singletons/logger'; type TreeNode = T & { children: TreeNode[] }; @@ -48,9 +46,6 @@ export class CoreUtilsProvider { constructor(protected zone: NgZone) { this.logger = CoreLogger.getInstance('CoreUtilsProvider'); - - // eslint-disable-next-line promise/catch-or-return - Platform.instance.ready().then(() => this.overrideWindowOpen()); } /** @@ -1606,17 +1601,6 @@ export class CoreUtilsProvider { return new Promise(resolve => setTimeout(resolve, milliseconds)); } - /** - * Override native window.open with InAppBrowser if available. - */ - private overrideWindowOpen() { - if (!window.cordova?.InAppBrowser) { - return; - } - - window.open = window.cordova.InAppBrowser.open; - } - } export class CoreUtils extends makeSingleton(CoreUtilsProvider) {} diff --git a/src/core/singletons/index.ts b/src/core/singletons/index.ts index 61c539be1..161f310ae 100644 --- a/src/core/singletons/index.ts +++ b/src/core/singletons/index.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Injector, NgZone as NgZoneService } from '@angular/core'; +import { ApplicationRef, ApplicationInitStatus, Injector, NgZone as NgZoneService } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { @@ -111,6 +111,8 @@ export class LoadingController extends makeSingleton(LoadingControllerService) { export class ModalController extends makeSingleton(ModalControllerService) {} export class ToastController extends makeSingleton(ToastControllerService) {} export class GestureController extends makeSingleton(GestureControllerService) {} +export class ApplicationInit extends makeSingleton(ApplicationInitStatus) {} +export class Application extends makeSingleton(ApplicationRef) {} // Convert external libraries injectables. export class Translate extends makeSingleton(TranslateService) {} diff --git a/tsconfig.app.json b/tsconfig.app.json index 1ec2414f0..69cd4cf31 100644 --- a/tsconfig.app.json +++ b/tsconfig.app.json @@ -7,7 +7,8 @@ "cordova-plugin-inappbrowser", "cordova", "dom-mediacapture-record", - "node" + "node", + "webpack-env" ], "paths": { "@classes/*": ["core/classes/*"], diff --git a/tsconfig.json b/tsconfig.json index b5c383525..f94ebb411 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -26,7 +26,8 @@ "dom-mediacapture-record", "faker", "jest", - "node" + "node", + "webpack-env" ], "paths": { "@classes/*": ["core/classes/*"],