mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-08-17 21:01:34 +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 * as TKUnit from '../../tk-unit';
|
||||||
import { unsetValue } from '@nativescript/core/ui/core/view';
|
import { unsetValue } from '@nativescript/core/ui/core/view';
|
||||||
import { PercentLength } from '@nativescript/core/ui/styling/style-properties';
|
import { PercentLength } from '@nativescript/core/ui/styling/style-properties';
|
||||||
|
import { buildUIAndRunTest } from '../../ui-helper';
|
||||||
export * from './frame-tests-common';
|
export * from './frame-tests-common';
|
||||||
|
|
||||||
|
|
||||||
export function test_percent_width_and_height_set_to_page_support() {
|
export function test_percent_width_and_height_set_to_page_support() {
|
||||||
let topFrame = Frame.topmost();
|
let topFrame = Frame.topmost();
|
||||||
let currentPage = topFrame.currentPage;
|
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.marginRight, 0));
|
||||||
TKUnit.assertTrue(PercentLength.equals(currentPage.marginBottom, 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;
|
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 {
|
export class Frame extends FrameBase {
|
||||||
public _originalBackground: any;
|
public _originalBackground: any;
|
||||||
private _android: AndroidFrame;
|
private _android: AndroidFrame;
|
||||||
@ -158,7 +178,7 @@ export class Frame extends FrameBase {
|
|||||||
this._attachedToWindow = false;
|
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)
|
// 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.
|
// and application is restored from recent apps, current fragment isn't recreated.
|
||||||
// In this case call _navigateCore in order to recreate the current fragment.
|
// 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();
|
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 entry = this._currentEntry;
|
||||||
const isNewEntry = !this._cachedTransitionState || entry !== this._cachedTransitionState.entry;
|
const isNewEntry = !this._cachedTransitionState || entry !== this._cachedTransitionState.entry;
|
||||||
|
|
||||||
@ -256,12 +255,24 @@ export class Frame extends FrameBase {
|
|||||||
this.disposeCurrentFragment();
|
this.disposeCurrentFragment();
|
||||||
}
|
}
|
||||||
|
|
||||||
onLoaded(): void {
|
onLoaded() {
|
||||||
if (this._originalBackground) {
|
if (this._originalBackground) {
|
||||||
this.backgroundColor = null;
|
this.backgroundColor = null;
|
||||||
this.backgroundColor = this._originalBackground;
|
this.backgroundColor = this._originalBackground;
|
||||||
this._originalBackground = null;
|
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();
|
super.onLoaded();
|
||||||
}
|
}
|
||||||
@ -414,6 +425,8 @@ export class Frame extends FrameBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const manager: androidx.fragment.app.FragmentManager = this._getFragmentManager();
|
const manager: androidx.fragment.app.FragmentManager = this._getFragmentManager();
|
||||||
|
const lifecycle = this._getFragmentLifecycle();
|
||||||
|
|
||||||
const clearHistory = newEntry.entry.clearHistory;
|
const clearHistory = newEntry.entry.clearHistory;
|
||||||
const currentEntry = this._currentEntry;
|
const currentEntry = this._currentEntry;
|
||||||
|
|
||||||
@ -430,30 +443,35 @@ export class Frame extends FrameBase {
|
|||||||
fragmentId++;
|
fragmentId++;
|
||||||
const newFragmentTag = `fragment${fragmentId}[${navDepth}]`;
|
const newFragmentTag = `fragment${fragmentId}[${navDepth}]`;
|
||||||
const newFragment = this.createFragment(newEntry, newFragmentTag);
|
const newFragment = this.createFragment(newEntry, newFragmentTag);
|
||||||
const transaction = manager.beginTransaction();
|
waitUntilReady(lifecycle, (started: boolean) => {
|
||||||
let animated = currentEntry ? this._getIsAnimatedNavigation(newEntry.entry) : false;
|
if (!started) {
|
||||||
// NOTE: Don't use transition for the initial navigation (same as on iOS)
|
return;
|
||||||
// On API 21+ transition won't be triggered unless there was at least one
|
}
|
||||||
// layout pass so we will wait forever for transitionCompleted handler...
|
const transaction = manager.beginTransaction();
|
||||||
// https://github.com/NativeScript/NativeScript/issues/4895
|
let animated = currentEntry ? this._getIsAnimatedNavigation(newEntry.entry) : false;
|
||||||
let navigationTransition: NavigationTransition;
|
// NOTE: Don't use transition for the initial navigation (same as on iOS)
|
||||||
if (this._currentEntry) {
|
// On API 21+ transition won't be triggered unless there was at least one
|
||||||
navigationTransition = this._getNavigationTransition(newEntry.entry);
|
// layout pass so we will wait forever for transitionCompleted handler...
|
||||||
} else {
|
// https://github.com/NativeScript/NativeScript/issues/4895
|
||||||
navigationTransition = null;
|
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) {
|
if (currentEntry && animated && !navigationTransition) {
|
||||||
//TODO: Check whether or not this is still necessary. For Modal views?
|
//TODO: Check whether or not this is still necessary. For Modal views?
|
||||||
//transaction.setTransition(androidx.fragment.app.FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
|
//transaction.setTransition(androidx.fragment.app.FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
|
||||||
}
|
}
|
||||||
|
|
||||||
transaction.replace(this.containerViewId, newFragment, newFragmentTag);
|
transaction.replace(this.containerViewId, newFragment, newFragmentTag);
|
||||||
transaction.commitAllowingStateLoss();
|
transaction.commitAllowingStateLoss();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public _goBackCore(backstackEntry: BackstackEntry) {
|
public _goBackCore(backstackEntry: BackstackEntry) {
|
||||||
|
Reference in New Issue
Block a user