diff --git a/api-reports/NativeScript.api.md b/api-reports/NativeScript.api.md index c26a8ac56..c6bf4bbf8 100644 --- a/api-reports/NativeScript.api.md +++ b/api-reports/NativeScript.api.md @@ -772,6 +772,9 @@ export class Frame extends View { animated: boolean; + // (undocumented) + _animationInProgress: boolean; + backStack: Array; canGoBack(): boolean; @@ -2931,6 +2934,7 @@ export abstract class ViewBase extends Observable { // (undocumented) _setupAsRootView(context: any): void; _setupUI(context: any /* android.content.Context */, atIndex?: number): void; + _shouldDelayLoad(): boolean; showModal(moduleName: string, modalOptions: ShowModalOptions): ViewBase; showModal(view: ViewBase, modalOptions: ShowModalOptions): ViewBase; public readonly style: Style; diff --git a/e2e/ui-tests-app/app/tabs/frame-in-tabs-inner-page-1.xml b/e2e/ui-tests-app/app/tabs/frame-in-tabs-inner-page-1.xml new file mode 100644 index 000000000..f0c7c0c06 --- /dev/null +++ b/e2e/ui-tests-app/app/tabs/frame-in-tabs-inner-page-1.xml @@ -0,0 +1,3 @@ + + \ No newline at end of file diff --git a/e2e/ui-tests-app/app/tabs/frame-in-tabs-inner-page-2.xml b/e2e/ui-tests-app/app/tabs/frame-in-tabs-inner-page-2.xml new file mode 100644 index 000000000..7c5812a0b --- /dev/null +++ b/e2e/ui-tests-app/app/tabs/frame-in-tabs-inner-page-2.xml @@ -0,0 +1,3 @@ + + \ No newline at end of file diff --git a/e2e/ui-tests-app/app/tabs/frame-in-tabs-inner-page-3.xml b/e2e/ui-tests-app/app/tabs/frame-in-tabs-inner-page-3.xml new file mode 100644 index 000000000..e36fce108 --- /dev/null +++ b/e2e/ui-tests-app/app/tabs/frame-in-tabs-inner-page-3.xml @@ -0,0 +1,3 @@ + + \ No newline at end of file diff --git a/e2e/ui-tests-app/app/tabs/frame-in-tabs-inner-page-4.xml b/e2e/ui-tests-app/app/tabs/frame-in-tabs-inner-page-4.xml new file mode 100644 index 000000000..a2607283d --- /dev/null +++ b/e2e/ui-tests-app/app/tabs/frame-in-tabs-inner-page-4.xml @@ -0,0 +1,3 @@ + + \ No newline at end of file diff --git a/e2e/ui-tests-app/app/tabs/frame-in-tabs.ts b/e2e/ui-tests-app/app/tabs/frame-in-tabs.ts new file mode 100644 index 000000000..2b96c7fa2 --- /dev/null +++ b/e2e/ui-tests-app/app/tabs/frame-in-tabs.ts @@ -0,0 +1,3 @@ +export function onItemTap(args) { + console.log(`Item with index: ${args.index} tapped`); +} diff --git a/e2e/ui-tests-app/app/tabs/frame-in-tabs.xml b/e2e/ui-tests-app/app/tabs/frame-in-tabs.xml new file mode 100644 index 000000000..02ca704c6 --- /dev/null +++ b/e2e/ui-tests-app/app/tabs/frame-in-tabs.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/e2e/ui-tests-app/e2e/suites/tab-navigation/tabs/tabs-tests.e2e-spec.ts b/e2e/ui-tests-app/e2e/suites/tab-navigation/tabs/tabs-tests.e2e-spec.ts index 28502fdf9..7a98d6b2a 100644 --- a/e2e/ui-tests-app/e2e/suites/tab-navigation/tabs/tabs-tests.e2e-spec.ts +++ b/e2e/ui-tests-app/e2e/suites/tab-navigation/tabs/tabs-tests.e2e-spec.ts @@ -283,4 +283,22 @@ describe(`${imagePrefix}-suite`, async function () { assert.isTrue(driver.imageHelper.hasImageComparisonPassed()); await tabsViewBasePage.navigateBackToSuitMainPage(); }); + + // it(`${imagePrefix}-frame-in-tabs`, async function () { + // await tabsViewBasePage.navigateToSample("frame-in-tabs"); + // await driver.imageHelper.compareScreen(); + + // // go through the tabs and check that they are loaded + // await tabsViewBasePage.tabOnItem(1); + // await driver.imageHelper.compareScreen(); + + // await tabsViewBasePage.tabOnItem(2); + // await driver.imageHelper.compareScreen(); + + // await tabsViewBasePage.tabOnItem(3); + // await driver.imageHelper.compareScreen(); + + // assert.isTrue(driver.imageHelper.hasImageComparisonPassed()); + // await tabsViewBasePage.navigateBackToSuitMainPage(); + // }); }); diff --git a/nativescript-core/globals/register-module-helpers.ts b/nativescript-core/globals/register-module-helpers.ts index 79242b71e..0d76c6c9d 100644 --- a/nativescript-core/globals/register-module-helpers.ts +++ b/nativescript-core/globals/register-module-helpers.ts @@ -14,6 +14,7 @@ const modulesLoadedForUI = new Set(); const defaultExtensionMap: ExtensionMap = { ".js": ".js", ".ts": ".js", + ".kt": ".js", ".css": ".css", ".scss": ".css", ".less": ".css", diff --git a/nativescript-core/ui/core/view-base/view-base.d.ts b/nativescript-core/ui/core/view-base/view-base.d.ts index 0537ac47e..a8fa7dd25 100644 --- a/nativescript-core/ui/core/view-base/view-base.d.ts +++ b/nativescript-core/ui/core/view-base/view-base.d.ts @@ -470,6 +470,12 @@ export abstract class ViewBase extends Observable { */ _setupAsRootView(context: any): void; + /** + * When returning true the callLoaded method will be run in setTimeout + * Method is intended to be overridden by inheritors and used as "protected" + */ + _shouldDelayLoad(): boolean; + /** * @private */ diff --git a/nativescript-core/ui/core/view-base/view-base.ts b/nativescript-core/ui/core/view-base/view-base.ts index 092eec6fc..c464a362a 100644 --- a/nativescript-core/ui/core/view-base/view-base.ts +++ b/nativescript-core/ui/core/view-base/view-base.ts @@ -615,10 +615,18 @@ export abstract class ViewBase extends Observable implements ViewBaseDefinition public loadView(view: ViewBase): void { if (view && !view.isLoaded) { - view.callLoaded(); + if (this._shouldDelayLoad()) { + setTimeout(() => view.callLoaded()); + } else { + view.callLoaded(); + } } } + public _shouldDelayLoad(): boolean { + return false; + } + public unloadView(view: ViewBase): void { if (view && view.isLoaded) { view.callUnloaded(); @@ -1053,7 +1061,7 @@ export const classNameProperty = new Property({ cssClasses.clear(); if (shouldAddModalRootViewCssClasses) { - cssClasses.add(MODAL_ROOT_VIEW_CSS_CLASS); + cssClasses.add(MODAL_ROOT_VIEW_CSS_CLASS); } else if (shouldAddRootViewCssClasses) { cssClasses.add(ROOT_VIEW_CSS_CLASS); } diff --git a/nativescript-core/ui/frame/frame-common.ts b/nativescript-core/ui/frame/frame-common.ts index 7abe9f168..dbfe3a8b5 100644 --- a/nativescript-core/ui/frame/frame-common.ts +++ b/nativescript-core/ui/frame/frame-common.ts @@ -42,6 +42,7 @@ export class FrameBase extends CustomLayoutView implements FrameDefinition { public actionBarVisibility: "auto" | "never" | "always"; public _currentEntry: BackstackEntry; + public _animationInProgress = false; public _executingContext: NavigationContext; public _isInFrameStack = false; public static defaultAnimatedNavigation = true; diff --git a/nativescript-core/ui/frame/frame.d.ts b/nativescript-core/ui/frame/frame.d.ts index bd204e4b1..ca40894dd 100644 --- a/nativescript-core/ui/frame/frame.d.ts +++ b/nativescript-core/ui/frame/frame.d.ts @@ -147,6 +147,10 @@ export class Frame extends View { * @private */ navigationBarHeight: number; + /** + * @private + */ + _animationInProgress: boolean; /** * @private */ @@ -218,21 +222,21 @@ export function setFragmentClass(clazz: any): void; /** * @deprecated Use Frame.getFrameById() instead. - * + * * Gets a frame by id. */ export function getFrameById(id: string): Frame; /** * @deprecated Use Frame.topmost() instead. - * + * * Gets the topmost frame in the frames stack. An application will typically has one frame instance. Multiple frames handle nested (hierarchical) navigation scenarios. */ export function topmost(): Frame; /** * @deprecated Use Frame.goBack() instead. - * + * * Navigates back using the navigation hierarchy (if any). Updates the Frame stack as needed. * This method will start from the topmost Frame and will recursively search for an instance that has the canGoBack operation available. */ @@ -241,7 +245,7 @@ export function goBack(); //@private /** * @deprecated Use Frame._stack() instead. - * + * * @private */ export function _stack(): Array; @@ -487,7 +491,7 @@ export function setActivityCallbacks(activity: any /*androidx.appcompat.app.AppC //@private /** * @deprecated Use Frame.reloadPage() instead. - * + * * @private */ export function reloadPage(context?: ModuleContext): void; diff --git a/nativescript-core/ui/page/page.ios.ts b/nativescript-core/ui/page/page.ios.ts index f921b8967..576eb25a8 100644 --- a/nativescript-core/ui/page/page.ios.ts +++ b/nativescript-core/ui/page/page.ios.ts @@ -93,7 +93,7 @@ class UIViewControllerImpl extends UIViewController { return; } - const frame = this.navigationController ? (this.navigationController).owner : null; + const frame = (this.navigationController ? (this.navigationController).owner : null); const newEntry = this[ENTRY]; // Don't raise event if currentPage was showing modal page. @@ -199,7 +199,7 @@ class UIViewControllerImpl extends UIViewController { if (!owner) { return; } - + // Cache presentedViewController if any. We don't want to raise // navigation events in case of presenting view controller. if (!owner._presentedViewController) { @@ -230,7 +230,6 @@ class UIViewControllerImpl extends UIViewController { if (!page || page.modal || page._presentedViewController) { return; } - // Forward navigation does not remove page from frame so we raise unloaded manually. if (page.isLoaded) { page.callUnloaded(); @@ -349,6 +348,10 @@ export class Page extends PageBase { // } + public _shouldDelayLoad(): boolean { + return this._frame && this._frame._animationInProgress; + } + public onLoaded(): void { super.onLoaded(); if (this.hasActionBar) { diff --git a/nativescript-core/ui/tabs/tabs.ios.ts b/nativescript-core/ui/tabs/tabs.ios.ts index 82aad3eb1..621a520d1 100644 --- a/nativescript-core/ui/tabs/tabs.ios.ts +++ b/nativescript-core/ui/tabs/tabs.ios.ts @@ -1091,7 +1091,18 @@ export class Tabs extends TabsBase { } this._currentNativeSelectedIndex = value; + + let itemControllerOwner = null; + if (itemController._owner) { + let itemControllerOwner = itemController._owner.get(); + // do not load new views while the animation is in progress https://stackoverflow.com/a/47031524/613113 + itemControllerOwner._animationInProgress = true; + } + this.viewController.setViewControllersDirectionAnimatedCompletion(controllers, navigationDirection, true, (finished: boolean) => { + if (itemControllerOwner) { + itemControllerOwner._animationInProgress = false; + } if (finished) { // HACK: UIPageViewController fix; see https://stackoverflow.com/a/17330606 invokeOnRunLoop(() => this.viewController.setViewControllersDirectionAnimatedCompletion(controllers, navigationDirection, false, null));