UNPKG

@nativescript/core

Version:

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

1,144 lines (1,143 loc) • 52.8 kB
import { ViewCommon, isEnabledProperty, originXProperty, originYProperty, isUserInteractionEnabledProperty, testIDProperty, AndroidHelper } from './view-common'; import { paddingLeftProperty, paddingTopProperty, paddingRightProperty, paddingBottomProperty, Length } from '../../styling/style-properties'; import { layout } from '../../../utils'; import { Trace } from '../../../trace'; import { hiddenProperty } from '../view-base'; import { isCssWideKeyword } from '../properties'; 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 { Background, refreshBorderDrawable } from '../../styling/background'; import { profile } from '../../../profiling'; import { topmost } from '../../frame/frame-stack'; import { Screen } from '../../../platform'; import { Application } from '../../../application'; import { accessibilityEnabledProperty, accessibilityHiddenProperty, accessibilityHintProperty, accessibilityIdentifierProperty, accessibilityLabelProperty, accessibilityLiveRegionProperty, accessibilityMediaSessionProperty, accessibilityRoleProperty, accessibilityStateProperty, accessibilityValueProperty } from '../../../accessibility/accessibility-properties'; import { AccessibilityLiveRegion, AccessibilityRole, AndroidAccessibilityEvent, updateAccessibilityProperties, updateContentDescription } 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 modalMap = new Map(); let TouchListener; let DialogFragment; function initializeTouchListener() { if (TouchListener) { return; } var TouchListenerImpl = /** @class */ (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); }; var _a; TouchListenerImpl = __decorate([ Interfaces([android.view.View.OnTouchListener]), __metadata("design:paramtypes", [typeof (_a = typeof View !== "undefined" && View) === "function" ? _a : Object]) ], TouchListenerImpl); return TouchListenerImpl; }(java.lang.Object)); TouchListener = TouchListenerImpl; } function initializeDialogFragment() { if (DialogFragment) { return; } var DialogImpl = /** @class */ (function (_super) { __extends(DialogImpl, _super); function DialogImpl(fragment, context, themeResId) { var _this = _super.call(this, context, themeResId) || this; _this.fragment = fragment; return global.__native(_this); } DialogImpl.prototype.onDetachedFromWindow = function () { _super.prototype.onDetachedFromWindow.call(this); this.fragment = null; }; DialogImpl.prototype.onBackPressed = function () { var view = this.fragment.owner; var args = { eventName: 'activityBackPressed', object: view, activity: view._context, cancel: false, }; // Fist fire application.android global event Application.android.notify(args); if (args.cancel) { return; } view.notify(args); if (!args.cancel && !view.onBackPressed()) { _super.prototype.onBackPressed.call(this); } }; return DialogImpl; }(android.app.Dialog)); var DialogFragmentImpl = /** @class */ (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); // The teardown when the activity is destroyed happens after the state is saved, but is not recoverable, // Cancel the native dialog in this case or the app will crash with subsequent errors. 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; // Set owner._dialogFragment to this in case the DialogFragment was recreated after app suspend 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) { // In fullscreen mode, get the application's theme. theme = this.getActivity().getApplicationInfo().theme; } var dialog = new DialogImpl(this, this.getActivity(), theme); // do not override alignment unless fullscreen modal will be shown; // otherwise we might break component-level layout: // https://github.com/NativeScript/NativeScript/issues/5392 if (!this._fullscreen && !this._stretched) { this.owner.horizontalAlignment = 'center'; this.owner.verticalAlignment = 'middle'; } else { this.owner.horizontalAlignment = 'stretch'; this.owner.verticalAlignment = 'stretch'; } // set the modal window animation // https://github.com/NativeScript/NativeScript/issues/5989 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; // we need to set the window SoftInputMode here. // it wont work is set in onStart var window = this.getDialog().getWindow(); if (this._windowSoftInputMode !== undefined) { window.setSoftInputMode(this._windowSoftInputMode); } else { // the dialog seems to not follow the default activity softInputMode, // thus set we set it here. 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); // This removes the default backgroundDrawable so there are no margins. 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) { // Android calls onDestroy before onDismiss. // Make sure we unload first and then call _tearDownUI. 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); } 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(); } } 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; } } _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; } 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) { for (const type in this._gestureObservers) { const list = this._gestureObservers[type]; list.forEach((element) => { element.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(); } } 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 (Application.inBackground && !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)); } [testIDProperty.setNative](value) { this.setAccessibilityIdentifier(this.nativeViewProtected, value); } 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; } [accessibilityEnabledProperty.setNative](value) { this.nativeViewProtected.setFocusable(!!value); updateAccessibilityProperties(this); } [accessibilityIdentifierProperty.setNative](value) { this.setAccessibilityIdentifier(this.nativeViewProtected, value); } [accessibilityRoleProperty.setNative](value) { this.accessibilityRole = value; updateAccessibilityProperties(this); if (SDK_VERSION >= 28) { this.nativeViewProtected?.setAccessibilityHeading(value === AccessibilityRole.Header); } } [accessibilityValueProperty.setNative]() { this._androidContentDescriptionUpdated = true; updateContentDescription(this); } [accessibilityLabelProperty.setNative]() { this._androidContentDescriptionUpdated = true; updateContentDescription(this); } [accessibilityHintProperty.setNative]() { 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); } } [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; updateAccessibilityProperties(this); } [accessibilityMediaSessionProperty.setNative]() { updateAccessibilityProperties(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); } [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 !== undefined) { switch (value) { 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 '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); } } [rotateProperty.setNative](value) { org.nativescript.widgets.ViewHelper.setRotate(this.nativeViewProtected, float(value)); } [rotateXProperty.setNative](value) { org.nativescript.widgets.ViewHelper.setRotateX(this.nativeViewProtected, float(value)); } [rotateYProperty.setNative](value) { org.nativescript.widgets.ViewHelper.setRotateY(this.nativeViewProtected, float(value)); } [perspectiveProperty.setNative](value) { org.nativescript.widgets.ViewHelper.setPerspective(this.nativeViewProtected, float(value * Screen.mainScreen.scale)); } [scaleXProperty.setNative](value) { org.nativescript.widgets.ViewHelper.setScaleX(this.nativeViewProtected, float(value)); } [scaleYProperty.setNative](value) { org.nativescript.widgets.ViewHelper.setScaleY(this.nativeViewProtected, float(value)); } [translateXProperty.setNative](value) { org.nativescript.widgets.ViewHelper.setTranslateX(this.nativeViewProtected, layout.toDevicePixels(value)); } [translateYProperty.setNative](value) { org.nativescript.widgets.ViewHelper.setTranslateY(this.nativeViewProtected, layout.toDevicePixels(value)); } [zIndexProperty.getDefault]() { return 0; } [zIndexProperty.setNative](value) { org.nativescript.widgets.ViewHelper.setZIndex(this.nativeViewProtected, value); } [backgroundInternalProperty.getDefault]() { const nativeView = this.nativeViewProtected; return AndroidHelper.getCopyOrDrawable(nativeView.getBackground(), nativeView.getResources()); } [backgroundInternalProperty.setNative](value) { this._redrawNativeBackground(value); } [minWidthProperty.setNative](value) { if (this.parent instanceof CustomLayoutView && this.parent.nativeViewProtected) { this.parent._setChildMinWidthNative(this, value); } else { this._setMinWidthNative(value); } } [minHeightProperty.setNative](value) { if (this.parent instanceof CustomLayoutView && this.parent.nativeViewProtected) { this.parent._setChildMinHeightNative(this, value); } else { this._setMinHeightNative(value); } } _applyBackground(background, isBorderDrawable, onlyColor, backgroundDrawable) { const nativeView = this.nativeViewProtected; if (onlyColor) { const backgroundColor = background.color.android; if (isBorderDrawable) { // We need to duplicate the drawable or we lose the "default" cached drawable backgroundDrawable = nativeView._cachedDrawable != null ? AndroidHelper.getCopyOrDrawable(nativeView._cachedDrawable, nativeView.getResources()) : null; nativeView.setBackground(backgroundDrawable); } // Apply color to drawables when there is the need to maintain visual things like button ripple effect if (this.needsNativeDrawableFill && backgroundDrawable) { backgroundDrawable.mutate(); AndroidHelper.setDrawableColor(backgroundColor, backgroundDrawable); backgroundDrawable.invalidateSelf(); } else { nativeView.setBackgroundColor(backgroundColor); } } else { if (background.clearFlags & 1 /* BackgroundClearFlags.CLEAR_BACKGROUND_COLOR */) { if (backgroundDrawable) { backgroundDrawable.mutate(); AndroidHelper.clearDrawableColor(backgroundDrawable); backgroundDrawable.invalidateSelf(); } else { nativeView.setBackgroundColor(-1); } } if (background.isEmpty()) { // Reset background to default if not already set const defaultDrawable = nativeView._cachedDrawable ?? null; if (backgroundDrawable !== defaultDrawable) { nativeView.setBackground(defaultDrawable); } } else { if (isBorderDrawable) { // org.nativescript.widgets.BorderDrawable refreshBorderDrawable(this, backgroundDrawable); } else { const borderDrawable = new org.nativescript.widgets.BorderDrawable(layout.getDisplayDensity(), this.toString()); refreshBorderDrawable(this, borderDrawable); nativeView.setBackground(borderDrawable); } } } } _drawBoxShadow(boxShadow) { const nativeView = this.nativeViewProtected; const config = { shadowColor: boxShadow.color.android, cornerRadius: Length.toDevicePixels(this.borderRadius, 0.0), spreadRadius: boxShadow.spreadRadius, blurRadius: boxShadow.blurRadius, offsetX: boxShadow.offsetX, offsetY: boxShadow.offsetY, }; org.nativescript.widgets.Utils.drawBoxShadow(nativeView, JSON.stringify(config)); } _redrawNativeBackground(value) { if (value instanceof Background) { this.onBackgroundOrBorderPropertyChanged(); } else { const nativeView = this.nativeViewProtected; nativeView.setBackground(value); const style = this.style; const paddingTop = paddingTopProperty.isSet(style) ? this.effectivePaddingTop : this._defaultPaddingTop; const paddingRight = paddingRightProperty.isSet(style) ? this.effectivePaddingRight : this._defaultPaddingRight; const paddingBottom = paddingBottomProperty.isSet(style) ? this.effectivePaddingBottom : this._defaultPaddingBottom; const paddingLeft = paddingLeftProperty.isSet(style) ? this.effectivePaddingLeft : this._defaultPaddingLeft; if (this._isPaddingRelative) { nativeView.setPaddingRelative(paddingLeft, paddingTop, paddingRight, paddingBottom); } else { nativeView.setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom); } } } onBackgroundOrBorderPropertyChanged() { const nativeView = this.nativeViewProtected; if (!nativeView) { return; } const background = this.style.backgroundInternal; const drawable = nativeView.getBackground(); const isBorderDrawable = drawable instanceof org.nativescript.widgets.BorderDrawable; // Use undefined as not set. getBackground will never return undefined only Drawable or null; if (nativeView._cachedDrawable === undefined) { nativeView._cachedDrawable = drawable; } if (background.clearFlags & 2 /* BackgroundClearFlags.CLEAR_BOX_SHADOW */) { // Clear background if we're clearing the box shadow if (drawable instanceof org.nativescript.widgets.BoxShadowDrawable) { nativeView.setBackground(nativeView._cachedDrawable ?? null); } } // prettier-ignore const onlyColor = !background.hasBorderWidth() && !background.hasBorderRadius() && !background.hasBoxShadow() && !background.clipPath && !background.image && !!background.color; this._applyBackground(background, isBorderDrawable, onlyColor, drawable); if (background.hasBoxShadow()) { this._drawBoxShadow(background.getBoxShadow()); } // TODO: Can we move BorderWidths as separate native setter? // This way we could skip setPadding if borderWidth is not changed. const leftPadding = Math.ceil(this.effectiveBorderLeftWidth + this.effectivePaddingLeft); const topPadding = Math.ceil(this.effectiveBorderTopWidth + this.effectivePaddingTop); const rightPadding = Math.ceil(this.effectiveBorderRightWidth + this.effectivePaddingRight); const bottomPadding = Math.ceil(this.effectiveBorderBottomWidth + this.effectivePaddingBottom); if (this._isPaddingRelative) { nativeView.setPaddingRelative(leftPadding, topPadding, rightPadding, bottomPadding); } else { nativeView.setPadding(leftPadding, topPadding, rightPadding, bottomPadding); } // reset clear flags background.clearFlags = 0 /* BackgroundClearFlags.NONE */; } accessibilityAnnouncement(message = this.accessibilityLabel) { this.sendAccessibilityEvent({ androidAccessibilityEvent: AndroidAccessibilityEvent.ANNOUNCEMENT, message, }); } accessibilityScreenChanged() { this.sendAccessibilityEvent({ androidAccessibilityEvent: AndroidAccessibilityEvent.WINDOW_STATE_CHANGED, }); } } View.androidBackPressedEvent = androidBackPressedEvent; __decorate([ profile, __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", void 0) ], View.prototype, "onLoaded", null); __decorate([ profile, __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", void 0) ], View.prototype, "onUnloaded", null); __decorate([ profile, __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", void 0) ], View.prototype, "requestLayout", null); export class ContainerView extends View { } export class CustomLayoutView extends ContainerView { createNativeView() { return new org.nativescript.widgets.ContentLayout(this._context); } _addViewToNativeVisualTree(child, atIndex = Number.MAX_SAFE_INTEGER) { super._addViewToNativeVisualTree(child); if (this.nativeViewProtected && child.nativeViewProtected) { if (Trace.isEnabled()) { Trace.write(`${this}.nativeView.addView(${child}.nativeView, ${atIndex})`, Trace.categories.VisualTreeEvents); } this.nativeViewProtected.addView(child.nativeViewProtected, atIndex); if (child instanceof View) { this._updateNativeLayoutParams(child); } return true; } return false; } _updateNativeLayoutParams(child) { // noop } _setChildMinWidthNative(child, value) { child._setMinWidthNative(value); } _setChildMinHeightNative(child, value) { child._setMinHeightNative(value); } _removeViewFromNativeVisualTree(child) { super._removeViewFromNativeVisualTree(child); const nativeView = this.nativeViewProtected; const childView = child.nativeViewProtected; if (nativeView && childView) { nativeView.removeView(childView); if (Trace.isEnabled()) { Trace.write(`${nativeView}.removeView(${childView})`, Trace.categories.VisualTreeEvents); Trace.notifyEvent(child, 'childInLayoutRemovedFromNativeVisualTree'); } } } } const percentNotSupported = (view, value) => { throw new Error('PercentLength is not supported.'); }; function createNativePercentLengthProperty(options) { const { getter, setter, auto = 0 } = options; let setPixels, getPixels, setPercent; if (getter) { View.prototype[getter] = function () { if (options) { setPixels = options.setPixels; getPixels = options.getPixels; setPercent = options.setPercent || percentNotSupported; options = null; } const value = getPixels(this.nativeViewProtected); if (value == auto) { // tslint:disable-line return 'auto'; } else { return { value, unit: 'px' }; } }; } if (setter) { View.prototype[setter] = function (length) { if (options) { setPixels = options.setPixels; getPixels = options.getPixels; setPercent = options.setPercent || percentNotSupported; options = null; } if (length == 'auto' || length == null || isCssWideKeyword(length)) { // tslint:disable-line setPixels(this.nativeViewProtected, auto); } else if (typeof length === 'number') { setPixels(this.nativeViewProtected, layout.round(layout.toDevicePixels(length))); } else if (length.unit == 'dip') {