@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
JavaScript
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));