showModal & closeModal can be called on any View

fix livesync implementation to be compatible with 3.4 and to replace mainEntry for 4.0 when root is not Frame
some refactoring of page.ios in order to allow showing multiple modal dialogs
This commit is contained in:
Hristo Hristov
2018-01-18 13:34:25 +02:00
parent 9dd3e1a807
commit 016c64fe04
20 changed files with 342 additions and 136 deletions

View File

@@ -0,0 +1,3 @@
Button {
background-color: red
}

View File

@@ -0,0 +1,19 @@
<Frame>
<Page backgroundColor="blue" loaded='onNavigatingTo'>
<ActionBar>
<Label text='MIDDLE TEXT' backgroundColor='green' />
</ActionBar> -->
<!-- <TabView backgroundColor="green">
<TabViewItem title="Item One"> -->
<!-- <ScrollView backgroundColor="purple"> -->
<StackLayout backgroundColor="orange" verticalAlignment="center">
<Button text="LiveSync" tap="sync" />
<Button text="Show Modal TabView" tap="onModalTab" />
<Button text="Show Modal Frame" tap="onModalFrame" />
<Button text="requestLayout for titleView" tap="onTap3" />
</StackLayout>
<!-- </ScrollView> -->
<!-- </TabViewItem>
</TabView> -->
</Page>
</Frame>

View File

@@ -0,0 +1,14 @@
<Page backgroundColor="pink" codeFile='~/page.2' actionBarHidden="true">
<Frame>
<Page backgroundColor="purple">
<ActionBar title=" AAA ">
<!-- <ActionBar title="{{ $value, 'Page ' + $value }} "> -->
<ActionItem text="close" tap="closeModal" ios.position='right' />
</ActionBar>
<StackLayout backgroundColor="orange">
<Button text="FORWARD" tap="onTap"/>
<Button text="BACKWARD" tap="onBack"/>
</StackLayout>
</Page>
</Frame>
</Page>

View File

@@ -0,0 +1,12 @@
<Frame codeFile='~/page.2'>
<Page backgroundColor="pink">
<ActionBar title="{{ $value, 'Page ' + $value }} ">
<ActionItem text="close" tap="closeModal" ios.position='right' />
</ActionBar>
<StackLayout backgroundColor="orange">
<Button text="FORWARD" tap="onTap"/>
<Button text="BACKWARD" tap="onBack"/>
<Button text="showModal" tap="showModal"/>
</StackLayout>
</Page>
</Frame>

View File

@@ -0,0 +1,33 @@
<Page backgroundColor="blue" actionBarHidden="true" loaded="pageLoaded"
codeFile='~/modal-tab'>
<ActionBar title="Frame in Modal Page">
<ActionItem text="close" tap="closeModal" />
</ActionBar>
<TabView backgroundColor="magenta">
<TabViewItem title="Button">
<StackLayout verticalAlignment="center" backgroundColor="green" verticalAlignment="bottom" horizontalAlignment="right">
<Button text="Close Modal" tap="tap" />
<Button text="Show Modal Frame" tap="onModalFrame" />
</StackLayout>
</TabViewItem>
<TabViewItem title="Frame">
<ScrollView backgroundColor="cyan">
<StackLayout backgroundColor="pink" verticalAlignment="bottom" >
<Label text="I'm frame in modal page" />
<Label text="Tap the button" />
<Button text="TAP" tap="onTap" />
<Button text="Do 10 go backs" tap="tenGoBacks" />
<Button text="Navigate To Page with Frame" tap="navigateToPageWithFrame" />
</StackLayout>
</ScrollView>
</TabViewItem>
<TabViewItem title="Item 3">
<ScrollView>
<StackLayout verticalAlignment="bottom" horizontalAlignment="right">
<Button text="Close Modal" tap="tap" />
<Button text="Show Modal Frame" tap="onModalFrame" />
</StackLayout>
</ScrollView>
</TabViewItem>
</TabView>
</Page>

View File

@@ -0,0 +1,3 @@
Button {
background-color: white
}

