From 4352ac6a85b82fca71d13e3580da98ce4e73041f Mon Sep 17 00:00:00 2001 From: Nathan Walker Date: Fri, 14 Jan 2022 12:06:31 -0800 Subject: [PATCH] fix(core): Application handling of nativeApp instance --- apps/automated/src/http/http-string-worker.ts | 8 +- packages/core/application/index.android.ts | 13 ++- packages/core/application/index.d.ts | 9 ++ packages/core/application/index.ios.ts | 20 +++- packages/core/index.ts | 4 +- packages/core/ui/core/view/index.android.ts | 103 +++++++++++++++++- 6 files changed, 143 insertions(+), 14 deletions(-) diff --git a/apps/automated/src/http/http-string-worker.ts b/apps/automated/src/http/http-string-worker.ts index a45ebb62d..e18e5d684 100644 --- a/apps/automated/src/http/http-string-worker.ts +++ b/apps/automated/src/http/http-string-worker.ts @@ -1,13 +1,13 @@ // todo: figure out why this worker is including the whole core and not just the Http module // ie. tree-shaking is not working as expected here. (same setup works in a separate app) -import { initGlobal } from '@nativescript/core/globals/index'; -initGlobal(); +// import { initGlobal } from '@nativescript/core/globals/index'; +// initGlobal(); -import { Http } from '@nativescript/core'; +import { getString } from '@nativescript/core/http'; declare var postMessage: any; -Http.getString('https://httpbin.org/get').then( +getString('https://httpbin.org/get').then( function (r) { postMessage(r); }, diff --git a/packages/core/application/index.android.ts b/packages/core/application/index.android.ts index e334f47b4..5184e2220 100644 --- a/packages/core/application/index.android.ts +++ b/packages/core/application/index.android.ts @@ -46,9 +46,15 @@ export class AndroidApplication extends Observable implements AndroidApplication private _systemAppearance: 'light' | 'dark'; public paused: boolean; public nativeApp: android.app.Application; + /** + * @deprecated Use Utils.android.getApplicationContext() instead. + */ public context: android.content.Context; public foregroundActivity: androidx.appcompat.app.AppCompatActivity; public startActivity: androidx.appcompat.app.AppCompatActivity; + /** + * @deprecated Use Utils.android.getPackageName() instead. + */ public packageName: string; // we are using these property to store the callbacks to avoid early GC collection which would trigger MarkReachableObjects private callbacks: any = {}; @@ -165,7 +171,7 @@ export { androidApp as android }; let mainEntry: NavigationEntry; let started = false; -function ensureNativeApplication() { +export function ensureNativeApplication() { if (!androidApp) { androidApp = new AndroidApplication(); appCommon.setApplication(androidApp); @@ -207,6 +213,7 @@ export function addCss(cssText: string, attributeScoped?: boolean): void { const CALLBACKS = '_callbacks'; export function _resetRootView(entry?: NavigationEntry | string): void { + ensureNativeApplication(); const activity = androidApp.foregroundActivity || androidApp.startActivity; if (!activity) { throw new Error('Cannot find android activity.'); @@ -225,6 +232,7 @@ export function getMainEntry() { } export function getRootView(): View { + ensureNativeApplication(); // Use start activity as a backup when foregroundActivity is still not set // in cases when we are getting the root view before activity.onResumed event is fired const activity = androidApp.foregroundActivity || androidApp.startActivity; @@ -269,14 +277,17 @@ export function getNativeApplication(): android.app.Application { } export function orientation(): 'portrait' | 'landscape' | 'unknown' { + ensureNativeApplication(); return androidApp.orientation; } export function systemAppearance(): 'dark' | 'light' { + ensureNativeApplication(); return androidApp.systemAppearance; } global.__onLiveSync = function __onLiveSync(context?: ModuleContext) { + ensureNativeApplication(); if (androidApp && androidApp.paused) { return; } diff --git a/packages/core/application/index.d.ts b/packages/core/application/index.d.ts index 0db88fdab..37c63cbba 100644 --- a/packages/core/application/index.d.ts +++ b/packages/core/application/index.d.ts @@ -360,6 +360,13 @@ export function systemAppearance(): 'dark' | 'light' | null; */ export let android: AndroidApplication; +/** + * Used internally for backwards compatibility, will be removed in the future. + * Allowed Application.android.context to work (or Application.ios). Instead use Utils.android.getApplicationContext() or Utils.android.getPackageName() + * @internal + */ +export function ensureNativeApplication(): void; + /** * This is the iOS-specific application object instance. * Encapsulates methods and properties specific to the iOS platform. @@ -468,6 +475,7 @@ export class AndroidApplication extends Observable { /** * The application's [android Context](http://developer.android.com/reference/android/content/Context.html) object instance. + * @deprecated Use Utils.android.getApplicationContext() instead. */ context: any /* android.content.Context */; @@ -495,6 +503,7 @@ export class AndroidApplication extends Observable { /** * The name of the application package. + * @deprecated Use Utils.android.getPackageName() instead. */ packageName: string; diff --git a/packages/core/application/index.ios.ts b/packages/core/application/index.ios.ts index 2ae32acbb..d06766c3a 100644 --- a/packages/core/application/index.ios.ts +++ b/packages/core/application/index.ios.ts @@ -353,8 +353,16 @@ let iosApp: iOSApplication; /* tslint:enable */ export { iosApp as ios }; +export function ensureNativeApplication() { + if (!iosApp) { + iosApp = new iOSApplication(); + setApplication(iosApp); + } +} + // attach on global, so it can be overwritten in NativeScript Angular (global).__onLiveSyncCore = function (context?: ModuleContext) { + ensureNativeApplication(); iosApp._onLivesync(context); }; @@ -383,15 +391,13 @@ export function getMainEntry() { } export function getRootView() { + ensureNativeApplication(); return iosApp.rootView; } let started = false; export function run(entry?: string | NavigationEntry) { - if (!iosApp) { - iosApp = new iOSApplication(); - setApplication(iosApp); - } + ensureNativeApplication(); mainEntry = typeof entry === 'string' ? { moduleName: entry } : entry; started = true; @@ -461,11 +467,13 @@ export function addCss(cssText: string, attributeScoped?: boolean): void { } export function _resetRootView(entry?: NavigationEntry | string) { + ensureNativeApplication(); mainEntry = typeof entry === 'string' ? { moduleName: entry } : entry; iosApp.setWindowContent(); } export function getNativeApplication(): UIApplication { + ensureNativeApplication(); return iosApp.nativeApp; } @@ -506,6 +514,7 @@ function setViewControllerView(view: View): void { } function setRootViewsCssClasses(rootView: View): void { + ensureNativeApplication(); const deviceType = Device.deviceType.toLowerCase(); CSSUtils.pushToSystemCssClasses(`${CSSUtils.CLASS_PREFIX}${IOS_PLATFORM}`); @@ -518,6 +527,7 @@ function setRootViewsCssClasses(rootView: View): void { } function setRootViewsSystemAppearanceCssClass(rootView: View): void { + ensureNativeApplication(); if (majorVersion >= 13) { const systemAppearanceCssClass = `${CSSUtils.CLASS_PREFIX}${iosApp.systemAppearance}`; CSSUtils.pushToSystemCssClasses(systemAppearanceCssClass); @@ -526,10 +536,12 @@ function setRootViewsSystemAppearanceCssClass(rootView: View): void { } export function orientation(): 'portrait' | 'landscape' | 'unknown' { + ensureNativeApplication(); return iosApp.orientation; } export function systemAppearance(): 'dark' | 'light' { + ensureNativeApplication(); return iosApp.systemAppearance; } diff --git a/packages/core/index.ts b/packages/core/index.ts index 3ff3545d0..c84bb8296 100644 --- a/packages/core/index.ts +++ b/packages/core/index.ts @@ -5,7 +5,7 @@ import './globals'; export { iOSApplication, AndroidApplication } from './application'; export type { ApplicationEventData, LaunchEventData, OrientationChangedEventData, UnhandledErrorEventData, DiscardedErrorEventData, CssChangedEventData, LoadAppCSSEventData, AndroidActivityEventData, AndroidActivityBundleEventData, AndroidActivityRequestPermissionsEventData, AndroidActivityResultEventData, AndroidActivityNewIntentEventData, AndroidActivityBackPressedEventData, SystemAppearanceChangedEventData } from './application'; -import { fontScaleChangedEvent, launchEvent, displayedEvent, uncaughtErrorEvent, discardedErrorEvent, suspendEvent, resumeEvent, exitEvent, lowMemoryEvent, orientationChangedEvent, systemAppearanceChanged, systemAppearanceChangedEvent, getMainEntry, getRootView, _resetRootView, getResources, setResources, setCssFileName, getCssFileName, loadAppCss, addCss, on, off, notify, hasListeners, run, orientation, getNativeApplication, hasLaunched, android as appAndroid, ios as iosApp, systemAppearance, setAutoSystemAppearanceChanged } from './application'; +import { fontScaleChangedEvent, launchEvent, displayedEvent, uncaughtErrorEvent, discardedErrorEvent, suspendEvent, resumeEvent, exitEvent, lowMemoryEvent, orientationChangedEvent, systemAppearanceChanged, systemAppearanceChangedEvent, getMainEntry, getRootView, _resetRootView, getResources, setResources, setCssFileName, getCssFileName, loadAppCss, addCss, on, off, notify, hasListeners, run, orientation, getNativeApplication, hasLaunched, android as appAndroid, ios as iosApp, systemAppearance, setAutoSystemAppearanceChanged, ensureNativeApplication } from './application'; export const Application = { launchEvent, displayedEvent, @@ -40,9 +40,11 @@ export const Application = { systemAppearance, setAutoSystemAppearanceChanged, get android() { + ensureNativeApplication(); return appAndroid; }, get ios() { + ensureNativeApplication(); return iosApp; }, }; diff --git a/packages/core/ui/core/view/index.android.ts b/packages/core/ui/core/view/index.android.ts index 15f464580..3dbf3d4fd 100644 --- a/packages/core/ui/core/view/index.android.ts +++ b/packages/core/ui/core/view/index.android.ts @@ -4,7 +4,7 @@ import type { GestureTypes, GestureEventData } from '../../gestures'; // Types. import { ViewCommon, isEnabledProperty, originXProperty, originYProperty, isUserInteractionEnabledProperty } from './view-common'; -import { paddingLeftProperty, paddingTopProperty, paddingRightProperty, paddingBottomProperty } from '../../styling/style-properties'; +import { paddingLeftProperty, paddingTopProperty, paddingRightProperty, paddingBottomProperty, Length } from '../../styling/style-properties'; import { layout } from '../../../utils'; import { Trace } from '../../../trace'; import { ShowModalOptions, hiddenProperty } from '../view-base'; @@ -14,6 +14,7 @@ import { perspectiveProperty, visibilityProperty, opacityProperty, horizontalAli import { CoreTypes } from '../../../core-types'; import { Background, ad as androidBackground } from '../../styling/background'; +import { BackgroundClearFlags, refreshBorderDrawable } from '../../styling/background.android'; import { profile } from '../../../profiling'; import { topmost } from '../../frame/frame-stack'; import { Screen } from '../../../platform'; @@ -23,6 +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, setupAccessibleView, isAccessibilityServiceEnabled, sendAccessibilityEvent, updateAccessibilityProperties, updateContentDescription, AccessibilityState } from '../../../accessibility'; import * as Utils from '../../../utils'; +import { CSSShadow } from '../../styling/css-shadow'; export * from './view-common'; // helpers (these are okay re-exported here) @@ -56,6 +58,10 @@ const modalMap = new Map(); let TouchListener: TouchListener; let DialogFragment: DialogFragment; +interface AndroidView { + _cachedDrawable: android.graphics.drawable.Drawable.ConstantState | android.graphics.drawable.Drawable; +} + interface DialogOptions { owner: View; fullscreen: boolean; @@ -1101,9 +1107,48 @@ export class View extends ViewCommon { } } + public _applyBackground(background: Background, isBorderDrawable: boolean, onlyColor: boolean, backgroundDrawable: any) { + const nativeView = this.nativeViewProtected; + if (!isBorderDrawable && onlyColor) { + if (backgroundDrawable && backgroundDrawable.setColor) { + // android.graphics.drawable.ColorDrawable + backgroundDrawable.setColor(background.color.android); + backgroundDrawable.invalidateSelf(); + } else { + nativeView.setBackgroundColor(background.color.android); + } + } else if (!background.isEmpty()) { + if (isBorderDrawable) { + // org.nativescript.widgets.BorderDrawable + refreshBorderDrawable(this, backgroundDrawable); + } else { + backgroundDrawable = new org.nativescript.widgets.BorderDrawable(layout.getDisplayDensity(), this.toString()); + refreshBorderDrawable(this, backgroundDrawable); + nativeView.setBackground(backgroundDrawable); + } + } else { + //empty background let's reset + const cachedDrawable = (nativeView)._cachedDrawable; + nativeView.setBackground(cachedDrawable); + } + } + + protected _drawBoxShadow(boxShadow: CSSShadow) { + const nativeView = this.nativeViewProtected; + const config = { + shadowColor: boxShadow.color.android, + cornerRadius: Length.toDevicePixels(this.borderRadius as CoreTypes.LengthType, 0.0), + spreadRadius: Length.toDevicePixels(boxShadow.spreadRadius, 0.0), + blurRadius: Length.toDevicePixels(boxShadow.blurRadius, 0.0), + offsetX: Length.toDevicePixels(boxShadow.offsetX, 0.0), + offsetY: Length.toDevicePixels(boxShadow.offsetY, 0.0), + }; + org.nativescript.widgets.Utils.drawBoxShadow(nativeView, JSON.stringify(config)); + } + _redrawNativeBackground(value: android.graphics.drawable.Drawable | Background): void { if (value instanceof Background) { - androidBackground.onBackgroundOrBorderPropertyChanged(this); + this.onBackgroundOrBorderPropertyChanged(); } else { const nativeView = this.nativeViewProtected; nativeView.setBackground(value); @@ -1119,11 +1164,61 @@ export class View extends ViewCommon { } else { nativeView.setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom); } - - (nativeView).background = undefined; } } + protected onBackgroundOrBorderPropertyChanged() { + const nativeView = this.nativeViewProtected; + if (!nativeView) { + return; + } + + const background = this.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); + } + + const drawable = nativeView.getBackground(); + const androidView = (this) 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; + + 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 = BackgroundClearFlags.NONE; + } + public accessibilityAnnouncement(message = this.accessibilityLabel): void { this.sendAccessibilityEvent({ androidAccessibilityEvent: AndroidAccessibilityEvent.ANNOUNCEMENT,