diff --git a/packages/core/ui/action-bar/index.android.ts b/packages/core/ui/action-bar/index.android.ts index 137871df50..0a1bcdd15f 100644 --- a/packages/core/ui/action-bar/index.android.ts +++ b/packages/core/ui/action-bar/index.android.ts @@ -7,6 +7,9 @@ import { colorProperty } from '../styling/style-properties'; import { ImageSource } from '../../image-source'; import * as application from '../../application'; import { isAccessibilityServiceEnabled, updateContentDescription } from '../../accessibility'; +import type { Background } from '../styling/background'; +import { Device } from '../../platform'; +import lazy from '../../utils/lazy'; export * from './action-bar-common'; @@ -14,6 +17,8 @@ const R_ID_HOME = 0x0102002c; const ACTION_ITEM_ID_OFFSET = 10000; const DEFAULT_ELEVATION = 4; +const sdkVersion = lazy(() => parseInt(Device.sdkVersion)); + let AppCompatTextView; let actionItemIdGenerator = ACTION_ITEM_ID_OFFSET; function generateItemId(): number { @@ -59,7 +64,7 @@ function initializeMenuItemClickListener(): void { return; } - apiLevel = android.os.Build.VERSION.SDK_INT; + apiLevel = sdkVersion(); AppCompatTextView = androidx.appcompat.widget.AppCompatTextView; @@ -216,6 +221,32 @@ export class ActionBar extends ActionBarBase { this._updateNavigationButton(); } + public _applyBackground(background: Background, isBorderDrawable, onlyColor: boolean, backgroundDrawable: any) { + const nativeView = this.nativeViewProtected; + if (backgroundDrawable && onlyColor && sdkVersion() >= 21) { + if (isBorderDrawable && (nativeView)._cachedDrawable) { + backgroundDrawable = (nativeView)._cachedDrawable; + // we need to duplicate the drawable or we lose the "default" cached drawable + const constantState = backgroundDrawable.getConstantState(); + if (constantState) { + try { + backgroundDrawable = constantState.newDrawable(nativeView.getResources()); + // eslint-disable-next-line no-empty + } catch {} + } + nativeView.setBackground(backgroundDrawable); + } + + const backgroundColor = ((backgroundDrawable).backgroundColor = background.color.android); + backgroundDrawable.mutate(); + backgroundDrawable.setColorFilter(backgroundColor, android.graphics.PorterDuff.Mode.SRC_IN); + backgroundDrawable.invalidateSelf(); // Make sure the drawable is invalidated. Android forgets to invalidate it in some cases: toolbar + (backgroundDrawable).backgroundColor = backgroundColor; + } else { + super._applyBackground(background, isBorderDrawable, onlyColor, backgroundDrawable); + } + } + public _onAndroidItemSelected(itemId: number): boolean { // Handle home button if (this.navigationButton && itemId === R_ID_HOME) { diff --git a/packages/core/ui/button/index.android.ts b/packages/core/ui/button/index.android.ts index 3dbdb738f0..66c6446670 100644 --- a/packages/core/ui/button/index.android.ts +++ b/packages/core/ui/button/index.android.ts @@ -7,6 +7,7 @@ import { profile } from '../../profiling'; import { TouchGestureEventData, GestureTypes, TouchAction } from '../gestures'; import { Device } from '../../platform'; import lazy from '../../utils/lazy'; +import type { Background } from 'ui/styling/background'; export * from './button-common'; @@ -58,6 +59,32 @@ export class Button extends ButtonBase { private _stateListAnimator: any; private _highlightedHandler: (args: TouchGestureEventData) => void; + public _applyBackground(background: Background, isBorderDrawable, onlyColor: boolean, backgroundDrawable: any) { + const nativeView = this.nativeViewProtected; + if (backgroundDrawable && onlyColor) { + if (isBorderDrawable && (nativeView)._cachedDrawable) { + backgroundDrawable = (nativeView)._cachedDrawable; + // we need to duplicate the drawable or we lose the "default" cached drawable + const constantState = backgroundDrawable.getConstantState(); + if (constantState) { + try { + backgroundDrawable = constantState.newDrawable(nativeView.getResources()); + // eslint-disable-next-line no-empty + } catch {} + } + nativeView.setBackground(backgroundDrawable); + } + + const backgroundColor = ((backgroundDrawable).backgroundColor = background.color.android); + backgroundDrawable.mutate(); + backgroundDrawable.setColorFilter(backgroundColor, android.graphics.PorterDuff.Mode.SRC_IN); + backgroundDrawable.invalidateSelf(); // Make sure the drawable is invalidated. Android forgets to invalidate it in some cases: toolbar + (backgroundDrawable).backgroundColor = backgroundColor; + } else { + super._applyBackground(background, isBorderDrawable, onlyColor, backgroundDrawable); + } + } + @profile public createNativeView() { if (!AndroidButton) { diff --git a/packages/core/ui/core/view/index.android.ts b/packages/core/ui/core/view/index.android.ts index 22dcbca979..01939454eb 100644 --- a/packages/core/ui/core/view/index.android.ts +++ b/packages/core/ui/core/view/index.android.ts @@ -24,7 +24,7 @@ import lazy from '../../../utils/lazy'; import { accessibilityEnabledProperty, accessibilityHiddenProperty, accessibilityHintProperty, accessibilityIdentifierProperty, accessibilityLabelProperty, accessibilityLanguageProperty, accessibilityLiveRegionProperty, accessibilityMediaSessionProperty, accessibilityRoleProperty, accessibilityStateProperty, accessibilityValueProperty } from '../../../accessibility/accessibility-properties'; import { AccessibilityLiveRegion, AccessibilityRole, AndroidAccessibilityEvent, isAccessibilityServiceEnabled, sendAccessibilityEvent, updateAccessibilityProperties, updateContentDescription, AccessibilityState } from '../../../accessibility'; import * as Utils from '../../../utils'; -import { CSSShadow } from 'ui/styling/css-shadow'; +import { CSSShadow } from '../../styling/css-shadow'; export * from './view-common'; // helpers (these are okay re-exported here) @@ -1211,11 +1211,13 @@ export class View extends ViewCommon { 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 = BackgroundClearFlags.NONE; } diff --git a/packages/core/ui/core/view/index.d.ts b/packages/core/ui/core/view/index.d.ts index 7f056306e2..7b4778ddd1 100644 --- a/packages/core/ui/core/view/index.d.ts +++ b/packages/core/ui/core/view/index.d.ts @@ -830,6 +830,12 @@ export abstract class View extends ViewCommon { * @private */ _redrawNativeBackground(value: any): void; + /** + * @private + * method called on Android to apply the background. This allows custom handling + */ + _applyBackground(background: Background, isBorderDrawable: boolean, onlyColor: boolean, backgroundDrawable: any); + /** * @private */ diff --git a/packages/core/ui/core/view/view-common.ts b/packages/core/ui/core/view/view-common.ts index 44793398de..d1f3ac73e8 100644 --- a/packages/core/ui/core/view/view-common.ts +++ b/packages/core/ui/core/view/view-common.ts @@ -1090,6 +1090,9 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition { public _redrawNativeBackground(value: any): void { // } + public _applyBackground(background, isBorderDrawable: boolean, onlyColor: boolean, backgroundDrawable: any) { + // + } _onAttachedToWindow(): void { // diff --git a/packages/core/ui/styling/background.android.ts b/packages/core/ui/styling/background.android.ts index 3fd7e36ccc..29542c3872 100644 --- a/packages/core/ui/styling/background.android.ts +++ b/packages/core/ui/styling/background.android.ts @@ -1,141 +1,11 @@ import { View } from '../core/view'; import { LinearGradient } from './linear-gradient'; -import { CoreTypes } from '../../core-types'; -import { isDataURI, isFileOrResourcePath, layout, RESOURCE_PREFIX, FILE_PREFIX } from '../../utils'; +import { isDataURI, isFileOrResourcePath, RESOURCE_PREFIX, FILE_PREFIX } from '../../utils'; import { parse } from '../../css-value'; import { path, knownFolders } from '../../file-system'; import * as application from '../../application'; -import { profile } from '../../profiling'; -import { CSSShadow } from './css-shadow'; -import { Length } from './style-properties'; -import { BackgroundClearFlags } from './background-common'; export * from './background-common'; -interface AndroidView { - _cachedDrawable: android.graphics.drawable.Drawable.ConstantState | android.graphics.drawable.Drawable; -} - -// TODO: Change this implementation to use -// We are using "ad" here to avoid namespace collision with the global android object -export namespace ad { - let SDK: number; - function getSDK() { - if (!SDK) { - SDK = android.os.Build.VERSION.SDK_INT; - } - - return SDK; - } - - function isSetColorFilterOnlyWidget(nativeView: android.view.View): boolean { - // prettier-ignore - return ( - nativeView instanceof android.widget.Button - || (nativeView instanceof androidx.appcompat.widget.Toolbar && getSDK() >= 21) - // There is an issue with the DrawableContainer which was fixed - // for API version 21 and above: https://code.google.com/p/android/issues/detail?id=60183 - ); - } - - export function onBackgroundOrBorderPropertyChanged(view: View) { - const nativeView = view.nativeViewProtected; - if (!nativeView) { - return; - } - - const background = view.style.backgroundInternal; - - if (background.clearFlags & BackgroundClearFlags.CLEAR_BOX_SHADOW || background.clearFlags & BackgroundClearFlags.CLEAR_BACKGROUND_COLOR) { - // clear background if we're clearing the box shadow - // or the background has been removed - nativeView.setBackground(null); - } - - let drawable = nativeView.getBackground(); - const androidView = (view) as AndroidView; - // use undefined as not set. getBackground will never return undefined only Drawable or null; - if (androidView._cachedDrawable === undefined && drawable) { - const constantState = drawable.getConstantState(); - androidView._cachedDrawable = constantState || drawable; - } - const isBorderDrawable = drawable instanceof org.nativescript.widgets.BorderDrawable; - - // prettier-ignore - const onlyColor = !background.hasBorderWidth() - && !background.hasBorderRadius() - && !background.hasBoxShadow() - && !background.clipPath - && !background.image - && !!background.color; - - if (!isBorderDrawable && drawable instanceof android.graphics.drawable.ColorDrawable && onlyColor) { - drawable.setColor(background.color.android); - drawable.invalidateSelf(); - } else if (isSetColorFilterOnlyWidget(nativeView) && drawable && onlyColor) { - if (isBorderDrawable && androidView._cachedDrawable) { - if (!(androidView._cachedDrawable instanceof android.graphics.drawable.Drawable.ConstantState)) { - return; - } - - drawable = androidView._cachedDrawable.newDrawable(nativeView.getResources()); - nativeView.setBackground(drawable); - } - - const backgroundColor = ((drawable).backgroundColor = background.color.android); - drawable.mutate(); - drawable.setColorFilter(backgroundColor, android.graphics.PorterDuff.Mode.SRC_IN); - drawable.invalidateSelf(); // Make sure the drawable is invalidated. Android forgets to invalidate it in some cases: toolbar - (drawable).backgroundColor = backgroundColor; - } else if (!isBorderDrawable && onlyColor) { - // this is the fastest way to change only background color - nativeView.setBackgroundColor(background.color.android); - } else if (!background.isEmpty()) { - let backgroundDrawable = drawable; - - if (drawable instanceof org.nativescript.widgets.BoxShadowDrawable) { - // if we have BoxShadow's we have to get the underlying drawable - backgroundDrawable = drawable.getWrappedDrawable(); - } - - if (backgroundDrawable instanceof org.nativescript.widgets.BorderDrawable) { - refreshBorderDrawable(view, backgroundDrawable); - } else { - backgroundDrawable = new org.nativescript.widgets.BorderDrawable(layout.getDisplayDensity(), view.toString()); - refreshBorderDrawable(view, backgroundDrawable); - nativeView.setBackground(backgroundDrawable); - } - } else { - const cachedDrawable = androidView._cachedDrawable; - let defaultDrawable: android.graphics.drawable.Drawable = null; - if (cachedDrawable) { - if (cachedDrawable instanceof android.graphics.drawable.Drawable.ConstantState) { - defaultDrawable = cachedDrawable.newDrawable(nativeView.getResources()); - } else if (cachedDrawable instanceof android.graphics.drawable.Drawable) { - defaultDrawable = cachedDrawable; - } - } - - nativeView.setBackground(defaultDrawable); - } - - if (background.hasBoxShadow()) { - drawBoxShadow(nativeView, view, 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(view.effectiveBorderLeftWidth + view.effectivePaddingLeft); - const topPadding = Math.ceil(view.effectiveBorderTopWidth + view.effectivePaddingTop); - const rightPadding = Math.ceil(view.effectiveBorderRightWidth + view.effectivePaddingRight); - const bottomPadding = Math.ceil(view.effectiveBorderBottomWidth + view.effectivePaddingBottom); - - nativeView.setPadding(leftPadding, topPadding, rightPadding, bottomPadding); - - // reset clear flags - background.clearFlags = BackgroundClearFlags.NONE; - } -} - function fromBase64(source: string): android.graphics.Bitmap { const bytes = android.util.Base64.decode(source, android.util.Base64.DEFAULT); @@ -253,18 +123,6 @@ function createNativeCSSValueArray(css: string): androidNative.Array