View File

@@ -0,0 +1,43 @@
<TabView backgroundColor="red" loaded="onLoaded">
<TabViewItem title="Button">
<StackLayout backgroundColor="orange"
verticalAlignment="bottom"
horizontalAlignment="right">
<Button text="LIVESYNC" tap="onLiveSync" />
<Button text="Close Modal" tap="tap" />
<Button text="new Frame" tap="newFrame" />
<Button text="Show Modal Frame" tap="onModalFrame" />
<TextView text="text viesw" />
<TextField text="text field" />
<TextField text="text field" />
<Button text="Button" />
</StackLayout>
</TabViewItem>
<TabViewItem title="Frame">
<Frame backgroundColor="green">
<Page backgroundColor="blue" actionBarHidden="true" loaded="pageLoaded">
<Page.actionBar>
<ActionBar title="Frame in Modal Page" />
<ActionItem text="close" tap="closeModal" />
</Page.actionBar>
<ScrollView backgroundColor="cyan">
<StackLayout backgroundColor="pink" verticalAlignment="bottom" >
<Label text="I'm frame in modal page" />
<Label text="Tap the button" />
<Button text="TAP" tap="onTap" />
<Button text="Do 10 go backs" tap="tenGoBacks" />
<Button text="Navigate To Page with Frame" tap="navigateToPageWithFrame" />
</StackLayout>
</ScrollView>
</Page>
</Frame>
</TabViewItem>
<TabViewItem title="Item 3">
<ScrollView>
<StackLayout verticalAlignment="bottom" horizontalAlignment="right">
<Button text="Close Modal" tap="tap" />
<Button text="Show Modal Frame" tap="onModalFrame" />
</StackLayout>
</ScrollView>
</TabViewItem>
</TabView>

View File

@@ -0,0 +1,14 @@
<Page backgroundColor="pink" showingModally="showingModally">
<ActionBar title="{{ $value, 'Page ' + $value }} ">
<ActionItem text="close" tap="closeModal" ios.position='right' />
</ActionBar>
<StackLayout backgroundColor="orange">
<Button text="{{ $value, Modal Page: $value }}" />
<Button text="FORWARD" tap="onTap"/>
<Button text="BACKWARD" tap="onBack"/>
<Button text="Do 10 go backs" tap="tenGoBacks" />
<Button text="Show Modal" tap="showModal"/>
<Button text="Close Modal" tap="closeModal"/>
<Button text="Livesync" tap="onLivesync"/>
</StackLayout>
</Page>

View File

@@ -0,0 +1,8 @@
<Frame>
<Page backgroundColor="blue" >
<StackLayout backgroundColor="orange" verticalAlignment="top">
<Button text="set text" tap='tap' />
<TextView text="text" />
</StackLayout>
</Page>
</Frame>

View File

