Resolved #142: Modal views.

This commit is contained in:
Rossen Hristov
2015-05-18 14:05:19 +03:00
parent 3ac52814c7
commit 90722cfc67
17 changed files with 285 additions and 74 deletions

View File

@ -92,6 +92,13 @@
<TypeScriptCompile Include="apps\template-blank\main-page.ts">
<DependentUpon>main-page.xml</DependentUpon>
</TypeScriptCompile>
<TypeScriptCompile Include="apps\modal-views-demo\app.ts" />
<TypeScriptCompile Include="apps\modal-views-demo\main-page.ts">
<DependentUpon>main-page.xml</DependentUpon>
</TypeScriptCompile>
<TypeScriptCompile Include="apps\modal-views-demo\login-page.ts">
<DependentUpon>login-page.xml</DependentUpon>
</TypeScriptCompile>
<TypeScriptCompile Include="apps\template-master-detail\main-page.ts" />
<TypeScriptCompile Include="apps\template-settings\app.ts" />
<TypeScriptCompile Include="apps\template-settings\main-page.ts">
@ -587,6 +594,7 @@
<Content Include="apps\gallery-app\views\segmented-bar.xml" />
<Content Include="apps\gallery-app\views\time-picker.xml" />
<Content Include="apps\gallery-app\views\date-picker.xml" />
<Content Include="apps\modal-views-demo\login-page.xml" />
<Content Include="apps\pickers-demo\main-page.xml">
<SubType>Designer</SubType>
</Content>
@ -594,6 +602,7 @@
<Content Include="apps\placeholder-demo\main-page.xml" />
<Content Include="apps\TelerikNEXT\lib\everlive.js" />
<Content Include="apps\TelerikNEXT\session-page.xml" />
<Content Include="apps\modal-views-demo\main-page.xml" />
<Content Include="apps\template-master-detail\details-view.xml" />
<Content Include="apps\template-master-detail\main-page.minWH600.xml" />
<Content Include="apps\TelerikNEXT\images\background.jpg" />
@ -1494,6 +1503,9 @@
<Content Include="apps\placeholder-demo\package.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="apps\modal-views-demo\package.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<None Include="js-libs\esprima\LICENSE.BSD" />
<Content Include="source-control.md" />
<Content Include="ui\segmented-bar\package.json">

View File

@ -31,48 +31,7 @@ class Window extends UIWindow {
}
public layoutSubviews(): void {
if (!this._content) {
// TODO: Invalid setup, throw an exception?
return;
}
var statusFrame = UIApplication.sharedApplication().statusBarFrame;
var statusBarHeight = 0;
try {
statusBarHeight = Math.min(statusFrame.size.width, statusFrame.size.height);
} catch (ex) {
console.log("exception: " + ex);
}
var isLandscape = utils.ios.isLandscape();
var iOSMajorVersion = utils.ios.MajorVersion;
// in iOS 8 when in landscape statusbar is hidden.
if (isLandscape && iOSMajorVersion > 7) {
statusBarHeight = 0;
}
var deviceFrame = UIScreen.mainScreen().bounds;
var size = deviceFrame.size;
var width = size.width;
var height = size.height;
// in iOS 7 when in landscape we switch width with height because on device they don't change even when rotated.
if (iOSMajorVersion < 8 && isLandscape) {
width = size.height;
height = size.width;
}
var origin = deviceFrame.origin;
var left = origin.x;
var top = origin.y + statusBarHeight;
var widthSpec = utils.layout.makeMeasureSpec(width, utils.layout.EXACTLY);
var heightSpec = utils.layout.makeMeasureSpec(height - statusBarHeight, utils.layout.EXACTLY);
this._content.measure(widthSpec, heightSpec);
this._content.layout(left, top, width, height);
utils.ios._layoutRootView(this._content);
}
}

View File

@ -0,0 +1,3 @@
import application = require("application");
application.mainModule = "main-page";
application.start();

View File

@ -0,0 +1,32 @@
import observable = require("data/observable");
import pages = require("ui/page");
import textField = require("ui/text-field");
var context: any;
var closeCallback: Function;
var page: pages.Page;
var usernameTextField: textField.TextField;
var passwordTextField: textField.TextField;
export function onShownModally(args: pages.ShownModallyData) {
console.log("login-page.onShownModally, context: " + args.context);
context = args.context;
closeCallback = args.closeCallback;
}
export function onLoaded(args: observable.EventData) {
console.log("login-page.onLoaded");
page = <pages.Page>args.object;
usernameTextField = page.getViewById<textField.TextField>("username");
passwordTextField = page.getViewById<textField.TextField>("password");
}
export function onUnloaded() {
console.log("login-page.onUnloaded");
}
export function onLoginButtonTap() {
console.log("login-page.onLoginButtonTap");
closeCallback(usernameTextField.text, passwordTextField.text);
}

