mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-08-17 04:41:36 +08:00
fix: better handling of states
This commit is contained in:
@ -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);
|
||||
}
|
||||
|
@ -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) {
|
||||
|
Reference in New Issue
Block a user