@@ -16,8 +16,8 @@ export * from "./application-common";
// TODO: Remove this and get it from global to decouple builder for angular
import { createViewFromEntry } from "../ui/builder";
import { ios as iosView, ViewBase } from "../ui/core/view";
import { Frame, View, NavigationEntry } from "../ui/frame";
import { ios as iosView, View } from "../ui/core/view";
import { Frame, NavigationEntry } from "../ui/frame";
import { ios } from "../ui/utils";
import * as utils from "../utils/utils";
import { profile } from "../profiling";
@@ -51,7 +51,7 @@ class IOSApplication implements IOSApplicationDefinition {
private _currentOrientation = utils.ios.getter(UIDevice, UIDevice.currentDevice).orientation;
private _window: UIWindow;
private _observers: Array<NotificationObserver>;
private _rootView: ViewBase;
private _rootView: View;
constructor() {
this._observers = new Array<NotificationObserver>();
@@ -114,18 +114,7 @@ class IOSApplication implements IOSApplicationDefinition {
notify(args);
notify(<LoadAppCSSEventData>{ eventName: "loadAppCss", object: <any>this, cssFile: getCssFileName() });
const rootView = createRootView(args.root);
this._rootView = rootView;
const controller = getViewController(rootView);
this._window.rootViewController = controller;
if (createRootFrame) {
// Don't setup as styleScopeHost
rootView._setupUI({});
} else {
// setup view as styleScopeHost
rootView._setupAsRootView({});
}
this._window.makeKeyAndVisible();
this.setWindowContent(args.root);
}
@profile
@@ -193,12 +182,44 @@ class IOSApplication implements IOSApplicationDefinition {
});
}
}
public _onLivesync(): void {
// If view can't handle livesync set window controller.
if (!this._rootView._onLivesync()) {
this.setWindowContent();
}
}
public setWindowContent(view?: View): void {
const rootView = createRootView(view);
this._rootView = rootView;
const controller = getViewController(rootView);
if (createRootFrame) {
// Don't setup as styleScopeHost
rootView._setupUI({});
} else {
// setup view as styleScopeHost
rootView._setupAsRootView({});
}
const haveController = this._window.rootViewController !== null;
this._window.rootViewController = controller;
if (!haveController) {
this._window.makeKeyAndVisible();
}
}
}
const iosApp = new IOSApplication();
exports.ios = iosApp;
setApplication(iosApp);
// attach on global, so it can be overwritten in NativeScript Angular
(<any>global).__onLiveSyncCore = function () {
iosApp._onLivesync();
}
let mainEntry: NavigationEntry;
function createRootView(v?: View) {
let rootView = v;

View File

@@ -1,7 +1,6 @@
/**
* @module "ui/core/view-base"
*/ /** */
import { Property, CssProperty, CssAnimationProperty, InheritedProperty, Style } from "../properties";
import { BindingOptions, Observable } from "../bindable";
@@ -110,6 +109,37 @@ export abstract class ViewBase extends Observable {
_suspendedUpdates: { [propertyName: string]: Property<ViewBase, any> | CssProperty<Style, any> | CssAnimationProperty<Style, any> };
//@endprivate
/**
* Shows the View contained in moduleName as a modal view.
* @param moduleName - The name of the module to load starting from the application root.
* @param context - Any context you want to pass to the modally shown view.
* This same context will be available in the arguments of the shownModally event handler.
* @param closeCallback - A function that will be called when the view is closed.
* Any arguments provided when calling ShownModallyData.closeCallback will be available here.
* @param fullscreen - An optional parameter specifying whether to show the modal page in full-screen mode.
*/
showModal(moduleName: string, context: any, closeCallback: Function, fullscreen?: boolean, animated?: boolean): ViewBase;
/**
* Shows the view passed as parameter as a modal view.
* @param view - View instance to be shown modally.
* @param context - Any context you want to pass to the modally shown view. This same context will be available in the arguments of the shownModally event handler.
* @param closeCallback - A function that will be called when the view is closed. Any arguments provided when calling ShownModallyData.closeCallback will be available here.
* @param fullscreen - An optional parameter specifying whether to show the modal view in full-screen mode.
*/
showModal(view: ViewBase, context: any, closeCallback: Function, fullscreen?: boolean, animated?: boolean): ViewBase;
/**
* Deprecated. Showing view as modal is deprecated.
* Use showModal method with arguments.
*/
showModal(): ViewBase;
/**
* Closes the current modal view that this page is showing.
*/
closeModal(): void;
public effectiveMinWidth: number;
public effectiveMinHeight: number;
public effectiveWidth: number;
@@ -139,7 +169,7 @@ export abstract class ViewBase extends Observable {
public ios: any;
public android: any;
/**
* returns the native UIViewController.
*/
@@ -337,7 +367,7 @@ export abstract class ViewBase extends Observable {
*/
public _isPaddingRelative: boolean;
public _styleScope: any;
/**
* @private
*/
@@ -365,12 +395,12 @@ export abstract class ViewBase extends Observable {
* @private
*/
_inheritStyleScope(styleScope: any /* StyleScope */): void;
/**
* @private
*/
callLoaded(): void;
/**
* @private
*/

View File

@@ -944,6 +944,18 @@ export abstract class ViewBase extends Observable implements ViewBaseDefinition
});
}
}
public showModal(): ViewBase {
const parent = this.parent;
return parent && parent.showModal();
}
public closeModal(): void {
const parent = this.parent;
if (parent) {
parent.closeModal();
}
}
}
ViewBase.prototype.isCollapsed = false;