View File

@ -0,0 +1,7 @@
<Page xmlns="http://www.nativescript.org/tns.xsd" shownModally="onShownModally" loaded="onLoaded" unloaded="onUnloaded">
<StackLayout>
<TextField hint="username" id="username"/>
<TextField hint="password" id="password" secure="true"/>
<Button text="Login" tap="onLoginButtonTap"/>
</StackLayout>
</Page>

View File

@ -0,0 +1,18 @@
import observable = require("data/observable");
import pages = require("ui/page");
import labelModule = require("ui/label");
var page: pages.Page;
var label: labelModule.Label;
export function pageLoaded(args: observable.EventData) {
page = <pages.Page>args.object;
label = page.getViewById<labelModule.Label>("label");
}
export function onTap(args: observable.EventData) {
page.showModal("./modal-views-demo/login-page", "some custom context", function(username: string, password: string) {
console.log(username + "/" + password);
label.text = username + "/" + password;
});
}

View File

@ -0,0 +1,6 @@
<Page xmlns="http://www.nativescript.org/tns.xsd" loaded="pageLoaded" id="_mainPage">
<StackLayout>
<Button text="Login" tap="onTap" />
<Label id="label" text="Anonymous"/>
</StackLayout>
</Page>

View File

@ -0,0 +1,2 @@
{ "name" : "modal-views-demo",
"main" : "app.js" }

View File

