Implemented showModal on View

This commit is contained in:
Hristo Hristov
2017-12-08 16:53:35 +02:00
parent 04c1132541
commit ac04ede97e
37 changed files with 1973 additions and 1526 deletions

View File

@@ -199,6 +199,18 @@ export abstract class ViewBase extends Observable {
*/
public getViewById<T extends ViewBase>(id: string): T;
/**
* Load view.
* @param view to load.
*/
public loadView(view: ViewBase): void;
/**
* Unload view.
* @param view to unload.
*/
public unloadView(view: ViewBase): void;
public onLoaded(): void;
public onUnloaded(): void;
public onResumeNativeUpdates(): void;
@@ -348,6 +360,16 @@ export abstract class ViewBase extends Observable {
* @private
*/
_inheritStyleScope(styleScope: any /* StyleScope */): void;
/**
* @private
*/
callLoaded(): void;
/**
* @private
*/
callUnloaded(): void;
//@endprivate
}

View File

@@ -140,10 +140,36 @@ let viewIdCounter = 1;
// list.push(new WeakRef(view.nativeViewProtected));
// }
enum Flags {
superOnLoadedCalled = 'Loaded',
superOnUnloadedCalled = 'Unloaded',
}
enum SuspendType {
Incremental = 0,
Loaded = 1 << 20,
NativeView = 1 << 21,
UISetup = 1 << 22,
IncrementalCountMask = ~((1 << 20) + (1 << 21) + (1 << 22))
}
namespace SuspendType {
export function toString(type: SuspendType): string {
return (type ? "suspended" : "resumed") + "(" +
"Incremental: " + (type & SuspendType.IncrementalCountMask) + ", " +
"Loaded: " + !(type & SuspendType.Loaded) + ", " +
"NativeView: " + !(type & SuspendType.NativeView) + ", " +
"UISetup: " + !(type & SuspendType.UISetup) +
")";
}
}
export abstract class ViewBase extends Observable implements ViewBaseDefinition {
public static loadedEvent = "loaded";
public static unloadedEvent = "unloaded";
private _onLoadedCalled: boolean = false;
private _onUnloadedCalled: boolean = false;
private _iosView: Object;
private _androidView: Object;
private _style: Style;
@@ -170,7 +196,7 @@ export abstract class ViewBase extends Observable implements ViewBaseDefinition
public _cssState: ssm.CssState = new ssm.CssState(this);
public _styleScope: ssm.StyleScope;
public _suspendedUpdates: { [propertyName: string]: Property<ViewBase, any> | CssProperty<Style, any> | CssAnimationProperty<Style, any> };
public _suspendNativeUpdatesCount: number;
public _suspendNativeUpdatesCount: SuspendType;
// Dynamic properties.
left: Length;
@@ -303,55 +329,109 @@ export abstract class ViewBase extends Observable implements ViewBaseDefinition
@profile
public onLoaded() {
this.setFlag(Flags.superOnLoadedCalled, true);
if (this._isLoaded) {
return;
}
this._isLoaded = true;
this._cssState.onLoaded();
this._resumeNativeUpdates();
this._loadEachChild();
this._emit("loaded");
}
this._resumeNativeUpdates(SuspendType.Loaded);
public _loadEachChild() {
this.eachChild((child) => {
child.onLoaded();
this.eachChild(child => {
this.loadView(child);
return true;
});
this._emit("loaded");
}
@profile
public onUnloaded() {
this._suspendNativeUpdates();
this._unloadEachChild();
this.setFlag(Flags.superOnUnloadedCalled, true);
if (!this._isLoaded) {
return;
}
this._suspendNativeUpdates(SuspendType.Loaded);
this.eachChild(child => {
this.unloadView(child);
return true;
});
this._isLoaded = false;
this._cssState.onUnloaded();
this._emit("unloaded");
}
public _suspendNativeUpdates(): void {
this._suspendNativeUpdatesCount++;
public _suspendNativeUpdates(type: SuspendType): void {
if (type) {
this._suspendNativeUpdatesCount = this._suspendNativeUpdatesCount | type;
} else {
this._suspendNativeUpdatesCount++;
}
}
public _resumeNativeUpdates(): void {
this._suspendNativeUpdatesCount--;
public _resumeNativeUpdates(type: SuspendType): void {
if (type) {
this._suspendNativeUpdatesCount = this._suspendNativeUpdatesCount & ~type;
} else {
if ((this._suspendNativeUpdatesCount & SuspendType.IncrementalCountMask) === 0) {
throw new Error(`Invalid call to ${this}._resumeNativeUpdates`);
}
this._suspendNativeUpdatesCount--;
}
if (!this._suspendNativeUpdatesCount) {
this.onResumeNativeUpdates();
}
}
public _batchUpdate<T>(callback: () => T): T {
try {
this._suspendNativeUpdates();
this._suspendNativeUpdates(SuspendType.Incremental);
return callback();
} finally {
this._resumeNativeUpdates();
this._resumeNativeUpdates(SuspendType.Incremental);
}
}
private _unloadEachChild() {
this.eachChild((child) => {
if (child.isLoaded) {
child.onUnloaded();
}
private setFlag(flag: Flags, value: boolean): void {
switch (flag) {
case Flags.superOnLoadedCalled:
this._onLoadedCalled = value;
break;
return true;
});
case Flags.superOnUnloadedCalled:
this._onUnloadedCalled = value;
break;
}
}
private isFlagSet(flag: Flags): boolean {
switch (flag) {
case Flags.superOnLoadedCalled:
return this._onLoadedCalled;
case Flags.superOnUnloadedCalled:
return this._onUnloadedCalled;
}
}
private callFunctionWithSuper(flag: Flags, func: () => void): void {
this.setFlag(flag, false);
func();
if (!this.isFlagSet(flag)) {
throw new Error(`super.${flag} not called in ${this}`);
}
}
public callLoaded(): void {
this.callFunctionWithSuper(Flags.superOnLoadedCalled, () => this.onLoaded());
}
public callUnloaded(): void {
this.callFunctionWithSuper(Flags.superOnUnloadedCalled, () => this.onUnloaded());
}
private notifyPseudoClassChanged(pseudoClass: string): void {
@@ -369,7 +449,7 @@ export abstract class ViewBase extends Observable implements ViewBaseDefinition
public cssPseudoClasses: Set<string> = new Set();
private getAllAliasedStates(name: string): Array<string> {
let allStates = [];
const allStates = [];
allStates.push(name);
if (name in this.pseudoClassAliases) {
for (let i = 0; i < this.pseudoClassAliases[name].length; i++) {
@@ -509,7 +589,19 @@ export abstract class ViewBase extends Observable implements ViewBaseDefinition
}
if (this._isLoaded) {
view.onLoaded();
this.loadView(view);
}
}
public loadView(view: ViewBase): void {
if (!view.isLoaded) {
view.callLoaded();
}
}
public unloadView(view: ViewBase): void {
if (view.isLoaded) {
view.callUnloaded();
}
}
@@ -538,9 +630,7 @@ export abstract class ViewBase extends Observable implements ViewBaseDefinition
* Method is intended to be overridden by inheritors and used as "protected"
*/
public _removeViewCore(view: ViewBase) {
if (view.isLoaded) {
view.onUnloaded();
}
this.unloadView(view);
if (view._context) {
view._tearDownUI();
@@ -588,20 +678,14 @@ export abstract class ViewBase extends Observable implements ViewBaseDefinition
}
@profile
public _setupUI(context: android.content.Context, atIndex?: number, parentIsLoaded?: boolean) {
traceNotifyEvent(this, "_setupUI");
if (traceEnabled()) {
traceWrite(`${this}._setupUI(${context})`, traceCategories.VisualTreeEvents);
}
public _setupUI(context: android.content.Context, atIndex?: number, parentIsLoaded?: boolean): void {
if (this._context === context) {
return;
} else if (this._context) {
this._tearDownUI(true);
}
this._context = context;
traceNotifyEvent(this, "_onContextChanged");
let nativeView;
if (isAndroid) {
// const recycle = this.recycleNativeView;
@@ -659,7 +743,7 @@ export abstract class ViewBase extends Observable implements ViewBaseDefinition
this._isAddedToNativeVisualTree = this.parent._addViewToNativeVisualTree(this, nativeIndex);
}
this._resumeNativeUpdates();
this._resumeNativeUpdates(SuspendType.UISetup);
this.eachChild((child) => {
child._setupUI(context);
@@ -673,23 +757,20 @@ export abstract class ViewBase extends Observable implements ViewBaseDefinition
}
if (this.__nativeView) {
this._suspendNativeUpdates();
this._suspendNativeUpdates(SuspendType.NativeView);
// We may do a `this.resetNativeView()` here?
}
this.__nativeView = this.nativeViewProtected = value;
if (this.__nativeView) {
this._suspendedUpdates = undefined;
this.initNativeView();
this._resumeNativeUpdates();
this._resumeNativeUpdates(SuspendType.NativeView);
}
}
@profile
public _tearDownUI(force?: boolean) {
if (traceEnabled()) {
traceWrite(`${this}._tearDownUI(${force})`, traceCategories.VisualTreeEvents);
}
public _tearDownUI(force?: boolean): void {
// No context means we are already teared down.
if (!this._context) {
return;
@@ -727,7 +808,7 @@ export abstract class ViewBase extends Observable implements ViewBaseDefinition
this.disposeNativeView();
this._suspendNativeUpdates();
this._suspendNativeUpdates(SuspendType.UISetup);
if (isAndroid) {
this.setNativeView(null);
@@ -742,9 +823,6 @@ export abstract class ViewBase extends Observable implements ViewBaseDefinition
this.domNode.dispose();
this.domNode = undefined;
}
traceNotifyEvent(this, "_onContextChanged");
traceNotifyEvent(this, "_tearDownUI");
}
_childIndexToNativeChildIndex(index?: number): number {
@@ -766,7 +844,6 @@ export abstract class ViewBase extends Observable implements ViewBaseDefinition
* Method is intended to be overridden by inheritors and used as "protected"
*/
public _removeViewFromNativeVisualTree(view: ViewBase) {
traceNotifyEvent(view, "_removeViewFromNativeVisualTree");
view._isAddedToNativeVisualTree = false;
}
@@ -774,6 +851,7 @@ export abstract class ViewBase extends Observable implements ViewBaseDefinition
if (traceEnabled()) {
traceWrite(this + " going to state: " + state, traceCategories.Style);
}
if (state === this._visualState) {
return;
}
@@ -890,11 +968,10 @@ ViewBase.prototype._defaultPaddingLeft = 0;
ViewBase.prototype._isViewBase = true;
ViewBase.prototype.recycleNativeView = "never";
// Removing from visual tree does +1, adding to visual tree does -1, see parentChanged
// Removing the nativeView does +1, adding the nativeView does -1, see set nativeView
// Pre _setupUI and post _tearDownUI does +1, in between _setupUI and _tearDownUI is -1
// Initially launch with 2, native updates wont fire unless both added to the JS visual tree and got nativeView.
ViewBase.prototype._suspendNativeUpdatesCount = 3;
ViewBase.prototype._suspendNativeUpdatesCount =
SuspendType.Loaded |
SuspendType.NativeView |
SuspendType.UISetup;
export const bindingContextProperty = new InheritedProperty<ViewBase, any>({ name: "bindingContext" });
bindingContextProperty.register(ViewBase);

View File

@@ -1,5 +1,8 @@
// Definitions.
import { View as ViewDefinition, Point, Size, Color, dip } from ".";
import {
View as ViewDefinition, Point, Size, Color, dip,
ShownModallyData
} from ".";
import {
ViewBase, Property, booleanConverter, EventData, layout,
@@ -16,6 +19,8 @@ import {
fromString as gestureFromString
} from "../../gestures";
import { createViewFromEntry } from "../../builder";
export * from "../../styling/style-properties";
export * from "../view-base";
@@ -28,8 +33,8 @@ function ensureAnimationModule() {
}
export function PseudoClassHandler(...pseudoClasses: string[]): MethodDecorator {
let stateEventNames = pseudoClasses.map(s => ":" + s);
let listeners = Symbol("listeners");
const stateEventNames = pseudoClasses.map(s => ":" + s);
const listeners = Symbol("listeners");
return <T>(target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>) => {
function update(change: number) {
let prev = this[listeners] || 0;
@@ -45,6 +50,14 @@ export function PseudoClassHandler(...pseudoClasses: string[]): MethodDecorator
}
export abstract class ViewCommon extends ViewBase implements ViewDefinition {
public static shownModallyEvent = "shownModally";
public static showingModallyEvent = "showingModally";
protected _closeModalCallback: Function;
private _modalContext: any;
public _modal: ViewCommon;
private _measuredWidth: number;
private _measuredHeight: number;
@@ -128,6 +141,82 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition {
}
}
public _onBackPressed(): boolean {
return false;
}
public _getFragmentManager(): any {
return undefined;
}
public showModal(): ViewDefinition {
if (arguments.length === 0) {
throw new Error('showModal without parameters is deprecated. Please call showModal on a view instance instead.');
} else {
const firstAgrument = arguments[0];
const context: any = arguments[1];
const closeCallback: Function = arguments[2];
const fullscreen: boolean = arguments[3];
const animated = arguments[4];
const view: ViewDefinition = firstAgrument instanceof ViewCommon
? firstAgrument : createViewFromEntry({ moduleName: firstAgrument });
(<ViewCommon>view)._showNativeModalView(this, context, closeCallback, fullscreen, animated);
return view;
}
}
public closeModal() {
if (this._closeModalCallback) {
this._closeModalCallback.apply(undefined, arguments);
}
}
public get modal(): ViewCommon {
return this._modal;
}
protected _showNativeModalView(parent: ViewCommon, context: any, closeCallback: Function, fullscreen?: boolean, animated?: boolean) {
parent._modal = this;
this._modalContext = context;
const that = this;
this._closeModalCallback = function () {
if (that._closeModalCallback) {
that._closeModalCallback = null;
that._modalContext = null;
that._hideNativeModalView(parent);
if (typeof closeCallback === "function") {
closeCallback.apply(undefined, arguments);
}
}
};
}
protected _hideNativeModalView(parent: ViewCommon) {
//
}
protected _raiseShownModallyEvent() {
const args: ShownModallyData = {
eventName: ViewCommon.shownModallyEvent,
object: this,
context: this._modalContext,
closeCallback: this._closeModalCallback
};
this.notify(args);
}
protected _raiseShowingModallyEvent() {
const args: ShownModallyData = {
eventName: ViewCommon.showingModallyEvent,
object: this,
context: this._modalContext,
closeCallback: this._closeModalCallback
}
this.notify(args);
}
private _isEvent(name: string): boolean {
return this.constructor && `${name}Event` in this.constructor;
}
@@ -585,7 +674,7 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition {
const width = layout.getMeasureSpecSize(widthSpec);
const widthMode = layout.getMeasureSpecMode(widthSpec);
const height = layout.getMeasureSpecSize(heightSpec);
const heightMode = layout.getMeasureSpecMode(heightSpec);
@@ -797,6 +886,10 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition {
public _redrawNativeBackground(value: any): void {
//
}
addCssFile(cssFileName: string): void {
// TODO: Implement
}
}
export const automationTextProperty = new Property<ViewCommon, string>({ name: "automationText" });

View File

@@ -1,13 +1,14 @@
// Definitions.
import { ViewBase } from "../view-base";
import { Point, CustomLayoutView as CustomLayoutViewDefinition, dip } from ".";
import { GestureTypes, GestureEventData } from "../../gestures";
import { AndroidActivityBackPressedEventData } from "../../../application";
// Types.
import { Background, ad as androidBackground } from "../../styling/background";
import {
ViewCommon, layout, isEnabledProperty, originXProperty, originYProperty, automationTextProperty, isUserInteractionEnabledProperty,
traceEnabled, traceWrite, traceCategories, traceNotifyEvent,
paddingLeftProperty, paddingTopProperty, paddingRightProperty, paddingBottomProperty
traceEnabled, traceWrite, traceCategories, traceNotifyEvent,
paddingLeftProperty, paddingTopProperty, paddingRightProperty, paddingBottomProperty,
Color
} from "./view-common";
import {
@@ -19,16 +20,35 @@ import {
zIndexProperty, backgroundInternalProperty
} from "../../styling/style-properties";
import { Background, ad as androidBackground } from "../../styling/background";
import { profile } from "../../../profiling";
export * from "./view-common";
const DOMID = "_domId";
const androidBackPressedEvent = "androidBackPressed";
const modalMap = new Map<number, DialogOptions>();
let TouchListener: TouchListener;
let disableUserInteractionListener: org.nativescript.widgets.DisableUserInteractionListener;
let DialogFragment: DialogFragment;
let Dialog: android.app.Dialog;
interface DialogOptions {
owner: View;
fullscreen: boolean;
shownCallback: () => void;
dismissCallback: () => void;
}
interface TouchListener {
new(owner: View): android.view.View.OnTouchListener;
}
let TouchListener: TouchListener;
let disableUserInteractionListener: org.nativescript.widgets.DisableUserInteractionListener;
interface DialogFragment {
new(): android.app.DialogFragment;
}
function initializeDisabledListener(): void {
if (disableUserInteractionListener) {
@@ -71,10 +91,128 @@ function initializeTouchListener(): void {
TouchListener = TouchListenerImpl;
}
function initializeDialogFragment() {
if (DialogFragment) {
return;
}
class DialogImpl extends android.app.Dialog {
constructor(public fragment: DialogFragmentImpl,
context: android.content.Context,
themeResId: number) {
super(context, themeResId);
return global.__native(this);
}
public onBackPressed(): void {
const view = this.fragment.owner;
const args = <AndroidActivityBackPressedEventData>{
eventName: "activityBackPressed",
object: view,
activity: view._context,
cancel: false,
};
view.notify(args);
if (!args.cancel && !view._onBackPressed()) {
super.onBackPressed();
}
}
}
class DialogFragmentImpl extends android.app.DialogFragment {
public owner: View;
private _fullscreen: boolean;
private _shownCallback: () => void;
private _dismissCallback: () => void;
constructor() {
super();
return global.__native(this);
}
public onCreateDialog(savedInstanceState: android.os.Bundle): android.app.Dialog {
const ownerId = this.getArguments().getInt(DOMID);
const options = getModalOptions(ownerId);
this.owner = options.owner;
this._fullscreen = options.fullscreen;
this._dismissCallback = options.dismissCallback;
this._shownCallback = options.shownCallback;
this.owner._dialogFragment = this;
this.setStyle(android.app.DialogFragment.STYLE_NO_TITLE, 0);
return new DialogImpl(this, this.getActivity(), this.getTheme());
}
public onCreateView(inflater: android.view.LayoutInflater, container: android.view.ViewGroup, savedInstanceState: android.os.Bundle): android.view.View {
const owner = this.owner;
owner._setupUI(this.getActivity());
owner._isAddedToNativeVisualTree = true;
return this.owner.nativeViewProtected;
}
public onStart(): void {
super.onStart();
if (this._fullscreen) {
const window = this.getDialog().getWindow();
const length = android.view.ViewGroup.LayoutParams.MATCH_PARENT;
window.setLayout(length, length);
window.setBackgroundDrawable(new android.graphics.drawable.ColorDrawable(android.graphics.Color.WHITE));
}
const owner = this.owner;
if (!owner.isLoaded) {
owner.callLoaded();
}
this._shownCallback();
}
public onStop(): void {
super.onStop();
const owner = this.owner;
if (owner.isLoaded) {
owner.callUnloaded();
}
}
public onDismiss(dialog: android.content.IDialogInterface): void {
super.onDismiss(dialog);
const manager = this.getFragmentManager();
if (manager) {
const owner = this.owner;
removeModal(owner._domId);
this._dismissCallback();
owner._isAddedToNativeVisualTree = false;
owner._tearDownUI(true);
}
}
}
DialogFragment = DialogFragmentImpl;
}
function saveModal(options: DialogOptions) {
modalMap.set(options.owner._domId, options);
}
function removeModal(domId: number) {
modalMap.delete(domId);
}
function getModalOptions(domId: number): DialogOptions {
return modalMap.get(domId);
}
export class View extends ViewCommon {
public static androidBackPressedEvent = androidBackPressedEvent;
public _dialogFragment: android.app.DialogFragment;
private _isClickable: boolean;
private touchListenerIsSet: boolean;
private touchListener: android.view.View.OnTouchListener;
private _manager: android.app.FragmentManager;
nativeViewProtected: android.view.View;
@@ -86,8 +224,35 @@ export class View extends ViewCommon {
}
}
public _getFragmentManager(): android.app.FragmentManager {
let manager = this._manager;
if (!manager) {
let view: View = this;
while (view) {
const dialogFragment = view._dialogFragment;
if (dialogFragment) {
manager = dialogFragment.getChildFragmentManager();
break;
} else {
// the case is needed because _dialogFragment is on View
// but parent may be ViewBase.
view = view.parent as View;
}
}
if (!manager && this._context) {
manager = (<android.app.Activity>this._context).getFragmentManager();
}
this._manager = manager;
}
return manager;
}
@profile
public onLoaded() {
this._manager = null;
super.onLoaded();
this.setOnTouchListener();
}
@@ -100,6 +265,7 @@ export class View extends ViewCommon {
this.nativeViewProtected.setClickable(this._isClickable);
}
this._manager = null;
super.onUnloaded();
}
@@ -277,6 +443,45 @@ export class View extends ViewCommon {
return result | (childMeasuredState & layout.MEASURED_STATE_MASK);
}
protected _showNativeModalView(parent: View, context: any, closeCallback: Function, fullscreen?: boolean, animated?: boolean) {
super._showNativeModalView(parent, context, closeCallback, fullscreen);
if (!this.backgroundColor) {
this.backgroundColor = new Color("White");
}
initializeDialogFragment();
const df = new DialogFragment();
const args = new android.os.Bundle();
args.putInt(DOMID, this._domId);
df.setArguments(args);
const dialogOptions: DialogOptions = {
owner: this,
fullscreen: !!fullscreen,
shownCallback: () => this._raiseShownModallyEvent(),
dismissCallback: () => this.closeModal()
}
saveModal(dialogOptions);
this._dialogFragment = df;
this._raiseShowingModallyEvent();
this._dialogFragment.show(parent._getFragmentManager(), this._domId.toString());
}
protected _hideNativeModalView(parent: View) {
const manager = this._dialogFragment.getFragmentManager();
if (manager) {
this._dialogFragment.dismissAllowingStateLoss();
}
this._dialogFragment = null;
parent._modal = undefined;
super._hideNativeModalView(parent);
}
[isEnabledProperty.setNative](value: boolean) {
this.nativeViewProtected.setEnabled(value);
}
@@ -469,7 +674,7 @@ export class View extends ViewCommon {
return drawable;
}
}
return null;
}
[backgroundInternalProperty.setNative](value: android.graphics.drawable.Drawable | Background) {
@@ -498,7 +703,7 @@ export class View extends ViewCommon {
} else {
const nativeView = this.nativeViewProtected;
org.nativescript.widgets.ViewHelper.setBackground(nativeView, value);
const style = this.style;
const paddingTop = paddingTopProperty.isSet(style) ? this.effectivePaddingTop : this._defaultPaddingTop;
const paddingRight = paddingRightProperty.isSet(style) ? this.effectivePaddingRight : this._defaultPaddingRight;
@@ -523,7 +728,7 @@ export class CustomLayoutView extends View implements CustomLayoutViewDefinition
return new org.nativescript.widgets.ContentLayout(this._context);
}
public _addViewToNativeVisualTree(child: ViewCommon, atIndex: number = -1): boolean {
public _addViewToNativeVisualTree(child: ViewCommon, atIndex: number = Number.MAX_SAFE_INTEGER): boolean {
super._addViewToNativeVisualTree(child);
if (this.nativeViewProtected && child.nativeViewProtected) {
@@ -670,4 +875,4 @@ createNativePercentLengthProperty({
createNativePercentLengthProperty({
setter: "_setMinHeightNative",
get setPixels() { return org.nativescript.widgets.ViewHelper.setMinHeight }
});
});

View File

@@ -63,6 +63,23 @@ export interface Size {
height: number;
}
/**
* Defines the data for the shownModally event.
*/
export interface ShownModallyData extends EventData {
/**
* The context (optional, may be undefined) passed to the view when shown modally.
*/
context?: any;
/**
* A callback to call when you want to close the modally shown view.
* Pass in any kind of arguments and you will receive when the callback parameter
* of View.showModal is executed.
*/
closeCallback?: Function;
}
/**
* This class is the base class for all UI components.
* A View occupies a rectangular area on the screen and is responsible for drawing and layouting of all UI components within.
@@ -427,7 +444,7 @@ export abstract class View extends ViewBase {
* @param callback - Callback function which will be executed when event is raised.
* @param thisArg - An optional parameter which will be used as `this` context for callback execution.
*/
on(eventNames: string | GestureTypes, callback: (data: EventData) => void, thisArg?: any);
on(eventNames: string | GestureTypes, callback: (args: EventData) => void, thisArg?: any);
/**
* Removes listener(s) for the specified event name.
@@ -435,7 +452,7 @@ export abstract class View extends ViewBase {
* @param callback An optional parameter pointing to a specific listener. If not defined, all listeners for the event names will be removed.
* @param thisArg An optional parameter which when set will be used to refine search of the correct callback which will be removed as event listener.
*/
off(eventNames: string | GestureTypes, callback?: (data: EventData) => void, thisArg?: any);
off(eventNames: string | GestureTypes, callback?: (args: EventData) => void, thisArg?: any);
/**
* Raised when a loaded event occurs.
@@ -447,6 +464,58 @@ export abstract class View extends ViewBase {
*/
on(event: "unloaded", callback: (args: EventData) => void, thisArg?: any);
/**
* Raised when a back button is pressed.
* This event is raised only for android.
*/
on(event: "androidBackPressed", callback: (args: EventData) => void, thisArg?: any);
/**
* Raised before the view is shown as a modal dialog.
*/
on(event: "showingModally", callback: (args: ShownModallyData) => void, thisArg?: any): void;
/**
* Raised after the view is shown as a modal dialog.
*/
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.
*/
showModal(moduleName: string, context: any, closeCallback: Function, fullscreen?: boolean, animated?: boolean): View;
/**
* 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: View, context: any, closeCallback: Function, fullscreen?: boolean, animated?: boolean): View;
/**
* Deprecated. Showing view as modal is deprecated.
* Use showModal method with arguments.
*/
showModal(): View;
/**
* Closes the current modal view that this page is showing.
*/
closeModal(): void;
/**
* Returns the current modal view that this page is showing (is parent of), if any.
*/
modal: View;
/**
* Animates one or more properties of the view based on the supplied options.
*/
@@ -485,6 +554,14 @@ export abstract class View extends ViewBase {
public eachChildView(callback: (view: View) => boolean): void;
//@private
/**
* @private
*/
_modal: View;
/**
* @private
*/
_modalParent?: View;
/**
* @private
*/
@@ -552,7 +629,21 @@ export abstract class View extends ViewBase {
/**
* @private
*/
_removeAnimation(animation: Animation): boolean
_removeAnimation(animation: Animation): boolean;
/**
* @private
*/
_onBackPressed(): boolean;
/**
* @private
*/
_getFragmentManager(): any; /* android.app.FragmentManager */
/**
* @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;
//@endprivate
/**

View File

@@ -29,6 +29,8 @@ const PFLAG_LAYOUT_REQUIRED = 1 << 2;
export class View extends ViewCommon {
nativeViewProtected: UIView;
viewController: UIViewController;
private _hasTransfrom = false;
private _privateFlags: number = PFLAG_LAYOUT_REQUIRED | PFLAG_FORCE_LAYOUT;
private _cachedFrame: CGRect;
@@ -41,6 +43,8 @@ export class View extends ViewCommon {
*/
_nativeBackgroundState: "unset" | "invalid" | "drawn";
public _modalParent: View;
get isLayoutRequired(): boolean {
return (this._privateFlags & PFLAG_LAYOUT_REQUIRED) === PFLAG_LAYOUT_REQUIRED;
}
@@ -304,6 +308,52 @@ export class View extends ViewCommon {
return this._suspendCATransaction || this._suspendNativeUpdatesCount;
}
protected _showNativeModalView(parent: View, context: any, closeCallback: Function, fullscreen?: boolean, animated?: boolean) {
super._showNativeModalView(parent, context, closeCallback, fullscreen);
let controller = this.viewController;
if (!controller) {
controller = ios.UILayoutViewController.initWithOwner(new WeakRef(this));
this.viewController = controller;
}
this._setupAsRootView({});
this._modalParent = parent;
const parentController = parent.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!");
}
if (fullscreen) {
controller.modalPresentationStyle = UIModalPresentationStyle.FullScreen;
} else {
controller.modalPresentationStyle = UIModalPresentationStyle.FormSheet;
}
this._raiseShowingModallyEvent();
animated = animated === undefined ? true : !!animated;
(<any>controller).animated = animated;
parentController.presentViewControllerAnimatedCompletion(controller, animated, null);
const transitionCoordinator = iosUtils.getter(parentController, parentController.transitionCoordinator);
if (transitionCoordinator) {
UIViewControllerTransitionCoordinator.prototype.animateAlongsideTransitionCompletion.call(transitionCoordinator, null, () => this._raiseShownModallyEvent());
} else {
// Apparently iOS 9+ stops all transitions and animations upon application suspend and transitionCoordinator becomes null here in this case.
// Since we are not waiting for any transition to complete, i.e. transitionCoordinator is null, we can directly raise our shownModally event.
// Take a look at https://github.com/NativeScript/NativeScript/issues/2173 for more info and a sample project.
this._raiseShownModallyEvent();
}
}
protected _hideNativeModalView(parent: View) {
parent.requestLayout();
const parentController = parent.viewController;
const animated = (<any>this.viewController).animated;
parentController.dismissModalViewControllerAnimated(animated);
super._hideNativeModalView(parent);
}
[isEnabledProperty.getDefault](): boolean {
const nativeView = this.nativeViewProtected;
return nativeView instanceof UIControl ? nativeView.enabled : true;
@@ -641,8 +691,24 @@ export namespace ios {
const frame = controller.view.subviews[0].bounds;
const origin = frame.origin;
const size = frame.size;
const width = layout.toDevicePixels(size.width);
const height = layout.toDevicePixels(size.height);
let width = layout.toDevicePixels(size.width);
let height = layout.toDevicePixels(size.height);
if (iosUtils.MajorVersion < 11) {
const window = controller.view.window;
if (window) {
const windowSize = window.frame.size;
const windowInPortrait = windowSize.width < windowSize.height;
const viewInPortrait = width < height;
if (windowInPortrait !== viewInPortrait) {
// NOTE: This happens on iOS <11.
// We were not visible (probably in backstack) when orientation happened.
// request layout so we get the new dimensions.
// There is no sync way to force a layout.
setTimeout(() => owner.requestLayout());
}
}
}
const widthSpec = layout.makeMeasureSpec(width, layout.EXACTLY);
const heightSpec = layout.makeMeasureSpec(height, layout.EXACTLY);