View File

@@ -50,14 +50,17 @@ export function PseudoClassHandler(...pseudoClasses: string[]): MethodDecorator
};
}
export const _rootModalViews = new Array<ViewBase>();
export abstract class ViewCommon extends ViewBase implements ViewDefinition {
public static shownModallyEvent = "shownModally";
public static showingModallyEvent = "showingModally";
protected _closeModalCallback: Function;
public _modalParent: ViewCommon;
private _modalContext: any;
public _modal: ViewCommon;
private _modal: ViewCommon;
private _measuredWidth: number;
private _measuredHeight: number;
@@ -188,6 +191,12 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition {
}
}
_onLivesync(): boolean {
_rootModalViews.forEach(v => v.closeModal());
_rootModalViews.length = 0;
return false;
}
public _onBackPressed(): boolean {
return false;
}
@@ -215,8 +224,14 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition {
}
public closeModal() {
if (this._closeModalCallback) {
this._closeModalCallback.apply(undefined, arguments);
let closeCallback = this._closeModalCallback;
if (closeCallback) {
closeCallback.apply(undefined, arguments);
} else {
let parent = this.parent;
if (parent) {
parent.closeModal();
}
}
}
@@ -225,14 +240,21 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition {
}
protected _showNativeModalView(parent: ViewCommon, context: any, closeCallback: Function, fullscreen?: boolean, animated?: boolean) {
_rootModalViews.push(this);
parent._modal = this;
this._modalParent = parent;
this._modalContext = context;
const that = this;
this._closeModalCallback = function () {
if (that._closeModalCallback) {
that._closeModalCallback = null;
that._modalContext = null;
const modalIndex = _rootModalViews.indexOf(that);
_rootModalViews.splice(modalIndex);
that._hideNativeModalView(parent);
that._modalParent = null
that._modalContext = null;
that._closeModalCallback = null;
parent._modal = null;
if (typeof closeCallback === "function") {
closeCallback.apply(undefined, arguments);
}
@@ -938,7 +960,7 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition {
_onAttachedToWindow(): void {
//
}
_onDetachedFromWindow(): void {
//
}

View File

@@ -489,7 +489,6 @@ export class View extends ViewCommon {
}
this._dialogFragment = null;
parent._modal = undefined;
super._hideNativeModalView(parent);
}

View File

@@ -481,14 +481,14 @@ export abstract class View extends ViewBase {
on(event: "shownModally", callback: (args: ShownModallyData) => void, thisArg?: any);
/**
* Shows the View contained in moduleName as a modal view.
* @param moduleName - The name of the module to load starting from the application root.
* @param context - Any context you want to pass to the modally shown view.
* This same context will be available in the arguments of the shownModally event handler.
* @param closeCallback - A function that will be called when the view is closed.
* Any arguments provided when calling ShownModallyData.closeCallback will be available here.
* @param fullscreen - An optional parameter specifying whether to show the modal page in full-screen mode.
*/
* Shows the View contained in moduleName as a modal view.
* @param moduleName - The name of the module to load starting from the application root.
* @param context - Any context you want to pass to the modally shown view.
* This same context will be available in the arguments of the shownModally event handler.
* @param closeCallback - A function that will be called when the view is closed.
* Any arguments provided when calling ShownModallyData.closeCallback will be available here.
* @param fullscreen - An optional parameter specifying whether to show the modal page in full-screen mode.
*/
showModal(moduleName: string, context: any, closeCallback: Function, fullscreen?: boolean, animated?: boolean): View;
/**
@@ -546,6 +546,26 @@ export abstract class View extends ViewBase {
*/
public getActualSize(): Size;
/**
* @private
* A valid css string which will be applied for all nested UI components (based on css rules).
*/
css: string;
/**
* @private
* Adds a new values to current css.
* @param cssString - A valid css which will be added to current css.
*/
addCss(cssString: string): void;
/**
* @private
* Adds the content of the file to the current css.
* @param cssFileName - A valid file name (from the application root) which contains a valid css.
*/
addCssFile(cssFileName: string): void;
// Lifecycle events
_getNativeViewsCount(): number;
@@ -554,10 +574,6 @@ export abstract class View extends ViewBase {
public eachChildView(callback: (view: View) => boolean): void;
//@private
/**
* @private
*/
_modal: View;
/**
* @private
*/
@@ -630,6 +646,10 @@ export abstract class View extends ViewBase {
* @private
*/
_removeAnimation(animation: Animation): boolean;
/**
* @private
*/
_onLivesync(): boolean;
/**
* @private
*/
@@ -639,26 +659,6 @@ export abstract class View extends ViewBase {
*/
_getFragmentManager(): any; /* android.app.FragmentManager */
/**
* @private
* A valid css string which will be applied for all nested UI components (based on css rules).
*/
css: string;
/**
* @private
* Adds a new values to current css.
* @param cssString - A valid css which will be added to current css.
*/
addCss(cssString: string): void;
/**
* @private
* Adds the content of the file to the current css.
* @param cssFileName - A valid file name (from the application root) which contains a valid css.
*/
addCssFile(cssFileName: string): void;
/**
* Updates styleScope or create new styleScope.
* @param cssFileName
@@ -666,12 +666,12 @@ export abstract class View extends ViewBase {
* @param css
*/
_updateStyleScope(cssFileName?: string, cssString?: string, css?: string): void;
/**
* Called in android when native view is attached to window.
*/
_onAttachedToWindow(): void;
/**
* Called in android when native view is dettached from window.
*/

View File

@@ -43,8 +43,6 @@ export class View extends ViewCommon {
*/
_nativeBackgroundState: "unset" | "invalid" | "drawn";
public _modalParent: View;
get isLayoutRequired(): boolean {
return (this._privateFlags & PFLAG_LAYOUT_REQUIRED) === PFLAG_LAYOUT_REQUIRED;
}
@@ -303,8 +301,20 @@ export class View extends ViewCommon {
return this._suspendCATransaction || this._suspendNativeUpdatesCount;
}
private getParentWithViewController(parent: View): View {
let view = parent;
let controller = view.viewController;
while (!controller) {
view = view.parent as View;
controller = view.viewController;
}
return view;
}
protected _showNativeModalView(parent: View, context: any, closeCallback: Function, fullscreen?: boolean, animated?: boolean) {
super._showNativeModalView(parent, context, closeCallback, fullscreen);
let parentWithController = this.getParentWithViewController(parent);
super._showNativeModalView(parentWithController, context, closeCallback, fullscreen);
let controller = this.viewController;
if (!controller) {
controller = ios.UILayoutViewController.initWithOwner(new WeakRef(this));
@@ -312,9 +322,8 @@ export class View extends ViewCommon {
}
this._setupAsRootView({});
this._modalParent = parent;
const parentController = parent.viewController;
const parentController = parentWithController.viewController;
if (!parentController.view.window) {
throw new Error("Parent page is not part of the window hierarchy. Close the current modal page before showing another one!");
}
@@ -343,9 +352,9 @@ export class View extends ViewCommon {
protected _hideNativeModalView(parent: View) {
const parentController = parent.viewController;
const animated = (<any>this.viewController).animated;
parentController.dismissModalViewControllerAnimated(animated);
super._hideNativeModalView(parent);
parentController.dismissModalViewControllerAnimated(animated);
}
[isEnabledProperty.getDefault](): boolean {

View File

@@ -526,6 +526,33 @@ export class FrameBase extends CustomLayoutView implements FrameDefinition {
return result;
}
public _onLivesync(): boolean {
super._onLivesync();
const currentEntry = this._currentEntry.entry;
const newEntry: NavigationEntry = {
animated: false,
clearHistory: true,
context: currentEntry.context,
create: currentEntry.create,
moduleName: currentEntry.moduleName,
backstackVisible: currentEntry.backstackVisible
}
// If create returns the same page instance we can't recreate it.
// Instead of navigation set activity content.
// This could happen if current page was set in XML as a Page instance.
if (newEntry.create) {
const page = newEntry.create();
if (page === this.currentPage) {
return false;
}
}
this.navigate(newEntry);
return true;
}
}
export function topmost(): FrameBase {

View File

@@ -73,8 +73,12 @@ function getAttachListener(): android.view.View.OnAttachStateChangeListener {
}
export function reloadPage(): void {
// Delete previously cached root view in order to recreate it.
resetActivityContent(application.android.foregroundActivity);
const app = application.android;
const rootView: View = (<any>app).rootView;
if (!rootView || !rootView._onLivesync()) {
// Delete previously cached root view in order to recreate it.
resetActivityContent(application.android.foregroundActivity);
}
}
// attach on global, so it can be overwritten in NativeScript Angular

View File

@@ -20,30 +20,6 @@ const DELEGATE = "_delegate";
let navDepth = -1;
export function reloadPage(): void {
const frame = topmost();
if (frame) {
if (frame.currentPage && frame.currentPage.modal) {
frame.currentPage.modal.closeModal();
}
const currentEntry = frame._currentEntry.entry;
const newEntry: NavigationEntry = {
animated: false,
clearHistory: true,
context: currentEntry.context,
create: currentEntry.create,
moduleName: currentEntry.moduleName,
backstackVisible: currentEntry.backstackVisible
}
frame.navigate(newEntry);
}
}
// attach on global, so it can be overwritten in NativeScript Angular
(<any>global).__onLiveSyncCore = reloadPage;
export class Frame extends FrameBase {
public viewController: UINavigationControllerImpl;
public _animatedDelegate = <UINavigationControllerDelegate>UINavigationControllerAnimatedDelegate.new();

View File

@@ -90,12 +90,6 @@ class UIViewControllerImpl extends UIViewController {
owner.onNavigatingTo(newEntry.entry.context, isBack, newEntry.entry.bindingContext);
}
// Add page to frame if showing modal page.
// TODO: This needs refactoring.
if (modalParent) {
modalParent._addView(owner);
}
if (frame) {
if (!owner.parent) {
owner._frame = frame;
@@ -206,47 +200,10 @@ class UIViewControllerImpl extends UIViewController {
return;
}
const modalParent = page._modalParent;
page._modalParent = undefined;
// Clear up after modal page has closed.
if (modalParent) {
modalParent._modal = undefined;
modalParent._removeView(page);
}
// Manually pop backStack when Back button is pressed or navigating back with edge swipe.
// Don't pop if we are hiding modally shown page.
// const frame = page.frame;
// We are not modal page, have frame with backstack and navigation queue is empty and currentPage is closed
// then pop our backstack.
// If we are in frame wich is in tab and tab.selectedControler is not the frame
// skip navigation.
// const tab = this.tabBarController;
// const fireNavigationEvents = !tab
// || tab.selectedViewController === this.navigationController;
// Remove from parent if page was in frame and we navigated back or
// navigate forward but current entry is not backstack visible.
// Showing page modally will not pass isBack check so currentPage won't be removed from Frame.
// const isBack = isBackNavigationFrom(this, page);
// if (frame && page.frame === frame &&
// (isBack || !frame._isCurrentEntryBackstackVisible)) {
// // Remove parent when navigating back.
// frame._removeBackstackEntries([_removeBackstackEntries])
// frame._removeView(page);
// page._frame = null;
// }
// Forward navigation does not remove page from frame so we raise unloaded manually.
if (page.isLoaded) {
page.callUnloaded();
}
// if (!modalParent && fireNavigationEvents) {
// // Last raise onNavigatedFrom event if we are not modally shown.
// page.onNavigatedFrom(isBack);
// }
}
public viewWillLayoutSubviews(): void {