fix: better handling of states

This commit is contained in:
Eduardo Speroni
2022-01-07 17:26:52 -03:00
parent 3cae2c28d0
commit 36dc88bfad
2 changed files with 200 additions and 45 deletions

View File

@ -1,10 +1,10 @@
import { Frame } from '@nativescript/core/ui/frame';
import { Frame, GridLayout, Label, Page, Trace, View } from '@nativescript/core';
import * as TKUnit from '../../tk-unit';
import { unsetValue } from '@nativescript/core/ui/core/view';
import { PercentLength } from '@nativescript/core/ui/styling/style-properties';
import { buildUIAndRunTest } from '../../ui-helper';
export * from './frame-tests-common';
export function test_percent_width_and_height_set_to_page_support() {
let topFrame = Frame.topmost();
let currentPage = topFrame.currentPage;
@ -65,3 +65,140 @@ export function test_percent_margin_set_to_page_support() {
TKUnit.assertTrue(PercentLength.equals(currentPage.marginRight, 0));
TKUnit.assertTrue(PercentLength.equals(currentPage.marginBottom, 0));
}
export function test_nested_frames() {
let topFrame = Frame.topmost();
let currentPage = topFrame.currentPage;
console.log(Frame.topmost(), currentPage);
class PageAbstraction {
page: Page;
root: GridLayout;
setContent(content: View) {
this.root.insertChild(content, 0);
content.marginTop = 30;
}
}
const pageFactory = (color: string) => {
const page = new Page();
page.backgroundColor = color;
const gl = new GridLayout();
const label = new Label();
page.on('navigatedTo', () => {
let depth = 0;
let parent = label.parent;
let parentFrame: Frame = null;
while (parent) {
if (parent instanceof Frame) {
parentFrame = parentFrame || parent;
depth++;
}
parent = parent.parent;
}
label.text = `Depth: ${depth} - Page: ${parentFrame?.backStack.length}`;
});
label.style.zIndex = 999;
gl.insertChild(label, 0);
page.content = gl;
const abs = new PageAbstraction();
abs.root = gl;
abs.page = page;
return abs;
};
const page1 = pageFactory('red');
const page2 = pageFactory('blue');
const page3 = pageFactory('green');
const parentPage2 = pageFactory('yellow');
const frameFactory = () => {
const frame = new Frame();
frame._popFromFrameStack();
frame.navigate = function (...args) {
// console.log('navigateTo', args, args[0].create().backgroundColor);
Frame.prototype.navigate.call(frame, ...args);
this._popFromFrameStack();
};
frame.goBack = function (...args) {
// console.log('goBack', args);
Frame.prototype.goBack.call(frame, ...args);
this._popFromFrameStack();
};
frame.on('navigatingTo', () => ((frame as any).__midNav = true));
frame.on('navigatedTo', () => ((frame as any).__midNav = false));
return frame;
};
const innerFrame = frameFactory();
const innerFrame2 = frameFactory();
page1.setContent(innerFrame2);
innerFrame.navigate({
create: () => {
return page1.page;
},
});
innerFrame2.navigate({
create: () => {
return page2.page;
},
});
function validateState(frame: Frame) {
TKUnit.waitUntilReady(() => (frame as any).__midNav === false, 1);
TKUnit.assertTrue(frame._executingContext == null);
TKUnit.assertTrue(frame._currentEntry != null);
if (frame.isLoaded) {
TKUnit.assertTrue(frame._currentEntry.fragment != null);
}
}
// TKUnit.wait(1);
buildUIAndRunTest(innerFrame, ([parentFrame, parentPage]) => {
Trace.enable();
Trace.setCategories(Trace.categories.concat(Trace.categories.NativeLifecycle, Trace.categories.Transition));
validateState(innerFrame);
validateState(innerFrame2);
innerFrame2.navigate({
create: () => {
return page3.page;
},
});
validateState(innerFrame);
validateState(innerFrame2);
innerFrame.navigate({
create: () => parentPage2.page,
});
validateState(innerFrame);
validateState(innerFrame2);
innerFrame.goBack();
validateState(innerFrame);
validateState(innerFrame2);
innerFrame2.goBack();
validateState(innerFrame);
validateState(innerFrame2);
innerFrame.navigate({
create: () => parentPage2.page,
});
validateState(innerFrame);
validateState(innerFrame2);
innerFrame.goBack();
validateState(innerFrame);
validateState(innerFrame2);
// innerFrame.navigate({
// create: () => parentPage2.page,
// });
// TKUnit.wait(1);
// innerFrame.goBack();
// TKUnit.wait(1);
// TKUnit.assertTrue(innerFrame2._currentEntry.fragment != null);
// TKUnit.wait(5);
});
// TKUnit.waitUntilReady(() => {
// return innerFrame.isLayoutValid;
// }, 1);
TKUnit.wait(10);
}

View File

@ -77,6 +77,26 @@ function getAttachListener(): android.view.View.OnAttachStateChangeListener {
return attachStateChangeListener;
}
function waitUntilReady(lifecycle: androidx.lifecycle.Lifecycle, resolve: (v: boolean) => unknown, targetState = androidx.lifecycle.Lifecycle.State.RESUMED) {
if (!lifecycle || lifecycle.getCurrentState().isAtLeast(targetState)) {
resolve(true);
return;
}
const observer = new androidx.lifecycle.LifecycleEventObserver({
onStateChanged: (source: androidx.lifecycle.LifecycleOwner, event: androidx.lifecycle.Lifecycle.Event) => {
if (lifecycle.getCurrentState().isAtLeast(targetState)) {
lifecycle.removeObserver(observer);
resolve(true);
}
if (event === androidx.lifecycle.Lifecycle.Event.ON_DESTROY) {
lifecycle.removeObserver(observer);
resolve(false);
}
},
});
lifecycle.addObserver(observer);
}
export class Frame extends FrameBase {
public _originalBackground: any;
private _android: AndroidFrame;
@ -158,7 +178,7 @@ export class Frame extends FrameBase {
this._attachedToWindow = false;
}
protected async _processNextNavigationEntry(): Promise<void> {
protected _processNextNavigationEntry(): any {
// In case activity was destroyed because of back button pressed (e.g. app exit)
// and application is restored from recent apps, current fragment isn't recreated.
// In this case call _navigateCore in order to recreate the current fragment.
@ -177,27 +197,6 @@ export class Frame extends FrameBase {
}
let manager = this._getFragmentManager();
const lifecycle: androidx.lifecycle.Lifecycle = this._getFragmentLifecycle();
if (lifecycle && !lifecycle.getCurrentState().isAtLeast(androidx.lifecycle.Lifecycle.State.STARTED)) {
const success = await new Promise<boolean>((resolve) => {
const observer = new androidx.lifecycle.LifecycleEventObserver({
onStateChanged: (source: androidx.lifecycle.LifecycleOwner, event: androidx.lifecycle.Lifecycle.Event) => {
if (event === androidx.lifecycle.Lifecycle.Event.ON_START) {
lifecycle.removeObserver(observer);
resolve(true);
}
if (event === androidx.lifecycle.Lifecycle.Event.ON_DESTROY) {
lifecycle.removeObserver(observer);
resolve(false);
}
},
});
lifecycle.addObserver(observer);
});
if (!success) {
manager = null;
}
}
const entry = this._currentEntry;
const isNewEntry = !this._cachedTransitionState || entry !== this._cachedTransitionState.entry;
@ -256,12 +255,24 @@ export class Frame extends FrameBase {
this.disposeCurrentFragment();
}
onLoaded(): void {
onLoaded() {
if (this._originalBackground) {
this.backgroundColor = null;
this.backgroundColor = this._originalBackground;
this._originalBackground = null;
}
// const entry = this._currentEntry || this._executingContext?.entry;
// if (entry && !entry.fragment && entry.fragmentTag) {
// let manager = this._getFragmentManager();
// const lifecycle: androidx.lifecycle.Lifecycle = this._getFragmentLifecycle();
// entry.fragment = this.createFragment(entry, entry.fragmentTag);
// waitUntilStarted(lifecycle, () => {
// const transaction = manager.beginTransaction();
// _updateTransitions(entry);
// transaction.replace(this.containerViewId, entry.fragment, entry.fragmentTag);
// transaction.commitAllowingStateLoss();
// });
// }
super.onLoaded();
}
@ -414,6 +425,8 @@ export class Frame extends FrameBase {
}
const manager: androidx.fragment.app.FragmentManager = this._getFragmentManager();
const lifecycle = this._getFragmentLifecycle();
const clearHistory = newEntry.entry.clearHistory;
const currentEntry = this._currentEntry;
@ -430,30 +443,35 @@ export class Frame extends FrameBase {
fragmentId++;
const newFragmentTag = `fragment${fragmentId}[${navDepth}]`;
const newFragment = this.createFragment(newEntry, newFragmentTag);
const transaction = manager.beginTransaction();
let animated = currentEntry ? this._getIsAnimatedNavigation(newEntry.entry) : false;
// NOTE: Don't use transition for the initial navigation (same as on iOS)
// On API 21+ transition won't be triggered unless there was at least one
// layout pass so we will wait forever for transitionCompleted handler...
// https://github.com/NativeScript/NativeScript/issues/4895
let navigationTransition: NavigationTransition;
if (this._currentEntry) {
navigationTransition = this._getNavigationTransition(newEntry.entry);
} else {
navigationTransition = null;
}
waitUntilReady(lifecycle, (started: boolean) => {
if (!started) {
return;
}
const transaction = manager.beginTransaction();
let animated = currentEntry ? this._getIsAnimatedNavigation(newEntry.entry) : false;
// NOTE: Don't use transition for the initial navigation (same as on iOS)
// On API 21+ transition won't be triggered unless there was at least one
// layout pass so we will wait forever for transitionCompleted handler...
// https://github.com/NativeScript/NativeScript/issues/4895
let navigationTransition: NavigationTransition;
if (this._currentEntry) {
navigationTransition = this._getNavigationTransition(newEntry.entry);
} else {
navigationTransition = null;
}
const isNestedDefaultTransition = !currentEntry;
const isNestedDefaultTransition = !currentEntry;
_setAndroidFragmentTransitions(animated, navigationTransition, currentEntry, newEntry, this._android.frameId, transaction, isNestedDefaultTransition);
_setAndroidFragmentTransitions(animated, navigationTransition, currentEntry, newEntry, this._android.frameId, transaction, isNestedDefaultTransition);
if (currentEntry && animated && !navigationTransition) {
//TODO: Check whether or not this is still necessary. For Modal views?
//transaction.setTransition(androidx.fragment.app.FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
}
if (currentEntry && animated && !navigationTransition) {
//TODO: Check whether or not this is still necessary. For Modal views?
//transaction.setTransition(androidx.fragment.app.FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
}
transaction.replace(this.containerViewId, newFragment, newFragmentTag);
transaction.commitAllowingStateLoss();
transaction.replace(this.containerViewId, newFragment, newFragmentTag);
transaction.commitAllowingStateLoss();
});
}
public _goBackCore(backstackEntry: BackstackEntry) {