mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-11-05 13:26:48 +08:00
Implemented showModal on View
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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" });
|
||||
|
||||
@@ -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 }
|
||||
});
|
||||
});
|
||||
97
tns-core-modules/ui/core/view/view.d.ts
vendored
97
tns-core-modules/ui/core/view/view.d.ts
vendored
@@ -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
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user