@ -171,9 +171,23 @@ export class View extends viewCommon.View {
public layoutNativeView(left: number, top: number, right: number, bottom: number): void {
var frame = CGRectMake(left, top, right - left, bottom - top);
if (!CGRectEqualToRect(this._nativeView.frame, frame)) {
trace.write(this + ", Native setFrame: " + NSStringFromCGRect(frame), trace.categories.Layout);
this._nativeView.frame = frame;
// This is done because when rotated in iOS7 there is rotation applied on the first subview on the Window which is our frame.nativeView.view.
// If we set it it should be transformed so it is correct.
// When in landscape in iOS 7 there is transformation on the first subview of the window so we set frame to its subview.
// in iOS 8 we set frame to subview again otherwise we get clipped.
var nativeView: UIView;
if (!this.parent && this._nativeView.subviews.count > 0) {
trace.write(this + " has no parent. Setting frame to first child instead.", trace.categories.Layout);
nativeView = (<UIView>this._nativeView.subviews[0]);
}
else {
nativeView = this._nativeView;
}
if (!CGRectEqualToRect(nativeView.frame, frame)) {
trace.write(this + ", Native setFrame: = " + NSStringFromCGRect(frame), trace.categories.Layout);
nativeView.frame = frame;
}
}

View File

@ -30,7 +30,7 @@ function buildEntryFromArgs(arg: any): definition.NavigationEntry {
return entry;
}
function resolvePageFromEntry(entry: definition.NavigationEntry): pages.Page {
export function resolvePageFromEntry(entry: definition.NavigationEntry): pages.Page {
var page: pages.Page;
if (entry.create) {

View File

@ -137,29 +137,6 @@ export class Frame extends frameCommon.Frame {
}
}
public layoutNativeView(left: number, top: number, right: number, bottom: number): void {
// We don't call super here because we set frame on our first subview.
// This is done because when rotated in iOS7 there is rotation applied on the first subview on the Window which is our frame.nativeView.view.
// If we set it it should be transformed so it is correct.
var frame = CGRectMake(left, top, right - left, bottom - top);
var nativeView: UIView;
// When in landscape in iOS 7 there is transformation on the first subview of the window so we set frame to its subview.
// in iOS 8 we set frame to subview again otherwise we get clipped.
if (!this.parent && this._nativeView.subviews.count > 0) {
nativeView = (<UIView>this._nativeView.subviews[0]);
}
else {
nativeView = this._nativeView;
}
if (!CGRectEqualToRect(nativeView.frame, frame)) {
trace.write(this + ", Native setFrame: " + NSStringFromCGRect(frame), trace.categories.Layout);
nativeView.frame = frame;
}
}
protected get navigationBarHeight(): number {
var navigationBar = this._ios.controller.navigationBar;
return (navigationBar && !this._ios.controller.navigationBarHidden) ? navigationBar.frame.size.height : 0;

View File

@ -8,6 +8,7 @@ import fileSystemAccess = require("file-system/file-system-access");
import bindable = require("ui/core/bindable");
import dependencyObservable = require("ui/core/dependency-observable");
import enums = require("ui/enums");
import frameCommon = require("ui/frame/frame-common");
var OPTIONS_MENU = "optionsMenu";
@ -17,6 +18,7 @@ export module knownCollections {
export class Page extends contentView.ContentView implements dts.Page, view.AddArrayFromBuilder {
public static navigatedToEvent = "navigatedTo";
public static shownModallyEvent = "shownModally";
private _navigationContext: any;
@ -114,6 +116,34 @@ export class Page extends contentView.ContentView implements dts.Page, view.AddA
this._navigationContext = undefined;
}
public showModal(moduleName: string, context: any, closeCallback: Function) {
var page = frameCommon.resolvePageFromEntry({ moduleName: moduleName });
(<Page>page)._showNativeModalView(this, context, closeCallback);
}
protected _showNativeModalView(parent: Page, context: any, closeCallback: Function) {
//
}
protected _hideNativeModalView(parent: Page) {
//
}
protected _raiseShownModallyEvent(parent: Page, context: any, closeCallback: Function) {
var that = this;
var closeProxy = function () {
that._hideNativeModalView(parent);
closeCallback.apply(undefined, arguments);
};
this.notify({
eventName: Page.shownModallyEvent,
object: this,
context: context,
closeCallback: closeProxy
});
}
public _getStyleScope(): styleScope.StyleScope {
return this._styleScope;
}

View File

@ -1,10 +1,33 @@
import pageCommon = require("ui/page/page-common");
import definition = require("ui/page");
import trace = require("trace");
import color = require("color");
declare var exports;
require("utils/module-merge").merge(pageCommon, exports);
class DialogFragmentClass extends android.app.DialogFragment {
private _owner: Page;
constructor(owner: Page) {
super();
this._owner = owner;
return global.__native(this);
}
public onCreateDialog(savedInstanceState: android.os.Bundle): android.app.Dialog {
var dialog = new android.app.Dialog(this._owner._context);
dialog.requestWindowFeature(android.view.Window.FEATURE_NO_TITLE);
dialog.setContentView(this._owner._nativeView);
var window = dialog.getWindow();
window.setBackgroundDrawable(new android.graphics.drawable.ColorDrawable(android.graphics.Color.TRANSPARENT));
window.setLayout(android.view.ViewGroup.LayoutParams.FILL_PARENT, android.view.ViewGroup.LayoutParams.FILL_PARENT);
return dialog;
}
};
export class Page extends pageCommon.Page {
private _isBackNavigation = false;
@ -34,4 +57,29 @@ export class Page extends pageCommon.Page {
this.frame.android.activity.invalidateOptionsMenu();
}
}
private _dialogFragment: DialogFragmentClass;
protected _showNativeModalView(parent: Page, context: any, closeCallback: Function) {
if (!this.backgroundColor) {
this.backgroundColor = new color.Color("White");
}
this._onAttached(parent._context);
this._isAddedToNativeVisualTree = true;
this.onLoaded();
this._dialogFragment = new DialogFragmentClass(this);
this._dialogFragment.show(parent.frame.android.activity.getFragmentManager(), "dialog");
super._raiseShownModallyEvent(parent, context, closeCallback);
}
protected _hideNativeModalView(parent: Page) {
this._dialogFragment.dismissAllowingStateLoss();
this._dialogFragment = null;
this.onUnloaded();
this._isAddedToNativeVisualTree = false;
this._onDetached(true);
}
}

33
ui/page/page.d.ts vendored
View File

@ -23,6 +23,21 @@ declare module "ui/page" {
context: any;
}
/**
* Defines the data for the Page.shownModally event.
*/
export interface ShownModallyData extends observable.EventData {
/**
* The context (optional, may be undefined) passed to the page when shown modally.
*/
context: any;
/**
* A callback to call when you want to close the modally shown page. Pass in any kind of arguments and you will receive when the callback parameter of Page.showModal is executed.
*/
closeCallback: Function;
}
export module knownCollections {
export var optionsMenu: string;
}
@ -106,6 +121,24 @@ declare module "ui/page" {
*/
on(event: "navigatedTo", callback: (args: NavigatedData) => void, thisArg?: any);
/**
* String value used when hooking to shownModally event.
*/
public static shownModallyEvent: string;
/**
* Raised when the page is shown as a modal dialog.
*/
on(event: "shownModally", callback: (args: ShownModallyData) => void, thisArg?: any);
/**
* Shows the page contained in moduleName as a modal view.
* @param moduleName - The name of the page module to load starting from the application root.
* @param context - Any context you want to pass to the modally shown page. This same context will be available in the arguments of the Page.shownModally event handler.
* @param closeCallback - A function that will be called when the page is closed. Any arguments provided when calling ShownModallyData.closeCallback will be available here.
*/
showModal(moduleName: string, context: any, closeCallback: Function);
_addArrayFromBuilder(name: string, value: Array<any>): void;
//@private

View File

@ -3,6 +3,7 @@ import definition = require("ui/page");
import viewModule = require("ui/core/view");
import imageSource = require("image-source");
import trace = require("trace");
import utils = require("utils/utils");
declare var exports;
require("utils/module-merge").merge(pageCommon, exports);
@ -20,7 +21,15 @@ class UIViewControllerImpl extends UIViewController {
return this;
}
public didRotateFromInterfaceOrientation(fromInterfaceOrientation: number) {
trace.write(this._owner + " didRotateFromInterfaceOrientation(" + fromInterfaceOrientation+ ")", trace.categories.ViewHierarchy);
if (this._owner._isModal) {
utils.ios._layoutRootView(this._owner);
}
}
public viewDidLoad() {
trace.write(this._owner + " viewDidLoad", trace.categories.ViewHierarchy);
this.view.autoresizesSubviews = false;
this.view.autoresizingMask = UIViewAutoresizing.UIViewAutoresizingNone;
}
@ -32,7 +41,6 @@ class UIViewControllerImpl extends UIViewController {
public viewWillAppear() {
trace.write(this._owner + " viewWillAppear", trace.categories.Navigation);
this._owner._enableLoadedEvents = true;
this._owner.onLoaded();
this._owner._enableLoadedEvents = false;
@ -40,17 +48,16 @@ class UIViewControllerImpl extends UIViewController {
public viewDidDisappear() {
trace.write(this._owner + " viewDidDisappear", trace.categories.Navigation);
this._owner._enableLoadedEvents = true;
this._owner.onUnloaded();
this._owner._enableLoadedEvents = false;
}
}
export class Page extends pageCommon.Page {
private _ios: UIViewController;
public _enableLoadedEvents: boolean;
public _isModal = false;
constructor(options?: definition.Options) {
super(options);
@ -113,7 +120,7 @@ export class Page extends pageCommon.Page {
this.populateMenuItems();
}
populateMenuItems() {
public populateMenuItems() {
var items = this.optionsMenu.getItems();
var navigationItem: UINavigationItem = (<UIViewController>this.ios).navigationItem;
@ -139,6 +146,21 @@ export class Page extends pageCommon.Page {
navigationItem.setRightBarButtonItemsAnimated(array, true);
}
protected _showNativeModalView(parent: Page, context: any, closeCallback: Function) {
this._isModal = true;
utils.ios._layoutRootView(this);
var that = this;
parent.ios.presentViewControllerAnimatedCompletion(this._ios, false, function completion() {
that._raiseShownModallyEvent(parent, context, closeCallback);
});
}
protected _hideNativeModalView(parent: Page) {
parent._ios.dismissModalViewControllerAnimated(false);
this._isModal = false;
}
}
class TapBarItemHandlerImpl extends NSObject {
@ -160,4 +182,4 @@ class TapBarItemHandlerImpl extends NSObject {
public static ObjCExposedMethods = {
"tap": { returns: interop.types.void, params: [interop.types.id] }
};
}
}

3
utils/utils.d.ts vendored
View File

@ -1,5 +1,6 @@
declare module "utils/utils" {
import colorModule = require("color");
import view = require("ui/core/view");
/**
* Utility module related to layout.
@ -118,6 +119,8 @@
* Gets the iOS device major version (for 8.1 will return 8).
*/
export var MajorVersion: number;
export function _layoutRootView(rootView: view.View): void;
}
/**
* An utility function that copies properties from source object to target object.

View File

@ -1,5 +1,6 @@
import common = require("utils/utils-common");
import colorModule = require("color");
import view = require("ui/core/view");
// merge the exports of the common file with the exports of this file
declare var exports;
@ -77,6 +78,50 @@ export module ios {
}
export var MajorVersion = NSString.stringWithString(UIDevice.currentDevice().systemVersion).intValue;
export function _layoutRootView(rootView: view.View) {
if (!rootView) {
return;
}
var statusFrame = UIApplication.sharedApplication().statusBarFrame;
var statusBarHeight = 0;
try {
statusBarHeight = Math.min(statusFrame.size.width, statusFrame.size.height);
} catch (ex) {
console.log("exception: " + ex);
}
var landscape = isLandscape();
var iOSMajorVersion = MajorVersion;
// in iOS 8 when in landscape statusbar is hidden.
if (landscape && iOSMajorVersion > 7) {
statusBarHeight = 0;
}
var deviceFrame = UIScreen.mainScreen().bounds;
var size = deviceFrame.size;
var width = size.width;
var height = size.height;
// in iOS 7 when in landscape we switch width with height because on device they don't change even when rotated.
if (iOSMajorVersion < 8 && landscape) {
width = size.height;
height = size.width;
}
var origin = deviceFrame.origin;
var left = origin.x;
var top = origin.y + statusBarHeight;
var widthSpec = layout.makeMeasureSpec(width, common.layout.EXACTLY);
var heightSpec = layout.makeMeasureSpec(height - statusBarHeight, common.layout.EXACTLY);
rootView.measure(widthSpec, heightSpec);
rootView.layout(left, top, width, height);
}
}
export function GC() {