UNPKG

@nativescript/core

Version:

A JavaScript library providing an easy to use api for interacting with iOS and Android platform APIs.

1,166 lines (1,165 loc) 66.8 kB
import { getNativeScriptGlobals } from '../../../globals/global-utils'; import { ViewCommon, isEnabledProperty, originXProperty, originYProperty, isUserInteractionEnabledProperty, testIDProperty, AndroidHelper, androidOverflowEdgeProperty, statusBarStyleProperty } from './view-common'; import { paddingLeftProperty, paddingTopProperty, paddingRightProperty, paddingBottomProperty, directionProperty } from '../../styling/style-properties'; import { layout } from '../../../utils'; import { Trace } from '../../../trace'; import { hiddenProperty } from '../view-base'; import { isCssWideKeyword } from '../properties/property-shared'; import { perspectiveProperty, visibilityProperty, opacityProperty, horizontalAlignmentProperty, verticalAlignmentProperty, minWidthProperty, minHeightProperty, widthProperty, heightProperty, marginLeftProperty, marginTopProperty, marginRightProperty, marginBottomProperty, rotateProperty, rotateXProperty, rotateYProperty, scaleXProperty, scaleYProperty, translateXProperty, translateYProperty, zIndexProperty, backgroundInternalProperty, androidElevationProperty, androidDynamicElevationOffsetProperty } from '../../styling/style-properties'; import { CoreTypes } from '../../../core-types'; import { Background, refreshBorderDrawable } from '../../styling/background'; import { profile } from '../../../profiling'; import { topmost } from '../../frame/frame-stack'; import { Screen } from '../../../platform'; import { isAppInBackground, updateA11yPropertiesCallback } from '../../../application/helpers-common'; import { updateContentDescription } from '../../../application/helpers'; import { accessibilityEnabledProperty, accessibilityHiddenProperty, accessibilityHintProperty, accessibilityIdentifierProperty, accessibilityLabelProperty, accessibilityLiveRegionProperty, accessibilityMediaSessionProperty, accessibilityRoleProperty, accessibilityStateProperty, accessibilityValueProperty } from '../../../accessibility/accessibility-properties'; import { AccessibilityLiveRegion, AccessibilityRole, AndroidAccessibilityEvent } from '../../../accessibility'; import * as Utils from '../../../utils'; import { SDK_VERSION } from '../../../utils/constants'; export * from './view-common'; // helpers (these are okay re-exported here) export * from './view-helper'; // This one can eventually be cleaned up but causes issues with a lot of ui-suite plugins in particular if not exported here export * from '../properties'; const DOMID = '_domId'; const androidBackPressedEvent = 'androidBackPressed'; const shortAnimTime = 17694720; // android.R.integer.config_shortAnimTime const statePressed = 16842919; // android.R.attr.state_pressed const stateEnabled = 16842910; // android.R.attr.state_enabled const styleAnimationDialog = 16973826; // android.R.style.Animation_Dialog const VERTICAL_GRAVITY_MASK = 112; // android.view.Gravity.VERTICAL_GRAVITY_MASK const HORIZONTAL_GRAVITY_MASK = 7; // android.view.Gravity.HORIZONTAL_GRAVITY_MASK const GRAVITY_LEFT = 3; // android.view.Gravity.LEFT const GRAVITY_RIGHT = 5; // android.view.Gravity.RIGHT const GRAVITY_TOP = 48; // android.view.Gravity.TOP const GRAVITY_BOTTOM = 80; // android.view.Gravity.BOTTOM const GRAVITY_CENTER_HORIZONTAL = 1; // android.view.Gravity.CENTER_HORIZONTAL const GRAVITY_FILL_HORIZONTAL = 7; // android.view.Gravity.FILL_HORIZONTAL const GRAVITY_CENTER_VERTICAL = 16; // android.view.Gravity.CENTER_VERTICAL const GRAVITY_FILL_VERTICAL = 112; // android.view.Gravity.FILL_VERTICAL const SYSTEM_UI_FLAG_LIGHT_STATUS_BAR = 0x00002000; const STATUS_BAR_LIGHT_BCKG = -657931; const STATUS_BAR_DARK_BCKG = 1711276032; const modalMap = new Map(); let TouchListener; let DialogFragment; let OnBackPressedCallback; if (SDK_VERSION >= 33) { OnBackPressedCallback = androidx.activity.OnBackPressedCallback.extend({ handleOnBackPressed() { console.log('OnBackPressedCallback handleOnBackPressed called'); const dialog = this['_dialog']?.get(); if (!dialog) { // disable the callback and call super to avoid infinite loop this.setEnabled(false); return; } const view = dialog.fragment.owner; const args = { eventName: 'activityBackPressed', object: view, activity: view._context, cancel: false, }; // Fist fire application.android global event getNativeScriptGlobals().events.notify(args); if (args.cancel) { return; } view.notify(args); if (!args.cancel) { this.setEnabled(false); dialog.getOnBackPressedDispatcher().onBackPressed(); this.setEnabled(true); } }, }); } function initializeTouchListener() { if (TouchListener) { return; } var TouchListenerImpl = (function (_super) { __extends(TouchListenerImpl, _super); function TouchListenerImpl(owner) { var _this = _super.call(this) || this; _this.owner = new WeakRef(owner); return global.__native(_this); } TouchListenerImpl.prototype.onTouch = function (view, event) { var owner = this.owner.get(); if (!owner) { return; } owner.handleGestureTouch(event); var nativeView = owner.nativeViewProtected; if (!nativeView || !nativeView.onTouchEvent) { return false; } return nativeView.onTouchEvent(event); }; TouchListenerImpl = __decorate([ Interfaces([android.view.View.OnTouchListener]) ], TouchListenerImpl); return TouchListenerImpl; }(java.lang.Object)); TouchListener = TouchListenerImpl; } function initializeDialogFragment() { if (DialogFragment) { return; } var DialogImpl = (function (_super) { __extends(DialogImpl, _super); function DialogImpl(fragment, context, themeResId) { var _this = _super.call(this, context, themeResId) || this; _this.fragment = fragment; if (SDK_VERSION >= 33 && OnBackPressedCallback) { var callback = new OnBackPressedCallback(true); callback["_dialog"] = new WeakRef(_this); _this.getOnBackPressedDispatcher().addCallback(_this, callback); } return global.__native(_this); } DialogImpl.prototype.onDetachedFromWindow = function () { _super.prototype.onDetachedFromWindow.call(this); this.fragment = null; }; DialogImpl.prototype.onBackPressed = function () { if (SDK_VERSION >= 33) { _super.prototype.onBackPressed.call(this); return; } var view = this.fragment.owner; var args = { eventName: "activityBackPressed", object: view, activity: view._context, cancel: false, }; getNativeScriptGlobals().events.notify(args); if (args.cancel) { return; } view.notify(args); if (!args.cancel && !view.onBackPressed()) { _super.prototype.onBackPressed.call(this); } }; return DialogImpl; }(androidx.appcompat.app.AppCompatDialog)); var DialogFragmentImpl = (function (_super) { __extends(DialogFragmentImpl, _super); function DialogFragmentImpl() { var _this = _super.call(this) || this; return global.__native(_this); } DialogFragmentImpl.prototype.onCreate = function (savedInstanceState) { var _a; _super.prototype.onCreate.call(this, savedInstanceState); var ownerId = (_a = this.getArguments()) === null || _a === void 0 ? void 0 : _a.getInt(DOMID); var options = getModalOptions(ownerId); if (savedInstanceState != null && options === undefined) { this.dismissAllowingStateLoss(); } }; DialogFragmentImpl.prototype.onCreateDialog = function (savedInstanceState) { var ownerId = this.getArguments().getInt(DOMID); var options = getModalOptions(ownerId); this.owner = options.owner; this.owner._dialogFragment = this; this._fullscreen = options.fullscreen; this._animated = options.animated; this._cancelable = options.cancelable; this._stretched = options.stretched; this._dismissCallback = options.dismissCallback; this._shownCallback = options.shownCallback; this._windowSoftInputMode = options.windowSoftInputMode; this.setStyle(androidx.fragment.app.DialogFragment.STYLE_NO_TITLE, 0); var theme = this.getTheme(); if (this._fullscreen) { theme = this.getActivity().getApplicationInfo().theme; } var dialog = new DialogImpl(this, this.getActivity(), theme); if (!this._fullscreen && !this._stretched) { this.owner.horizontalAlignment = "center"; this.owner.verticalAlignment = "middle"; } else { this.owner.horizontalAlignment = "stretch"; this.owner.verticalAlignment = "stretch"; } if (this._animated) { dialog.getWindow().setWindowAnimations(styleAnimationDialog); } dialog.setCanceledOnTouchOutside(this._cancelable); return dialog; }; DialogFragmentImpl.prototype.onCreateView = function (inflater, container, savedInstanceState) { var owner = this.owner; this.activity = new WeakRef(this.getActivity()); owner._setupAsRootView(this.getActivity()); owner._isAddedToNativeVisualTree = true; var window = this.getDialog().getWindow(); if (this._windowSoftInputMode !== undefined) { window.setSoftInputMode(this._windowSoftInputMode); } else { window.setSoftInputMode(owner._context.getWindow().getAttributes().softInputMode); } return owner.nativeViewProtected; }; DialogFragmentImpl.prototype.onStart = function () { _super.prototype.onStart.call(this); if (this._fullscreen) { var window = this.getDialog().getWindow(); var length = android.view.ViewGroup.LayoutParams.MATCH_PARENT; window.setLayout(length, length); window.setBackgroundDrawable(new android.graphics.drawable.ColorDrawable(android.graphics.Color.WHITE)); } var owner = this.owner; if (owner && !owner.isLoaded) { owner.callLoaded(); } this._shownCallback(); }; DialogFragmentImpl.prototype.onDismiss = function (dialog) { var _a; _super.prototype.onDismiss.call(this, dialog); var manager = this.getFragmentManager(); var activity = (_a = this.activity) === null || _a === void 0 ? void 0 : _a.get(); if (manager && !(activity === null || activity === void 0 ? void 0 : activity.isChangingConfigurations())) { removeModal(this.owner._domId); this._dismissCallback(); } var owner = this.owner; if (owner && owner.isLoaded) { owner.callUnloaded(); } }; DialogFragmentImpl.prototype.onDestroy = function () { var _a; _super.prototype.onDestroy.call(this); var owner = this.owner; var activity = (_a = this.activity) === null || _a === void 0 ? void 0 : _a.get(); if (!(activity === null || activity === void 0 ? void 0 : activity.isChangingConfigurations())) { this.activity = null; } if (owner) { if (owner.isLoaded) { owner.callUnloaded(); } owner._isAddedToNativeVisualTree = false; owner._tearDownUI(true); } }; return DialogFragmentImpl; }(androidx.fragment.app.DialogFragment)); DialogFragment = DialogFragmentImpl; } function saveModal(options) { modalMap.set(options.owner._domId, options); } function removeModal(domId) { modalMap.delete(domId); } function getModalOptions(domId) { return modalMap.get(domId); } const INSET_LEFT = 0; const INSET_TOP = 4; const INSET_RIGHT = 8; const INSET_BOTTOM = 12; const INSET_LEFT_CONSUMED = 16; const INSET_TOP_CONSUMED = 20; const INSET_RIGHT_CONSUMED = 24; const INSET_BOTTOM_CONSUMED = 28; const OverflowEdgeIgnore = -1; const OverflowEdgeNone = 0; const OverflowEdgeLeft = 1 << 1; const OverflowEdgeTop = 1 << 2; const OverflowEdgeRight = 1 << 3; const OverflowEdgeBottom = 1 << 4; const OverflowEdgeDontApply = 1 << 5; const OverflowEdgeLeftDontConsume = 1 << 6; const OverflowEdgeTopDontConsume = 1 << 7; const OverflowEdgeRightDontConsume = 1 << 8; const OverflowEdgeBottomDontConsume = 1 << 9; const OverflowEdgeAllButLeft = 1 << 10; const OverflowEdgeAllButTop = 1 << 11; const OverflowEdgeAllButRight = 1 << 12; const OverflowEdgeAllButBottom = 1 << 13; class Inset { constructor(data) { this.data = ArrayBuffer.from(data); this.view = new DataView(this.data); } get left() { return this.view.getInt32(INSET_LEFT, true); } set left(value) { this.view.setInt32(INSET_LEFT, value, true); } get top() { return this.view.getInt32(INSET_TOP, true); } set top(value) { this.view.setInt32(INSET_TOP, value, true); } get right() { return this.view.getInt32(INSET_RIGHT, true); } set right(value) { this.view.setInt32(INSET_RIGHT, value, true); } get bottom() { return this.view.getInt32(INSET_BOTTOM, true); } set bottom(value) { this.view.setInt32(INSET_BOTTOM, value, true); } get leftConsumed() { return this.view.getInt32(INSET_LEFT_CONSUMED, true) > 0; } set leftConsumed(value) { this.view.setInt32(INSET_LEFT_CONSUMED, value ? 1 : 0, true); } get topConsumed() { return this.view.getInt32(INSET_TOP_CONSUMED, true) > 0; } set topConsumed(value) { this.view.setInt32(INSET_TOP_CONSUMED, value ? 1 : 0, true); } get rightConsumed() { return this.view.getInt32(INSET_RIGHT_CONSUMED, true) > 0; } set rightConsumed(value) { this.view.setInt32(INSET_RIGHT_CONSUMED, value ? 1 : 0, true); } get bottomConsumed() { return this.view.getInt32(INSET_BOTTOM_CONSUMED, true) > 0; } set bottomConsumed(value) { this.view.setInt32(INSET_BOTTOM_CONSUMED, value ? 1 : 0, true); } toString() { return `Inset: left=${this.left}, top=${this.top}, right=${this.right}, bottom=${this.bottom}, ` + `leftConsumed=${this.leftConsumed}, topConsumed=${this.topConsumed}, ` + `rightConsumed=${this.rightConsumed}, bottomConsumed=${this.bottomConsumed}`; } toJSON() { return { left: this.left, top: this.top, right: this.right, bottom: this.bottom, leftConsumed: this.leftConsumed, topConsumed: this.topConsumed, rightConsumed: this.rightConsumed, bottomConsumed: this.bottomConsumed, }; } } export class View extends ViewCommon { // TODO: Implement unobserve that detach the touchListener. _observe(type, callback, thisArg) { super._observe(type, callback, thisArg); if (this.isLoaded && !this.touchListenerIsSet) { this.setOnTouchListener(); } } addEventListener(eventNames, callback, thisArg, once) { super.addEventListener(eventNames, callback, thisArg, once); const isLayoutEvent = typeof eventNames === 'string' ? eventNames.indexOf(ViewCommon.layoutChangedEvent) !== -1 : false; if (this.isLoaded && !this.layoutChangeListenerIsSet && isLayoutEvent) { this.setOnLayoutChangeListener(); } const isInsetEvent = typeof eventNames === 'string' ? eventNames.indexOf(ViewCommon.androidOverflowInsetEvent) !== -1 : false; // only avaiable on LayoutBase if (!this.insetListenerIsSet && isInsetEvent) { this.setInsetListener(); } } removeEventListener(eventNames, callback, thisArg) { super.removeEventListener(eventNames, callback, thisArg); const isLayoutEvent = typeof eventNames === 'string' ? eventNames.indexOf(ViewCommon.layoutChangedEvent) !== -1 : false; // Remove native listener only if there are no more user listeners for LayoutChanged event if (this.isLoaded && this.layoutChangeListenerIsSet && isLayoutEvent && !this.needsOnLayoutChangeListener()) { this.nativeViewProtected.removeOnLayoutChangeListener(this.layoutChangeListener); this.layoutChangeListenerIsSet = false; } const isInsetEvent = typeof eventNames === 'string' ? eventNames.indexOf(ViewCommon.androidOverflowInsetEvent) !== -1 : false; if (this.insetListenerIsSet && isInsetEvent && this.nativeViewProtected && this.nativeViewProtected.setInsetListener) { this.nativeViewProtected.setInsetListener(null); this.insetListenerIsSet = false; } } setInsetListener() { if (this.nativeViewProtected) { if (this.nativeViewProtected.setInsetListener) { const ref = new WeakRef(this); this.nativeViewProtected.setInsetListener(new org.nativescript.widgets.LayoutBase.WindowInsetListener({ onApplyWindowInsets(param0) { const owner = ref.get(); if (!owner) { return; } const inset = new Inset(param0); const args = { eventName: ViewCommon.androidOverflowInsetEvent, object: this, inset, }; owner.notify(args); }, })); this.insetListenerIsSet = true; } this.needsInsetListener = false; } else { this.needsInsetListener = true; } } _getChildFragmentManager() { return null; } _getRootFragmentManager() { if (!this._rootManager && this._context) { this._rootManager = this._context.getSupportFragmentManager(); } return this._rootManager; } _getFragmentManager() { let manager = this._manager; if (!manager) { let view = this; let frameOrTabViewItemFound = false; while (view) { // when interacting with nested fragments instead of using getSupportFragmentManager // we must always use getChildFragmentManager instead; // we have three sources of fragments -- Frame fragments, TabViewItem fragments, and // modal dialog fragments // modal -> frame / tabview (frame / tabview use modal CHILD fm) const dialogFragment = view._dialogFragment; if (dialogFragment) { manager = dialogFragment.getChildFragmentManager(); break; } // - frame1 -> frame2 (frame2 uses frame1 CHILD fm) // - tabview -> frame1 (frame1 uses tabview item CHILD fm) // - frame1 -> tabview (tabview uses frame1 CHILD fm) // - frame1 -> tabview -> frame2 (tabview uses frame1 CHILD fm; frame2 uses tabview item CHILD fm) if (view._hasFragments) { if (frameOrTabViewItemFound) { manager = view._getChildFragmentManager(); break; } frameOrTabViewItemFound = true; } // the case is needed because _dialogFragment is on View // but parent may be ViewBase. view = view.parent; } if (!manager) { manager = this._getRootFragmentManager(); } this._manager = manager; } return manager; } [androidOverflowEdgeProperty.setNative](value) { const nativeView = this.nativeViewProtected; if (typeof value !== 'string' || nativeView === null || nativeView == undefined) { return; } if (!('setOverflowEdge' in nativeView)) { return; } switch (value) { case 'none': nativeView.setOverflowEdge(OverflowEdgeNone); break; case 'ignore': nativeView.setOverflowEdge(OverflowEdgeIgnore); break; default: { const edge = parseEdges(value); if (edge != null) { nativeView.setOverflowEdge(edge); } } break; } } onLoaded() { this._manager = null; this._rootManager = null; super.onLoaded(); this.setOnTouchListener(); } onUnloaded() { this._manager = null; this._rootManager = null; super.onUnloaded(); } onBackPressed() { const topmostFrame = topmost(); // Delegate back navigation handling to the topmost Frame // when it's a child of the current View. if (topmostFrame && topmostFrame._hasAncestorView(this)) { return topmostFrame.onBackPressed(); } return false; } handleGestureTouch(event) { // This keeps a copy of gesture observers from the map to ensure concurrency const allObservers = Object.values(this._gestureObservers); for (const observers of allObservers) { const length = observers.length; if (!length) { continue; } if (length === 1) { const entry = observers[0]; if (entry) { entry.androidOnTouchEvent(event); } } else { // This keeps a copy of gesture observers list to ensure concurrency const observersCp = observers.slice(); for (let i = 0; i < length; i++) { const entry = observersCp[i]; if (entry) { entry.androidOnTouchEvent(event); } } } } if (this.parent instanceof View) { this.parent.handleGestureTouch(event); } } hasGestureObservers() { return this._gestureObservers && Object.keys(this._gestureObservers).length > 0; } initNativeView() { super.initNativeView(); if (this.needsOnLayoutChangeListener()) { this.setOnLayoutChangeListener(); } if (!this.insetListenerIsSet && this.needsInsetListener) { this.setInsetListener(); } } needsOnLayoutChangeListener() { return this.hasListeners(ViewCommon.layoutChangedEvent); } disposeNativeView() { if (this.touchListenerIsSet) { this.touchListenerIsSet = false; if (this.nativeViewProtected) { this.nativeViewProtected.setOnTouchListener(null); } } if (this.layoutChangeListenerIsSet) { this.layoutChangeListenerIsSet = false; if (this.nativeViewProtected) { this.nativeViewProtected.removeOnLayoutChangeListener(this.layoutChangeListener); this.layoutChangeListener = null; } } super.disposeNativeView(); } setOnTouchListener() { if (this.touchListenerIsSet || !this.nativeViewProtected || !this.hasGestureObservers()) { return; } // do not set noop listener that handles the event (disabled listener) if IsUserInteractionEnabled is // false as we might need the ability for the event to pass through to a parent view initializeTouchListener(); this.touchListener = this.touchListener || new TouchListener(this); this.nativeViewProtected.setOnTouchListener(this.touchListener); this.touchListenerIsSet = true; if (this.nativeViewProtected.setClickable) { this.nativeViewProtected.setClickable(this.isUserInteractionEnabled); } } setOnLayoutChangeListener() { if (this.nativeViewProtected) { const owner = this; this.layoutChangeListenerIsSet = true; this.layoutChangeListener = this.layoutChangeListener || new android.view.View.OnLayoutChangeListener({ onLayoutChange(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) { if (left !== oldLeft || top !== oldTop || right !== oldRight || bottom !== oldBottom) { owner._raiseLayoutChangedEvent(); } }, }); this.nativeViewProtected.addOnLayoutChangeListener(this.layoutChangeListener); } } get isLayoutRequired() { return !this.isLayoutValid; } get isLayoutValid() { if (this.nativeViewProtected) { return !this.nativeViewProtected.isLayoutRequested(); } return false; } get _hasFragments() { return false; } layoutNativeView(left, top, right, bottom) { if (this.nativeViewProtected) { this.nativeViewProtected.layout(left, top, right, bottom); } } requestLayout() { super.requestLayout(); if (this.nativeViewProtected) { this.nativeViewProtected.requestLayout(); } } measure(widthMeasureSpec, heightMeasureSpec) { super.measure(widthMeasureSpec, heightMeasureSpec); this.onMeasure(widthMeasureSpec, heightMeasureSpec); } layout(left, top, right, bottom) { super.layout(left, top, right, bottom); this.onLayout(left, top, right, bottom); } onMeasure(widthMeasureSpec, heightMeasureSpec) { const view = this.nativeViewProtected; if (view) { view.measure(widthMeasureSpec, heightMeasureSpec); this.setMeasuredDimension(view.getMeasuredWidth(), view.getMeasuredHeight()); } } onLayout(left, top, right, bottom) { const view = this.nativeViewProtected; if (view) { this.layoutNativeView(left, top, right, bottom); } } _getCurrentLayoutBounds() { if (this.nativeViewProtected && !this.isCollapsed) { return { left: this.nativeViewProtected.getLeft(), top: this.nativeViewProtected.getTop(), right: this.nativeViewProtected.getRight(), bottom: this.nativeViewProtected.getBottom(), }; } else { return { left: 0, top: 0, right: 0, bottom: 0 }; } } getMeasuredWidth() { if (this.nativeViewProtected) { return this.nativeViewProtected.getMeasuredWidth(); } return super.getMeasuredWidth(); } getMeasuredHeight() { if (this.nativeViewProtected) { return this.nativeViewProtected.getMeasuredHeight(); } return super.getMeasuredHeight(); } focus() { if (this.nativeViewProtected) { return this.nativeViewProtected.requestFocus(); } return false; } getLocationInWindow() { if (!this.nativeViewProtected || !this.nativeViewProtected.getWindowToken()) { return undefined; } const nativeArray = Array.create('int', 2); this.nativeViewProtected.getLocationInWindow(nativeArray); return { x: layout.toDeviceIndependentPixels(nativeArray[0]), y: layout.toDeviceIndependentPixels(nativeArray[1]), }; } getLocationOnScreen() { if (!this.nativeViewProtected || !this.nativeViewProtected.getWindowToken()) { return undefined; } const nativeArray = Array.create('int', 2); this.nativeViewProtected.getLocationOnScreen(nativeArray); return { x: layout.toDeviceIndependentPixels(nativeArray[0]), y: layout.toDeviceIndependentPixels(nativeArray[1]), }; } getLocationRelativeTo(otherView) { if (!this.nativeViewProtected || !this.nativeViewProtected.getWindowToken() || !otherView || !otherView.nativeViewProtected || !otherView.nativeViewProtected.getWindowToken() || this.nativeViewProtected.getWindowToken() !== otherView.nativeViewProtected.getWindowToken()) { return undefined; } const myArray = Array.create('int', 2); this.nativeViewProtected.getLocationOnScreen(myArray); const otherArray = Array.create('int', 2); otherView.nativeViewProtected.getLocationOnScreen(otherArray); return { x: layout.toDeviceIndependentPixels(myArray[0] - otherArray[0]), y: layout.toDeviceIndependentPixels(myArray[1] - otherArray[1]), }; } static resolveSizeAndState(size, specSize, specMode, childMeasuredState) { let result = size; switch (specMode) { case layout.UNSPECIFIED: result = size; break; case layout.AT_MOST: if (specSize < size) { result = specSize | layout.MEASURED_STATE_TOO_SMALL; } break; case layout.EXACTLY: result = specSize; break; } return result | (childMeasuredState & layout.MEASURED_STATE_MASK); } _showNativeModalView(parent, options) { // if the app is in background while triggering _showNativeModalView // then DialogFragment.show will trigger IllegalStateException: Can not perform this action after onSaveInstanceState // so if in background we create an event to call _showNativeModalView when loaded (going back in foreground) if (isAppInBackground() && !parent.isLoaded) { const onLoaded = () => { parent.off('loaded', onLoaded); this._showNativeModalView(parent, options); }; parent.on('loaded', onLoaded); return; } super._showNativeModalView(parent, options); initializeDialogFragment(); const df = new DialogFragment(); const args = new android.os.Bundle(); args.putInt(DOMID, this._domId); df.setArguments(args); let cancelable = true; let windowSoftInputMode; if (options.android) { if (options.android.cancelable !== undefined) { cancelable = !!options.android.cancelable; console.log('ShowModalOptions.android.cancelable is deprecated. Use ShowModalOptions.cancelable instead.'); } windowSoftInputMode = options.android.windowSoftInputMode; } cancelable = options.cancelable !== undefined ? !!options.cancelable : cancelable; const dialogOptions = { owner: this, fullscreen: !!options.fullscreen, animated: !!options.animated, stretched: !!options.stretched, cancelable: cancelable, windowSoftInputMode: windowSoftInputMode, shownCallback: () => this._raiseShownModallyEvent(), dismissCallback: () => this.closeModal(), }; saveModal(dialogOptions); this._dialogFragment = df; this._raiseShowingModallyEvent(); this._dialogFragment.show(parent._getRootFragmentManager(), this._domId.toString()); } _hideNativeModalView(parent, whenClosedCallback) { if (this._dialogFragment) { const manager = this._dialogFragment.getFragmentManager(); if (manager) { this._dialogFragment.dismissAllowingStateLoss(); } } this._dialogFragment = null; whenClosedCallback(); } [isEnabledProperty.setNative](value) { this.nativeViewProtected.setEnabled(value); } [originXProperty.getDefault]() { return this.nativeViewProtected.getPivotX(); } [originXProperty.setNative](value) { org.nativescript.widgets.OriginPoint.setX(this.nativeViewProtected, value); } [originYProperty.getDefault]() { return this.nativeViewProtected.getPivotY(); } [originYProperty.setNative](value) { org.nativescript.widgets.OriginPoint.setY(this.nativeViewProtected, value); } [isUserInteractionEnabledProperty.setNative](value) { this.nativeViewProtected.setClickable(value); this.nativeViewProtected.setFocusable(value); } [hiddenProperty.getDefault]() { return this.nativeViewProtected.getVisibility() === android.view.View.GONE; } [hiddenProperty.setNative](value) { this.nativeViewProtected.setVisibility(value ? android.view.View.GONE : android.view.View.VISIBLE); } [visibilityProperty.getDefault]() { const nativeVisibility = this.nativeViewProtected.getVisibility(); switch (nativeVisibility) { case android.view.View.VISIBLE: return 'visible'; case android.view.View.INVISIBLE: return 'hidden'; case android.view.View.GONE: return 'collapse'; default: throw new Error(`Unsupported android.view.View visibility: ${nativeVisibility}. Currently supported values are android.view.View.VISIBLE, android.view.View.INVISIBLE, android.view.View.GONE.`); } } [visibilityProperty.setNative](value) { switch (value) { case 'visible': this.nativeViewProtected.setVisibility(android.view.View.VISIBLE); break; case 'hidden': this.nativeViewProtected.setVisibility(android.view.View.INVISIBLE); break; case 'collapse': this.nativeViewProtected.setVisibility(android.view.View.GONE); break; default: throw new Error(`Invalid visibility value: ${value}. Valid values are: visible, hidden, collapse.`); } } [opacityProperty.getDefault]() { return this.nativeViewProtected.getAlpha(); } [opacityProperty.setNative](value) { this.nativeViewProtected.setAlpha(float(value)); } [accessibilityRoleProperty.setNative](value) { this.accessibilityRole = value; updateA11yPropertiesCallback(this); if (SDK_VERSION >= 28) { this.nativeViewProtected?.setAccessibilityHeading(value === AccessibilityRole.Header); } } [accessibilityLiveRegionProperty.setNative](value) { switch (value) { case AccessibilityLiveRegion.Assertive: { this.nativeViewProtected.setAccessibilityLiveRegion(android.view.View.ACCESSIBILITY_LIVE_REGION_ASSERTIVE); break; } case AccessibilityLiveRegion.Polite: { this.nativeViewProtected.setAccessibilityLiveRegion(android.view.View.ACCESSIBILITY_LIVE_REGION_POLITE); break; } default: { this.nativeViewProtected.setAccessibilityLiveRegion(android.view.View.ACCESSIBILITY_LIVE_REGION_NONE); break; } } } [accessibilityStateProperty.setNative](value) { this.accessibilityState = value; updateA11yPropertiesCallback(this); } [horizontalAlignmentProperty.getDefault]() { return org.nativescript.widgets.ViewHelper.getHorizontalAlignment(this.nativeViewProtected); } [horizontalAlignmentProperty.setNative](value) { const nativeView = this.nativeViewProtected; const lp = nativeView.getLayoutParams() || new org.nativescript.widgets.CommonLayoutParams(); const gravity = lp.gravity; const weight = lp.weight; // Set only if params gravity exists. if (gravity != null) { switch (value) { case 'start': lp.gravity = (this.direction === CoreTypes.LayoutDirection.rtl ? GRAVITY_RIGHT : GRAVITY_LEFT) | (gravity & VERTICAL_GRAVITY_MASK); if (weight < 0) { lp.weight = -2; } break; case 'left': lp.gravity = GRAVITY_LEFT | (gravity & VERTICAL_GRAVITY_MASK); if (weight < 0) { lp.weight = -2; } break; case 'center': lp.gravity = GRAVITY_CENTER_HORIZONTAL | (gravity & VERTICAL_GRAVITY_MASK); if (weight < 0) { lp.weight = -2; } break; case 'right': lp.gravity = GRAVITY_RIGHT | (gravity & VERTICAL_GRAVITY_MASK); if (weight < 0) { lp.weight = -2; } break; case 'end': lp.gravity = (this.direction === CoreTypes.LayoutDirection.rtl ? GRAVITY_LEFT : GRAVITY_RIGHT) | (gravity & VERTICAL_GRAVITY_MASK); if (weight < 0) { lp.weight = -2; } break; case 'stretch': lp.gravity = GRAVITY_FILL_HORIZONTAL | (gravity & VERTICAL_GRAVITY_MASK); if (weight < 0) { lp.weight = -1; } break; } nativeView.setLayoutParams(lp); } } [verticalAlignmentProperty.getDefault]() { return org.nativescript.widgets.ViewHelper.getVerticalAlignment(this.nativeViewProtected); } [verticalAlignmentProperty.setNative](value) { const nativeView = this.nativeViewProtected; const lp = nativeView.getLayoutParams() || new org.nativescript.widgets.CommonLayoutParams(); const gravity = lp.gravity; const height = lp.height; // Set only if params gravity exists. if (gravity !== undefined) { switch (value) { case 'top': lp.gravity = GRAVITY_TOP | (gravity & HORIZONTAL_GRAVITY_MASK); if (height < 0) { lp.height = -2; } break; case 'middle': lp.gravity = GRAVITY_CENTER_VERTICAL | (gravity & HORIZONTAL_GRAVITY_MASK); if (height < 0) { lp.height = -2; } break; case 'bottom': lp.gravity = GRAVITY_BOTTOM | (gravity & HORIZONTAL_GRAVITY_MASK); if (height < 0) { lp.height = -2; } break; case 'stretch': lp.gravity = GRAVITY_FILL_VERTICAL | (gravity & HORIZONTAL_GRAVITY_MASK); if (height < 0) { lp.height = -1; } break; } nativeView.setLayoutParams(lp); } } [statusBarStyleProperty.getDefault]() { return this.style.statusBarStyle; } [statusBarStyleProperty.setNative](value) { this.updateStatusBarStyle(value); } updateStatusBarStyle(value) { if (SDK_VERSION < 21) return; // nothing we can do const window = this.getClosestWindow(); // Ensure the window draws system bar backgrounds (required to color status bar) window.clearFlags(android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); window.addFlags(android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); const decorView = window.getDecorView(); // API 30+ path (preferred) const controller = window.getInsetsController?.(); if (controller && SDK_VERSION >= 30) { const APPEARANCE_LIGHT_STATUS_BARS = android.view.WindowInsetsController?.APPEARANCE_LIGHT_STATUS_BARS; if (typeof value === 'string') { this.style.statusBarStyle = value; if (value === 'light') { // light icons/text controller.setSystemBarsAppearance(0, APPEARANCE_LIGHT_STATUS_BARS); } else { // dark icons/text controller.setSystemBarsAppearance(APPEARANCE_LIGHT_STATUS_BARS, APPEARANCE_LIGHT_STATUS_BARS); } } else { if (value.color != null) window.setStatusBarColor(value.color); // No direct passthrough for systemUiVisibility on API 30+, use appearances instead } return; } // API 23–29 path (systemUiVisibility) if (SDK_VERSION >= 23) { if (typeof value === 'string') { this.style.statusBarStyle = value; let flags = decorView.getSystemUiVisibility(); if (value === 'light') { // Add the LIGHT_STATUS_BAR bit without clobbering other flags flags |= android.view.View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; decorView.setSystemUiVisibility(flags); } else { // Remove only the LIGHT_STATUS_BAR bit flags &= ~android.view.View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; decorView.setSystemUiVisibility(flags); } } else { if (value.color != null) window.setStatusBarColor(value.color); if (value.systemUiVisibility != null) { // Preserve existing flags, don’t blindly overwrite to 0 const merged = (decorView.getSystemUiVisibility() & ~android.view.View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR) | (value.systemUiVisibility & android.view.View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); decorView.setSystemUiVisibility(merged); } } return; } // API 21–22: you can only change the background color; icon color is fixed (light) if (typeof value === 'object' && value.color != null) { window.setStatusBarColor(value.color); } } getClosestWindow() { // When it comes to modals, check parent as it may not be the modal root view itself const view = this.parent ?? this; const dialogFragment = view._dialogFragment; if (dialogFragment) { const dialog = dialogFragment.getDialog(); if (dialog) { return dialog.getWindow(); } } return this._context.getWindow(); } setAccessibilityIdentifier(view, value) { const id = Utils.android.resources.getId(':id/nativescript_accessibility_id'); if (id) { view.setTag(id, value); view.setTag(value); } if (this.testID && this.testID !== value) this.testID = value; if (this.accessibilityIdentifier !== value) this.accessibilityIdentifier = value; } [directionProperty.setNative](value) { const nativeView = this.nativeViewProtected; switch (value) { case CoreTypes.LayoutDirection.ltr: nativeView.setLayoutDirection(android.view.View.LAYOUT_DIRECTION_LTR); break; case CoreTypes.LayoutDirection.rtl: nativeView.setLayoutDirection(android.view.View.LAYOUT_DIRECTION_RTL); break; default: nativeView.setLayoutDirection(android.view.View.LAYOUT_DIRECTION_LOCALE); break; } } [testIDProperty.setNative](value) { this.setAccessibilityIdentifier(this.nativeViewProtected, value); } [accessibilityEnabledProperty.setNative](value) { this.nativeViewProtected.setFocusable(!!value); updateA11yPropertiesCallback(this); } [accessibilityIdentifierProperty.setNative](value) { this.setAccessibilityIdentifier(this.nativeViewProtected, value); } [accessibilityValueProperty.setNative](value) { this._androidContentDescriptionUpdated = true; updateContentDescription(this); } [accessibilityLabelProperty.setNative](value) { this._androidContentDescriptionUpdated = true; updateContentDescription(this); } [accessibilityHintProperty.setNative](value) { this._androidContentDescriptionUpdated = true; updateContentDescription(this); } [accessibilityHiddenProperty.setNative](value) { if (value) { this.nativeViewProtected.setImportantForAccessibility(android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); } else { this.nativeViewProtected.setImportantForAccessibility(android.view.View.IMPORTANT_FOR_ACCESSIBILITY_YES); } } [accessibilityMediaSessionProperty.setNative](value) { updateA11yPropertiesCallback(this); } [androidElevationProperty.getDefault]() { return this.getDefaultElevation(); } [androidElevationProperty.setNative](value) { if (SDK_VERSION < 21) { return; } this.refreshStateListAnimator(); } [androidDynamicElevationOffsetProperty.getDefault]() { return this.getDefaultDynamicElevationOffset(); } [androidDynamicElevationOffsetProperty.setNative](value) { if (SDK_VERSION < 21) { return; } this.refreshStateListAnimator(); } getDefaultElevation() { if (SDK_VERSION < 21) { return 0; } // NOTE: overriden in Button implementation as for widgets with StateListAnimator (Button) // nativeView.getElevation() returns 0 at the time of the getDefault() query return layout.toDeviceIndependentPixels(this.nativeViewProtected.getElevation()); } getDefaultDynamicElevationOffset() { // NOTE: overriden in Button implementation return 0; } refreshStateListAnimator() { const nativeView = this.nativeViewProtected; const ObjectAnimator = android.animation.ObjectAnimator; const AnimatorSet = android.animation.AnimatorSet; const duration = nativeView.getContext().getResources().getInteger(shortAnimTime) / 2; let elevation = this.androidElevation; if (typeof elevation === 'undefined' || elevation === null) { elevation = this.getDefaultElevation(); } elevation = layout.toDevicePixels(elevation); const z = layout.toDevicePixels(0); let pressedZ = this.androidDynamicElevationOffset; if (typeof pressedZ === 'undefined' || pressedZ === null) { pressedZ = this.getDefaultDynamicElevationOffset(); } pressedZ = layout.toDevicePixels(pressedZ); const pressedSet = new AnimatorSet(); pressedSet.playTogether(java.util.Arrays.asList([ObjectAnimator.ofFloat(nativeView, 'translationZ', [pressedZ]).setDuration(duration), ObjectAnimator.ofFloat(nativeView, 'elevation', [elevation]).setDuration(0)])); const notPressedSet = new AnimatorSet(); notPressedSet.playTogether(java.util.Arrays.asList([ObjectAnimator.ofFloat(nativeView, 'translationZ', [z]).setDuration(duration), ObjectAnimator.ofFloat(nativeView, 'elevation', [elevation]).setDuration(0)])); const defaultSet = new AnimatorSet(); defaultSet.playTogether(java.util.Arrays.asList([ObjectAnimator.ofFloat(nativeView, 'translationZ', [0]).setDuration(0), ObjectAnimator.ofFloat(nativeView, 'elevation', [0]).setDuration(0)])); const stateListAnimator = new android.animation.StateListAnimator(); stateListAnimator.addState([statePressed, stateEnabled], pressedSet); stateListAnimator.addState([stateEnabled], notPressedSet); stateListAnimator.addState([], defaultSet); const currentAnimator = nativeView.getStateListAnimator(); if (currentAnimator) { currentAnimator.jumpToCurrentState(); } nativeView.setStateListAnimator(stateListAnimator); } [rotateProperty.setNative](value) { org.nativescript.widgets.ViewHelper.setRotate(this.nativeViewProtected, float(value)); } [rotateXProperty.setNative](value) { org.nativescript.widgets.ViewHelper.setRotateX(this.nativeViewProtected, float(value));