mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-11-05 13:26:48 +08:00
fix(RootLayout): close popup views on live-sync (#9834)
This commit is contained in:
committed by
GitHub
parent
efa80c728b
commit
694146689b
@@ -191,19 +191,25 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition {
|
||||
Trace.write(`${this}._onLivesync(${JSON.stringify(context)})`, Trace.categories.Livesync);
|
||||
}
|
||||
|
||||
let handled = false;
|
||||
|
||||
const rootLayout = global.rootLayout;
|
||||
if (rootLayout != null && rootLayout.topmost() != null) {
|
||||
rootLayout.closeAll();
|
||||
handled = true;
|
||||
}
|
||||
|
||||
if (this._closeAllModalViewsInternal()) {
|
||||
return true;
|
||||
handled = true;
|
||||
}
|
||||
|
||||
if (this._handleLivesync(context)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let handled = false;
|
||||
this.eachChildView((child: ViewCommon) => {
|
||||
if (child._onLivesync(context)) {
|
||||
handled = true;
|
||||
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2,14 +2,14 @@ import { GridLayout } from '../grid-layout';
|
||||
import { View } from '../../core/view';
|
||||
|
||||
export class RootLayout extends GridLayout {
|
||||
open(view: View, options?: RootLayoutOptions): Promise<void>;
|
||||
open(view: View, options: RootLayoutOptions = {}): Promise<void>;
|
||||
close(view: View, exitTo?: TransitionAnimation): Promise<void>;
|
||||
topmost(): View;
|
||||
bringToFront(view: View, animated?: boolean): Promise<void>;
|
||||
closeAll(): Promise<void>;
|
||||
closeAll(): Promise<void[]>;
|
||||
getShadeCover(): View;
|
||||
openShadeCover(options: ShadeCoverOptions): void;
|
||||
closeShadeCover(shadeCoverOptions?: ShadeCoverOptions): Promise<void>;
|
||||
openShadeCover(options: ShadeCoverOptions = {}): void;
|
||||
closeShadeCover(shadeCoverOptions: ShadeCoverOptions = {}): Promise<void>;
|
||||
}
|
||||
|
||||
export function getRootLayout(): RootLayout;
|
||||
|
||||
@@ -15,60 +15,59 @@ export class RootLayoutBase extends GridLayout {
|
||||
constructor() {
|
||||
super();
|
||||
global.rootLayout = this;
|
||||
this.on('loaded', () => {
|
||||
// get actual content count of rootLayout (elements between the <RootLayout> tags in the template).
|
||||
// All popups will be inserted dynamically at a higher index
|
||||
this.staticChildCount = this.getChildrenCount();
|
||||
});
|
||||
}
|
||||
|
||||
// ability to add any view instance to compositie views like layers
|
||||
open(view: View, options?: RootLayoutOptions): Promise<void> {
|
||||
public onLoaded() {
|
||||
// get actual content count of rootLayout (elements between the <RootLayout> tags in the template).
|
||||
// All popups will be inserted dynamically at a higher index
|
||||
this.staticChildCount = this.getChildrenCount();
|
||||
|
||||
super.onLoaded();
|
||||
}
|
||||
|
||||
// ability to add any view instance to composite views like layers
|
||||
open(view: View, options: RootLayoutOptions = {}): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
if (this.hasChild(view)) {
|
||||
if (Trace.isEnabled()) {
|
||||
Trace.write(`${view} has already been added`, Trace.categories.Layout);
|
||||
}
|
||||
if (!(view instanceof View)) {
|
||||
return reject(new Error(`Invalid open view: ${view}`));
|
||||
}
|
||||
|
||||
if (this.hasChild(view)) {
|
||||
return reject(new Error(`${view} has already been added`));
|
||||
}
|
||||
|
||||
const enterAnimationDefinition = options.animation ? options.animation.enterFrom : null;
|
||||
|
||||
// keep track of the views locally to be able to use their options later
|
||||
this.popupViews.push({ view: view, options: options });
|
||||
|
||||
if (options.shadeCover) {
|
||||
// perf optimization note: we only need 1 layer of shade cover
|
||||
// we just update properties if needed by additional overlaid views
|
||||
if (this.shadeCover) {
|
||||
// overwrite current shadeCover options if topmost popupview has additional shadeCover configurations
|
||||
this.updateShadeCover(this.shadeCover, options.shadeCover);
|
||||
} else {
|
||||
// keep track of the views locally to be able to use their options later
|
||||
this.popupViews.push({ view: view, options: options });
|
||||
|
||||
if (options?.shadeCover) {
|
||||
// perf optimization note: we only need 1 layer of shade cover
|
||||
// we just update properties if needed by additional overlaid views
|
||||
if (this.shadeCover) {
|
||||
// overwrite current shadeCover options if topmost popupview has additional shadeCover configurations
|
||||
this.updateShadeCover(this.shadeCover, options.shadeCover);
|
||||
} else {
|
||||
this.openShadeCover(options.shadeCover);
|
||||
}
|
||||
}
|
||||
|
||||
view.opacity = 0; // always begin with view invisible when adding dynamically
|
||||
this.insertChild(view, this.getChildrenCount() + 1);
|
||||
|
||||
setTimeout(() => {
|
||||
// only apply initial state and animate after the first tick - ensures safe areas and other measurements apply correctly
|
||||
this.applyInitialState(view, options.animation ? options.animation.enterFrom : null);
|
||||
this.getEnterAnimation(view, options.animation ? options.animation.enterFrom : null)
|
||||
.play()
|
||||
.then(() => {
|
||||
this.applyDefaultState(view);
|
||||
resolve();
|
||||
})
|
||||
.catch((ex) => {
|
||||
if (Trace.isEnabled()) {
|
||||
Trace.write(`Error playing enter animation: ${ex}`, Trace.categories.Layout, Trace.messageType.error);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
} catch (ex) {
|
||||
if (Trace.isEnabled()) {
|
||||
Trace.write(`Error opening popup (${view}): ${ex}`, Trace.categories.Layout, Trace.messageType.error);
|
||||
this.openShadeCover(options.shadeCover);
|
||||
}
|
||||
}
|
||||
|
||||
view.opacity = 0; // always begin with view invisible when adding dynamically
|
||||
this.insertChild(view, this.getChildrenCount() + 1);
|
||||
|
||||
setTimeout(() => {
|
||||
// only apply initial state and animate after the first tick - ensures safe areas and other measurements apply correctly
|
||||
this.applyInitialState(view, enterAnimationDefinition);
|
||||
this.getEnterAnimation(view, enterAnimationDefinition)
|
||||
.play()
|
||||
.then(() => {
|
||||
this.applyDefaultState(view);
|
||||
resolve();
|
||||
})
|
||||
.catch((ex) => {
|
||||
reject(new Error(`Error playing enter animation: ${ex}`));
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -76,81 +75,67 @@ export class RootLayoutBase extends GridLayout {
|
||||
// ability to remove any view instance from composite views
|
||||
close(view: View, exitTo?: TransitionAnimation): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (this.hasChild(view)) {
|
||||
const cleanupAndFinish = () => {
|
||||
this.removeChild(view);
|
||||
resolve();
|
||||
};
|
||||
if (!(view instanceof View)) {
|
||||
return reject(new Error(`Invalid close view: ${view}`));
|
||||
}
|
||||
|
||||
try {
|
||||
const popupIndex = this.getPopupIndex(view);
|
||||
const poppedView = this.popupViews[popupIndex];
|
||||
// use exitAnimation that is passed in and fallback to the exitAnimation passed in when opening
|
||||
const exitAnimationDefinition = exitTo || poppedView?.options?.animation?.exitTo;
|
||||
if (!this.hasChild(view)) {
|
||||
return reject(new Error(`Unable to close popup. ${view} not found`));
|
||||
}
|
||||
|
||||
// Remove view from tracked popupviews
|
||||
this.popupViews.splice(popupIndex, 1);
|
||||
const popupIndex = this.getPopupIndex(view);
|
||||
const poppedView = this.popupViews[popupIndex];
|
||||
const cleanupAndFinish = () => {
|
||||
this.removeChild(view);
|
||||
resolve();
|
||||
};
|
||||
// use exitAnimation that is passed in and fallback to the exitAnimation passed in when opening
|
||||
const exitAnimationDefinition = exitTo || poppedView?.options?.animation?.exitTo;
|
||||
|
||||
if (this.shadeCover) {
|
||||
// update shade cover with the topmost popupView options (if not specifically told to ignore)
|
||||
if (!poppedView?.options?.shadeCover.ignoreShadeRestore) {
|
||||
const shadeCoverOptions = this.popupViews[this.popupViews.length - 1]?.options?.shadeCover;
|
||||
if (shadeCoverOptions) {
|
||||
this.updateShadeCover(this.shadeCover, shadeCoverOptions);
|
||||
}
|
||||
}
|
||||
// remove shade cover animation if this is the last opened popup view
|
||||
if (this.popupViews.length === 0) {
|
||||
this.closeShadeCover(poppedView.options.shadeCover);
|
||||
}
|
||||
}
|
||||
// Remove view from tracked popupviews
|
||||
this.popupViews.splice(popupIndex, 1);
|
||||
|
||||
if (exitAnimationDefinition) {
|
||||
this.getExitAnimation(view, exitAnimationDefinition)
|
||||
.play()
|
||||
.then(cleanupAndFinish.bind(this))
|
||||
.catch((ex) => {
|
||||
if (Trace.isEnabled()) {
|
||||
Trace.write(`Error playing exit animation: ${ex}`, Trace.categories.Layout, Trace.messageType.error);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
cleanupAndFinish();
|
||||
}
|
||||
} catch (ex) {
|
||||
if (Trace.isEnabled()) {
|
||||
Trace.write(`Error closing popup (${view}): ${ex}`, Trace.categories.Layout, Trace.messageType.error);
|
||||
if (this.shadeCover) {
|
||||
// update shade cover with the topmost popupView options (if not specifically told to ignore)
|
||||
if (!poppedView?.options?.shadeCover.ignoreShadeRestore) {
|
||||
const shadeCoverOptions = this.popupViews[this.popupViews.length - 1]?.options?.shadeCover;
|
||||
if (shadeCoverOptions) {
|
||||
this.updateShadeCover(this.shadeCover, shadeCoverOptions);
|
||||
}
|
||||
}
|
||||
// remove shade cover animation if this is the last opened popup view
|
||||
if (this.popupViews.length === 0) {
|
||||
this.closeShadeCover(poppedView.options.shadeCover);
|
||||
}
|
||||
}
|
||||
|
||||
if (exitAnimationDefinition) {
|
||||
this.getExitAnimation(view, exitAnimationDefinition)
|
||||
.play()
|
||||
.then(cleanupAndFinish.bind(this))
|
||||
.catch((ex) => {
|
||||
reject(new Error(`Error playing exit animation: ${ex}`));
|
||||
});
|
||||
} else {
|
||||
if (Trace.isEnabled()) {
|
||||
Trace.write(`Unable to close popup. ${view} not found`, Trace.categories.Layout);
|
||||
}
|
||||
cleanupAndFinish();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
closeAll(): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
while (this.popupViews.length > 0) {
|
||||
// remove all children in the popupViews array
|
||||
this.close(this.popupViews[this.popupViews.length - 1].view);
|
||||
}
|
||||
resolve();
|
||||
} catch (ex) {
|
||||
if (Trace.isEnabled()) {
|
||||
Trace.write(`Error closing popups: ${ex}`, Trace.categories.Layout, Trace.messageType.error);
|
||||
}
|
||||
}
|
||||
});
|
||||
closeAll(): Promise<void[]> {
|
||||
const toClose = [];
|
||||
// Close all views at the same time and wait for all of them
|
||||
while (this.popupViews.length > 0) {
|
||||
toClose.push(this.close(this.popupViews[this.popupViews.length - 1].view));
|
||||
}
|
||||
return Promise.all(toClose);
|
||||
}
|
||||
|
||||
getShadeCover(): View {
|
||||
return this.shadeCover;
|
||||
}
|
||||
|
||||
openShadeCover(options: ShadeCoverOptions) {
|
||||
openShadeCover(options: ShadeCoverOptions = {}) {
|
||||
if (this.shadeCover) {
|
||||
if (Trace.isEnabled()) {
|
||||
Trace.write(`RootLayout shadeCover already open.`, Trace.categories.Layout, Trace.messageType.warn);
|
||||
@@ -163,7 +148,7 @@ export class RootLayoutBase extends GridLayout {
|
||||
}
|
||||
}
|
||||
|
||||
closeShadeCover(shadeCoverOptions?: ShadeCoverOptions): Promise<void> {
|
||||
closeShadeCover(shadeCoverOptions: ShadeCoverOptions = {}): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
// if shade cover is displayed and the last popup is closed, also close the shade cover
|
||||
if (this.shadeCover) {
|
||||
@@ -191,67 +176,60 @@ export class RootLayoutBase extends GridLayout {
|
||||
// bring any view instance open on the rootlayout to front of all the children visually
|
||||
bringToFront(view: View, animated: boolean = false): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
const popupIndex = this.getPopupIndex(view);
|
||||
// popupview should be present and not already the topmost view
|
||||
if (popupIndex > -1 && popupIndex !== this.popupViews.length - 1) {
|
||||
// keep the popupViews array in sync with the stacking of the views
|
||||
const currentView = this.popupViews[this.getPopupIndex(view)];
|
||||
this.popupViews.splice(this.getPopupIndex(view), 1);
|
||||
this.popupViews.push(currentView);
|
||||
if (!(view instanceof View)) {
|
||||
return reject(new Error(`Invalid bringToFront view: ${view}`));
|
||||
}
|
||||
|
||||
if (this.hasChild(view)) {
|
||||
const exitAnimation = this.getViewExitState(view);
|
||||
if (animated && exitAnimation) {
|
||||
this.getExitAnimation(view, exitAnimation)
|
||||
if (!this.hasChild(view)) {
|
||||
return reject(new Error(`${view} not found or already at topmost`));
|
||||
}
|
||||
|
||||
const popupIndex = this.getPopupIndex(view);
|
||||
// popupview should be present and not already the topmost view
|
||||
if (popupIndex < 0 || popupIndex == this.popupViews.length - 1) {
|
||||
return reject(new Error(`${view} not found or already at topmost`));
|
||||
}
|
||||
|
||||
// keep the popupViews array in sync with the stacking of the views
|
||||
const currentView = this.popupViews[this.getPopupIndex(view)];
|
||||
this.popupViews.splice(this.getPopupIndex(view), 1);
|
||||
this.popupViews.push(currentView);
|
||||
|
||||
const exitAnimation = this.getViewExitState(view);
|
||||
if (animated && exitAnimation) {
|
||||
this.getExitAnimation(view, exitAnimation)
|
||||
.play()
|
||||
.then(() => {
|
||||
this._bringToFront(view);
|
||||
const initialState = this.getViewInitialState(currentView.view);
|
||||
if (initialState) {
|
||||
this.applyInitialState(view, initialState);
|
||||
this.getEnterAnimation(view, initialState)
|
||||
.play()
|
||||
.then(() => {
|
||||
this._bringToFront(view);
|
||||
const initialState = this.getViewInitialState(currentView.view);
|
||||
if (initialState) {
|
||||
this.applyInitialState(view, initialState);
|
||||
this.getEnterAnimation(view, initialState)
|
||||
.play()
|
||||
.then(() => {
|
||||
this.applyDefaultState(view);
|
||||
})
|
||||
.catch((ex) => {
|
||||
if (Trace.isEnabled()) {
|
||||
Trace.write(`Error playing enter animation: ${ex}`, Trace.categories.Layout, Trace.messageType.error);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.applyDefaultState(view);
|
||||
}
|
||||
this.applyDefaultState(view);
|
||||
})
|
||||
.catch((ex) => {
|
||||
if (Trace.isEnabled()) {
|
||||
Trace.write(`Error playing exit animation: ${ex}`, Trace.categories.Layout, Trace.messageType.error);
|
||||
}
|
||||
this._bringToFront(view);
|
||||
reject(new Error(`Error playing enter animation: ${ex}`));
|
||||
});
|
||||
} else {
|
||||
this._bringToFront(view);
|
||||
this.applyDefaultState(view);
|
||||
}
|
||||
}
|
||||
|
||||
// update shadeCover to reflect topmost's shadeCover options
|
||||
const shadeCoverOptions = currentView?.options?.shadeCover;
|
||||
if (shadeCoverOptions) {
|
||||
this.updateShadeCover(this.shadeCover, shadeCoverOptions);
|
||||
}
|
||||
|
||||
resolve();
|
||||
} else {
|
||||
if (Trace.isEnabled()) {
|
||||
Trace.write(`${view} not found or already at topmost`, Trace.categories.Layout);
|
||||
}
|
||||
}
|
||||
} catch (ex) {
|
||||
if (Trace.isEnabled()) {
|
||||
Trace.write(`Error in bringing view to front: ${ex}`, Trace.categories.Layout, Trace.messageType.error);
|
||||
}
|
||||
})
|
||||
.catch((ex) => {
|
||||
this._bringToFront(view);
|
||||
reject(new Error(`Error playing exit animation: ${ex}`));
|
||||
});
|
||||
} else {
|
||||
this._bringToFront(view);
|
||||
}
|
||||
|
||||
// update shadeCover to reflect topmost's shadeCover options
|
||||
const shadeCoverOptions = currentView?.options?.shadeCover;
|
||||
if (shadeCoverOptions) {
|
||||
this.updateShadeCover(this.shadeCover, shadeCoverOptions);
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -337,7 +315,7 @@ export class RootLayoutBase extends GridLayout {
|
||||
};
|
||||
}
|
||||
|
||||
private createShadeCover(shadeOptions: ShadeCoverOptions): View {
|
||||
private createShadeCover(shadeOptions: ShadeCoverOptions = {}): View {
|
||||
const shadeCover = new GridLayout();
|
||||
shadeCover.verticalAlignment = 'bottom';
|
||||
shadeCover.on('loaded', () => {
|
||||
@@ -347,7 +325,7 @@ export class RootLayoutBase extends GridLayout {
|
||||
return shadeCover;
|
||||
}
|
||||
|
||||
private updateShadeCover(shade: View, shadeOptions: ShadeCoverOptions): void {
|
||||
private updateShadeCover(shade: View, shadeOptions: ShadeCoverOptions = {}): void {
|
||||
if (shadeOptions.tapToClose !== undefined && shadeOptions.tapToClose !== null) {
|
||||
shade.off('tap');
|
||||
if (shadeOptions.tapToClose) {
|
||||
|
||||
Reference in New Issue
Block a user