diff --git a/e2e/animation-examples/app/app.module.ts b/e2e/animation-examples/app/app.module.ts index 6b5a3249f..959a16990 100644 --- a/e2e/animation-examples/app/app.module.ts +++ b/e2e/animation-examples/app/app.module.ts @@ -1,7 +1,8 @@ import { NgModule, NO_ERRORS_SCHEMA, - NgModuleFactoryLoader + NgModuleFactoryLoader, + APP_INITIALIZER } from "@angular/core"; import { NativeScriptModule } from "@nativescript/angular"; @@ -21,6 +22,14 @@ import { QueryStaggerComponent } from "./query-stagger.component"; import { AppComponent } from "./app.component"; +export function asyncBoot(): Function { + return (): Promise => new Promise(resolve => { + setTimeout(() => { + resolve(); + }, 2000); + }) +} + @NgModule({ bootstrap: [ AppComponent, @@ -42,6 +51,16 @@ import { AppComponent } from "./app.component"; NativeScriptAnimationsModule, AppRoutingModule, ], + /** + * Uncomment to test APP_INITIALIZER + */ + // providers: [ + // { + // provide: APP_INITIALIZER, + // useFactory: asyncBoot, + // multi: true + // }, + // ], schemas: [NO_ERRORS_SCHEMA], }) export class AppModule {} diff --git a/e2e/animation-examples/app/main.ts b/e2e/animation-examples/app/main.ts index 8c0e3deb1..6d0342a02 100644 --- a/e2e/animation-examples/app/main.ts +++ b/e2e/animation-examples/app/main.ts @@ -1,10 +1,103 @@ import { platformNativeScriptDynamic } from "@nativescript/angular/platform"; import { animationsTraceCategory } from "@nativescript/angular/trace"; import { setCategories, enable } from "@nativescript/core/trace"; +import { + GridLayout, + ItemSpec, + GridUnitType, +} from '@nativescript/core/ui/layouts/grid-layout'; +import { + HorizontalAlignment, + VerticalAlignment, +} from '@nativescript/core/ui/enums/enums'; import { AppModule } from "./app.module"; setCategories(animationsTraceCategory); enable(); -platformNativeScriptDynamic().bootstrapModule(AppModule); +class LaunchAnimation extends GridLayout { + circle: GridLayout; + animatedContainer: GridLayout; + finished = false; + + constructor() { + super(); + + // setup container to house launch animation + this.animatedContainer = new GridLayout(); + this.animatedContainer.style.zIndex = 100; + this.animatedContainer.backgroundColor = '#4caef7'; + this.animatedContainer.className = 'w-full h-full'; + + // any creative animation can be put inside + this.circle = new GridLayout(); + this.circle.width = 30; + this.circle.height = 30; + this.circle.borderRadius = 15; + this.circle.horizontalAlignment = HorizontalAlignment.center; + this.circle.verticalAlignment = VerticalAlignment.center; + this.circle.backgroundColor = '#fff'; + this.animatedContainer.addRow(new ItemSpec(1, GridUnitType.STAR)); + this.animatedContainer.addRow(new ItemSpec(1, GridUnitType.AUTO)); + this.animatedContainer.addRow(new ItemSpec(1, GridUnitType.STAR)); + GridLayout.setRow(this.circle, 1); + this.animatedContainer.addChild(this.circle); + + // add animation to top row since booted app will insert into bottom row + GridLayout.setRow(this.animatedContainer, 1); + this.addChild(this.animatedContainer); + } + + startAnimation() { + this.circle + .animate({ + scale: { x: 2, y: 2 }, + duration: 800, + }) + .then(() => { + this.circle + .animate({ + scale: { x: 1, y: 1 }, + duration: 800, + }) + .then(() => { + if (this.finished) { + this.circle + .animate({ + scale: { x: 30, y: 30 }, + duration: 400, + }) + .then(() => { + this.fadeOut(); + }); + } else { + // keep looping + this.startAnimation(); + } + }); + }); + } + + cleanup() { + this.finished = true; + } + + fadeOut() { + this.animatedContainer + .animate({ + opacity: 0, + duration: 400, + }) + .then(() => { + this._removeView(this.animatedContainer); + this.animatedContainer = null; + this.circle = null; + }); + } +} + +platformNativeScriptDynamic({ + launchView: new LaunchAnimation(), + // backgroundColor: 'purple' +}).bootstrapModule(AppModule); diff --git a/nativescript-angular/platform-common.ts b/nativescript-angular/platform-common.ts index 35dc2211c..bcd329721 100644 --- a/nativescript-angular/platform-common.ts +++ b/nativescript-angular/platform-common.ts @@ -13,6 +13,7 @@ import "nativescript-intl"; import { TextView } from "@nativescript/core/ui/text-view"; import { Color, View } from "@nativescript/core/ui/core/view"; import { Frame } from "@nativescript/core/ui/frame"; +import { GridLayout } from '@nativescript/core/ui/layouts/grid-layout'; import { Type, @@ -29,8 +30,7 @@ import { import { DOCUMENT } from "@angular/common"; import { bootstrapLog, bootstrapLogError, isLogEnabled } from "./trace"; -import { defaultPageFactoryProvider, setRootPage, PageFactory, PAGE_FACTORY } from "./platform-providers"; -import { AppHostView } from "./app-host-view"; +import { defaultPageFactoryProvider, setRootPage, PageFactory, PAGE_FACTORY, getRootPage } from "./platform-providers"; import { setCssFileName, @@ -75,13 +75,18 @@ export interface HmrOptions { } // tslint:enable:max-line-length +export interface AppLaunchView extends View { + startAnimation?: () => void; + cleanup?: () => void; +} export interface AppOptions { bootInExistingPage?: boolean; cssFile?: string; startPageActionBarHidden?: boolean; - createFrameOnBootstrap?: boolean; hmrOptions?: HmrOptions; + backgroundColor?: string; + launchView?: AppLaunchView; } export type PlatformFactory = (extraProviders?: StaticProvider[]) => PlatformRef; @@ -190,81 +195,68 @@ export class NativeScriptPlatformRef extends PlatformRef { @profile private bootstrapNativeScriptApp() { - const autoCreateFrame = !!this.appOptions.createFrameOnBootstrap; let rootContent: View; + let launchView: AppLaunchView; if (isLogEnabled()) { bootstrapLog("NativeScriptPlatform bootstrap started."); } const launchCallback = profile( - "nativescript-angular/platform-common.launchCallback", + "@nativescript/angular/platform-common.launchCallback", (args: LaunchEventData) => { if (isLogEnabled()) { bootstrapLog("Application launch event fired"); } - let tempAppHostView: AppHostView; - if (autoCreateFrame) { - const { page, frame } = this.createFrameAndPage(false); - setRootPage(page); - rootContent = frame; + if (this.appOptions && this.appOptions.launchView) { + launchView = this.appOptions.launchView; + if (this.appOptions.launchView.startAnimation) { + setTimeout(() => { + // ensure launch animation is executed after launchView added to view stack + this.appOptions.launchView.startAnimation(); + }); + } } else { - // Create a temp page for root of the renderer - tempAppHostView = new AppHostView(); - setRootPage(tempAppHostView); + launchView = new GridLayout(); + // Custom launch view color (useful when doing async app intializers where you don't want a flash of undesirable color) + launchView.backgroundColor = new Color(this.appOptions && this.appOptions.backgroundColor ? this.appOptions.backgroundColor : '#fff'); } + args.root = launchView; + setRootPage(launchView); - let bootstrapPromiseCompleted = false; + // Launch Angular app this._bootstrapper().then( - moduleRef => { - bootstrapPromiseCompleted = true; - - if (isLogEnabled()) { - bootstrapLog(`Angular bootstrap bootstrap done. uptime: ${uptime()}`); - } - - if (!autoCreateFrame) { - rootContent = tempAppHostView.content; - } - - lastBootstrappedModule = new WeakRef(moduleRef); - }, - err => { - bootstrapPromiseCompleted = true; - - const errorMessage = err.message + "\n\n" + err.stack; - if (isLogEnabled()) { - bootstrapLogError("ERROR BOOTSTRAPPING ANGULAR"); - } - if (isLogEnabled()) { - bootstrapLogError(errorMessage); - } - - rootContent = this.createErrorUI(errorMessage); - } - ); - - if (isLogEnabled()) { - bootstrapLog("bootstrapAction called, draining micro tasks queue. Root: " + rootContent); - } - (global).Zone.drainMicroTaskQueue(); - if (isLogEnabled()) { - bootstrapLog("bootstrapAction called, draining micro tasks queue finished! Root: " + rootContent); - } - - if (!bootstrapPromiseCompleted) { - const errorMessage = "Bootstrap promise didn't resolve"; - if (isLogEnabled()) { - bootstrapLogError(errorMessage); - } - rootContent = this.createErrorUI(errorMessage); - } - - args.root = rootContent; + moduleRef => { + + if (isLogEnabled()) { + bootstrapLog(`Angular bootstrap bootstrap done. uptime: ${uptime()}`); + } + + rootContent = launchView; + if (launchView && launchView.cleanup) { + // cleanup any custom launch views + launchView.cleanup(); + } + + lastBootstrappedModule = new WeakRef(moduleRef); + }, + err => { + + const errorMessage = err.message + "\n\n" + err.stack; + if (isLogEnabled()) { + bootstrapLogError("ERROR BOOTSTRAPPING ANGULAR"); + } + if (isLogEnabled()) { + bootstrapLogError(errorMessage); + } + + rootContent = this.createErrorUI(errorMessage); + } + ); } ); const exitCallback = profile( - "nativescript-angular/platform-common.exitCallback", (args: ApplicationEventData) => { + "@nativescript/angular/platform-common.exitCallback", (args: ApplicationEventData) => { const androidActivity = args.android; if (androidActivity && !androidActivity.isFinishing()) { // Exit event was triggered as a part of a restart of the app. @@ -279,9 +271,7 @@ export class NativeScriptPlatformRef extends PlatformRef { lastModuleRef.destroy(); } - if (!autoCreateFrame) { - rootContent = null; - } + rootContent = null; } ); on(launchEvent, launchCallback); @@ -302,72 +292,33 @@ export class NativeScriptPlatformRef extends PlatformRef { lastModuleRef.destroy(); } - const autoCreateFrame = !!this.appOptions.createFrameOnBootstrap; - let tempAppHostView: AppHostView; - let rootContent: View; - - if (autoCreateFrame) { - const { page, frame } = this.createFrameAndPage(true); - setRootPage(page); - rootContent = frame; - } else { - // Create a temp page for root of the renderer - tempAppHostView = new AppHostView(); - setRootPage(tempAppHostView); - } - - let bootstrapPromiseCompleted = false; this._bootstrapper().then( - moduleRef => { - bootstrapPromiseCompleted = true; - if (isLogEnabled()) { - bootstrapLog("Angular livesync done."); - } - onAfterLivesync.next({ moduleRef }); - - if (!autoCreateFrame) { - rootContent = tempAppHostView.content; - } - - lastBootstrappedModule = new WeakRef(moduleRef); - }, - error => { - bootstrapPromiseCompleted = true; - if (isLogEnabled()) { - bootstrapLogError("ERROR LIVESYNC BOOTSTRAPPING ANGULAR"); - } - const errorMessage = error.message + "\n\n" + error.stack; - if (isLogEnabled()) { - bootstrapLogError(errorMessage); - } - - rootContent = this.createErrorUI(errorMessage); - - onAfterLivesync.next({ error }); - } - ); - - if (isLogEnabled()) { - bootstrapLog("livesync bootstrapAction called, draining micro tasks queue. Root: " + rootContent); - } - (global).Zone.drainMicroTaskQueue(); - if (isLogEnabled()) { - bootstrapLog("livesync bootstrapAction called, draining micro tasks queue finished! Root: " + rootContent); - } - - if (!bootstrapPromiseCompleted) { - const result = "Livesync bootstrap promise didn't resolve"; - if (isLogEnabled()) { - bootstrapLogError(result); - } - rootContent = this.createErrorUI(result); - - onAfterLivesync.next({ error: new Error(result) }); - } - - applicationRerun({ - create: () => rootContent, - }); + moduleRef => { + if (isLogEnabled()) { + bootstrapLog("Angular livesync done."); + } + onAfterLivesync.next({ moduleRef }); + + lastBootstrappedModule = new WeakRef(moduleRef); + applicationRerun({ + create: () => getRootPage(), + }); + }, + error => { + if (isLogEnabled()) { + bootstrapLogError("ERROR LIVESYNC BOOTSTRAPPING ANGULAR"); + } + const errorMessage = error.message + "\n\n" + error.stack; + if (isLogEnabled()) { + bootstrapLogError(errorMessage); + } + + applicationRerun({ + create: () => this.createErrorUI(errorMessage), + }); + onAfterLivesync.next({ error }); + } + ); } private createErrorUI(message: string): View {