From 5b4624a30865ab2b5ee7cb8ff7e6bf5149f4374d Mon Sep 17 00:00:00 2001 From: Igor Randjelovic Date: Thu, 11 May 2023 20:51:45 +0200 Subject: [PATCH] feat: add deprecated shims to keep b/w compatibility --- .../application/application-tests.android.ts | 2 +- apps/automated/src/main.ts | 135 +---- .../core/application/application-shims.ts | 393 +++++++++++++ .../core/application/application.android.ts | 532 +++++++++++++++++ packages/core/application/application.d.ts | 57 ++ packages/core/application/application.ios.ts | 554 +++++++++++++++++ packages/core/application/index.android.ts | 529 +---------------- packages/core/application/index.d.ts | 59 +- packages/core/application/index.ios.ts | 556 +----------------- 9 files changed, 1544 insertions(+), 1273 deletions(-) create mode 100644 packages/core/application/application-shims.ts create mode 100644 packages/core/application/application.android.ts create mode 100644 packages/core/application/application.d.ts create mode 100644 packages/core/application/application.ios.ts diff --git a/apps/automated/src/application/application-tests.android.ts b/apps/automated/src/application/application-tests.android.ts index 7e5d803a2..0f4c62f34 100644 --- a/apps/automated/src/application/application-tests.android.ts +++ b/apps/automated/src/application/application-tests.android.ts @@ -51,8 +51,8 @@ export function testAndroidApplicationInitialized() { Application.android.foregroundActivity, 'Android foregroundActivity not initialized.' ); - // @ts-expect-error TKUnit.assert( + // @ts-expect-error Application.android.foregroundActivity.isNativeScriptActivity, 'Android foregroundActivity.isNativeScriptActivity is false.' ); diff --git a/apps/automated/src/main.ts b/apps/automated/src/main.ts index b19757686..b4cba0987 100644 --- a/apps/automated/src/main.ts +++ b/apps/automated/src/main.ts @@ -5,136 +5,6 @@ if (typeof NSDate !== 'undefined') { start = java.lang.System.currentTimeMillis(); } -// Utils.android.dismissSoftInput(); -// Utils.ad.dismissSoftInput(); - -// Utils.ios.animateWithSpring(); - -// Utils.android.isRealDevice() - -// isAndroid -// const { -// GC, -// SDK_VERSION, -// RESOURCE_PREFIX, -// FILE_PREFIX, -// isFontIconURI, -// isDataURI, -// isFileOrResourcePath, -// getFileExtension, -// executeOnMainThread, -// executeOnUIThread, -// mainThreadify, -// isMainThread, -// dispatchToMainThread, -// queueMacrotask, -// queueGC, -// debounce, -// throttle, -// releaseNativeObject, -// convertString, -// escapeRegexSymbols, - -// getModuleName, -// openFile, -// openUrl, -// isRealDevice, - -// layout, -// android, -// // legacy (a lot of plugins use the shorthand "ad" Utils.ad instead of Utils.android) -// ad, -// ios, -// dataSerialize, -// dataDeserialize, -// numberHasDecimals, -// numberIs64Bit, -// setTimeout, -// setInterval, -// clearInterval, -// clearTimeout, -// Source, -// ClassInfo, -// getClass, -// getBaseClasses, -// getClassInfo, -// isBoolean, -// isDefined, -// isFunction, -// isNullOrUndefined, -// isNumber, -// isObject, -// isString, -// isUndefined, -// toUIString, -// verifyCallback, -// dismissSoftInput, -// dismissKeyboard, -// copyToClipboard, -// isEmoji, -// getDurationWithDampingFromSpring, - -// // wtf -// } = Utils; - -// const { -// launchEvent, -// displayedEvent, -// uncaughtErrorEvent, -// discardedErrorEvent, -// suspendEvent, -// resumeEvent, -// exitEvent, -// foregroundEvent, -// backgroundEvent, -// lowMemoryEvent, -// orientationChangedEvent, -// systemAppearanceChangedEvent, -// systemAppearanceChanged, -// fontScaleChangedEvent, -// setMaxRefreshRate, - -// getMainEntry, -// getRootView, -// resetRootView, -// getResources, -// setResources, -// setCssFileName, -// getCssFileName, -// loadAppCss, -// addCss, -// on, -// off, -// notify, -// hasListeners, -// run, -// orientation, -// getNativeApplication, -// hasLaunched, -// systemAppearance, -// setAutoSystemAppearanceChanged, -// android: appAndroid, -// inBackground, -// suspended, -// ios: iosApp, -// // get android() { -// // ensureNativeApplication(); -// // return appAndroid; -// // }, -// // get ios() { -// // ensureNativeApplication(); -// // return iosApp; -// // }, -// // get suspended() { -// // return suspended; -// // }, -// // get inBackground() { -// // return inBackground; -// // }, -// } = Application; - -// Application.suspendEvent - import { Application, ApplicationEventData, @@ -148,10 +18,7 @@ import { AndroidActivityBackPressedEventData, } from '@nativescript/core'; -// import { Application } from "@nativescript/core/application-new"; - -// Application.AndroidApplication -// Application2.AndroidApplication +// import * as Application from "@nativescript/core/application"; if (Application.ios) { // Observe application notifications. diff --git a/packages/core/application/application-shims.ts b/packages/core/application/application-shims.ts new file mode 100644 index 000000000..e324a0d97 --- /dev/null +++ b/packages/core/application/application-shims.ts @@ -0,0 +1,393 @@ +import { Application } from './application'; + +/** + * @deprecated Deep imports into the Application module are depreacted and will be removed in a future release. + * Use the `Application` class imported from "@nativescript/core" instead: + * ```ts + * import { Application } from "@nativescript/core"; + * Application.addCss() + * ``` + */ +export const addCss = Application.addCss.bind(Application); + +/** + * @deprecated Deep imports into the Application module are depreacted and will be removed in a future release. + * Use the `Application` class imported from "@nativescript/core" instead: + * ```ts + * import { Application } from "@nativescript/core"; + * Application.getCssFileName() + * ``` + */ +export const getCssFileName = Application.getCssFileName.bind(Application); + +/** + * @deprecated Deep imports into the Application module are depreacted and will be removed in a future release. + * Use the `Application` class imported from "@nativescript/core" instead: + * ```ts + * import { Application } from "@nativescript/core"; + * Application.getMainEntry() + * ``` + */ +export const getMainEntry = Application.getMainEntry.bind(Application); + +/** + * @deprecated Deep imports into the Application module are depreacted and will be removed in a future release. + * Use the `Application` class imported from "@nativescript/core" instead: + * ```ts + * import { Application } from "@nativescript/core"; + * Application.getNativeApplication() + * ``` + */ +export const getNativeApplication = Application.getNativeApplication.bind(Application); + +/** + * @deprecated Deep imports into the Application module are depreacted and will be removed in a future release. + * Use the `Application` class imported from "@nativescript/core" instead: + * ```ts + * import { Application } from "@nativescript/core"; + * Application.getResources() + * ``` + */ +export const getResources = Application.getResources.bind(Application); + +/** + * @deprecated Deep imports into the Application module are depreacted and will be removed in a future release. + * Use the `Application` class imported from "@nativescript/core" instead: + * ```ts + * import { Application } from "@nativescript/core"; + * Application.getRootView() + * ``` + */ +export const getRootView = Application.getRootView.bind(Application); + +/** + * @deprecated Deep imports into the Application module are depreacted and will be removed in a future release. + * Use the `Application` class imported from "@nativescript/core" instead: + * ```ts + * import { Application } from "@nativescript/core"; + * Application.hasLaunched() + * ``` + */ +export const hasLaunched = Application.hasLaunched.bind(Application); + +/** + * @deprecated Deep imports into the Application module are depreacted and will be removed in a future release. + * Use the `Application` class imported from "@nativescript/core" instead: + * ```ts + * import { Application } from "@nativescript/core"; + * Application.hasListeners() + * ``` + */ +export const hasListeners = Application.hasListeners.bind(Application); + +/** + * @deprecated Deep imports into the Application module are depreacted and will be removed in a future release. + * Use the `Application` class imported from "@nativescript/core" instead: + * ```ts + * import { Application } from "@nativescript/core"; + * Application.loadAppCss() + * ``` + */ +export const loadAppCss = Application.loadAppCss.bind(Application); + +/** + * @deprecated Deep imports into the Application module are depreacted and will be removed in a future release. + * Use the `Application` class imported from "@nativescript/core" instead: + * ```ts + * import { Application } from "@nativescript/core"; + * Application.notify() + * ``` + */ +export const notify = Application.notify.bind(Application); + +/** + * @deprecated Deep imports into the Application module are depreacted and will be removed in a future release. + * Use the `Application` class imported from "@nativescript/core" instead: + * ```ts + * import { Application } from "@nativescript/core"; + * Application.off() + * ``` + */ +export const off = Application.off.bind(Application); + +/** + * @deprecated Deep imports into the Application module are depreacted and will be removed in a future release. + * Use the `Application` class imported from "@nativescript/core" instead: + * ```ts + * import { Application } from "@nativescript/core"; + * Application.on() + * ``` + */ +export const on = Application.on.bind(Application); + +/** + * @deprecated Deep imports into the Application module are depreacted and will be removed in a future release. + * Use the `Application` class imported from "@nativescript/core" instead: + * ```ts + * import { Application } from "@nativescript/core"; + * Application.orientation + * ``` + */ +export const orientation = () => Application.orientation; + +/** + * @deprecated Deep imports into the Application module are depreacted and will be removed in a future release. + * Use the `Application` class imported from "@nativescript/core" instead: + * ```ts + * import { Application } from "@nativescript/core"; + * Application.resetRootView() + * ``` + */ +export const resetRootView = Application.resetRootView.bind(Application); + +/** + * @deprecated Deep imports into the Application module are depreacted and will be removed in a future release. + * Use the `Application` class imported from "@nativescript/core" instead: + * ```ts + * import { Application } from "@nativescript/core"; + * Application.resetRootView() + * ``` + */ +export const _resetRootView = Application.resetRootView.bind(Application); + +/** + * @deprecated Deep imports into the Application module are depreacted and will be removed in a future release. + * Use the `Application` class imported from "@nativescript/core" instead: + * ```ts + * import { Application } from "@nativescript/core"; + * Application.run() + * ``` + */ +export const run = Application.run.bind(Application); + +/** + * @deprecated Deep imports into the Application module are depreacted and will be removed in a future release. + * Use the `Application` class imported from "@nativescript/core" instead: + * ```ts + * import { Application } from "@nativescript/core"; + * Application.setAutoSystemAppearanceChanged() + * ``` + */ +export const setAutoSystemAppearanceChanged = + Application.setAutoSystemAppearanceChanged.bind(Application); + +/** + * @deprecated Deep imports into the Application module are depreacted and will be removed in a future release. + * Use the `Application` class imported from "@nativescript/core" instead: + * ```ts + * import { Application } from "@nativescript/core"; + * Application.setCssFileName() + * ``` + */ +export const setCssFileName = Application.setCssFileName.bind(Application); + +/** + * @deprecated Deep imports into the Application module are depreacted and will be removed in a future release. + * Use the `Application` class imported from "@nativescript/core" instead: + * ```ts + * import { Application } from "@nativescript/core"; + * Application.setMaxRefreshRate() + * ``` + */ +export const setMaxRefreshRate = Application.setMaxRefreshRate.bind(Application); + +/** + * @deprecated Deep imports into the Application module are depreacted and will be removed in a future release. + * Use the `Application` class imported from "@nativescript/core" instead: + * ```ts + * import { Application } from "@nativescript/core"; + * Application.setResources() + * ``` + */ +export const setResources = Application.setResources.bind(Application); + +/** + * @deprecated Deep imports into the Application module are depreacted and will be removed in a future release. + * Use the `Application` class imported from "@nativescript/core" instead: + * ```ts + * import { Application } from "@nativescript/core"; + * Application.systemAppearance + * ``` + */ +export const systemAppearance = () => Application.systemAppearance; + +/** + * @deprecated Deep imports into the Application module are depreacted and will be removed in a future release. + * Use the `Application` class imported from "@nativescript/core" instead: + * ```ts + * import { Application } from "@nativescript/core"; + * Application.systemAppearanceChanged() + * ``` + */ +export const systemAppearanceChanged = + Application.systemAppearanceChanged.bind(Application); + +/** + * @deprecated Deep imports into the Application module are depreacted and will be removed in a future release. + * Use the `Application` class imported from "@nativescript/core" instead: + * ```ts + * import { Application } from "@nativescript/core"; + * Application.discardedErrorEvent + * ``` + */ +export const discardedErrorEvent = Application.discardedErrorEvent; + +/** + * @deprecated Deep imports into the Application module are depreacted and will be removed in a future release. + * Use the `Application` class imported from "@nativescript/core" instead: + * ```ts + * import { Application } from "@nativescript/core"; + * Application.uncaughtErrorEvent + * ``` + */ +export const uncaughtErrorEvent = Application.uncaughtErrorEvent; + +/** + * @deprecated Deep imports into the Application module are depreacted and will be removed in a future release. + * Use the `Application` class imported from "@nativescript/core" instead: + * ```ts + * import { Application } from "@nativescript/core"; + * Application.displayedEvent + * ``` + */ +export const displayedEvent = Application.displayedEvent; + +/** + * @deprecated Deep imports into the Application module are depreacted and will be removed in a future release. + * Use the `Application` class imported from "@nativescript/core" instead: + * ```ts + * import { Application } from "@nativescript/core"; + * Application.exitEvent + * ``` + */ +export const exitEvent = Application.exitEvent; + +/** + * @deprecated Deep imports into the Application module are depreacted and will be removed in a future release. + * Use the `Application` class imported from "@nativescript/core" instead: + * ```ts + * import { Application } from "@nativescript/core"; + * Application.fontScaleChangedEvent + * ``` + */ +export const fontScaleChangedEvent = Application.fontScaleChangedEvent; + +/** + * @deprecated Deep imports into the Application module are depreacted and will be removed in a future release. + * Use the `Application` class imported from "@nativescript/core" instead: + * ```ts + * import { Application } from "@nativescript/core"; + * Application.launchEvent + * ``` + */ +export const launchEvent = Application.launchEvent; + +/** + * @deprecated Deep imports into the Application module are depreacted and will be removed in a future release. + * Use the `Application` class imported from "@nativescript/core" instead: + * ```ts + * import { Application } from "@nativescript/core"; + * Application.lowMemoryEvent + * ``` + */ +export const lowMemoryEvent = Application.lowMemoryEvent; + +/** + * @deprecated Deep imports into the Application module are depreacted and will be removed in a future release. + * Use the `Application` class imported from "@nativescript/core" instead: + * ```ts + * import { Application } from "@nativescript/core"; + * Application.resumeEvent + * ``` + */ +export const resumeEvent = Application.resumeEvent; + +/** + * @deprecated Deep imports into the Application module are depreacted and will be removed in a future release. + * Use the `Application` class imported from "@nativescript/core" instead: + * ```ts + * import { Application } from "@nativescript/core"; + * Application.orientationChangedEvent + * ``` + */ +export const orientationChangedEvent = Application.orientationChangedEvent; + +/** + * @deprecated Deep imports into the Application module are depreacted and will be removed in a future release. + * Use the `Application` class imported from "@nativescript/core" instead: + * ```ts + * import { Application } from "@nativescript/core"; + * Application.suspendEvent + * ``` + */ +export const suspendEvent = Application.suspendEvent; + +/** + * @deprecated Deep imports into the Application module are depreacted and will be removed in a future release. + * Use the `Application` class imported from "@nativescript/core" instead: + * ```ts + * import { Application } from "@nativescript/core"; + * Application.systemAppearanceChangedEvent + * ``` + */ +export const systemAppearanceChangedEvent = Application.systemAppearanceChangedEvent; + +/** + * @deprecated Deep imports into the Application module are depreacted and will be removed in a future release. + * Use the `Application` class imported from "@nativescript/core" instead: + * ```ts + * import { Application } from "@nativescript/core"; + * Application.backgroundEvent + * ``` + */ +export const backgroundEvent = Application.backgroundEvent; + +/** + * @deprecated Deep imports into the Application module are depreacted and will be removed in a future release. + * Use the `Application` class imported from "@nativescript/core" instead: + * ```ts + * import { Application } from "@nativescript/core"; + * Application.foregroundEvent + * ``` + */ +export const foregroundEvent = Application.foregroundEvent; + +/** + * @deprecated Deep imports into the Application module are depreacted and will be removed in a future release. + * Use the `Application` class imported from "@nativescript/core" instead: + * ```ts + * import { Application } from "@nativescript/core"; + * Application.inBackground + * ``` + */ +export const inBackground = Application.inBackground; + +/** + * @deprecated Deep imports into the Application module are depreacted and will be removed in a future release. + * Use the `Application` class imported from "@nativescript/core" instead: + * ```ts + * import { Application } from "@nativescript/core"; + * Application.suspended + * ``` + */ +export const suspended = Application.suspended; + +/** + * @deprecated Deep imports into the Application module are depreacted and will be removed in a future release. + * Use the `Application` class imported from "@nativescript/core" instead: + * ```ts + * import { Application } from "@nativescript/core"; + * Application.android + * ``` + */ +export const android = Application.android; + +/** + * @deprecated Deep imports into the Application module are depreacted and will be removed in a future release. + * Use the `Application` class imported from "@nativescript/core" instead: + * ```ts + * import { Application } from "@nativescript/core"; + * Application.ios + * ``` + */ +export const ios = Application.ios; diff --git a/packages/core/application/application.android.ts b/packages/core/application/application.android.ts new file mode 100644 index 000000000..299544e86 --- /dev/null +++ b/packages/core/application/application.android.ts @@ -0,0 +1,532 @@ +import { initAccessibilityCssHelper } from '../accessibility/accessibility-css-helper'; +import { initAccessibilityFontScale } from '../accessibility/font-scale'; +import { profile } from '../profiling'; +import { View } from '../ui'; +import { AndroidActivityCallbacks, NavigationEntry } from '../ui/frame/frame-common'; +import type { + AndroidActivityBundleEventData, + AndroidActivityEventData, + ApplicationEventData, + AndroidApplication as IAndroidApplication, +} from '.'; +import { ApplicationCommon } from './application-common'; + +declare namespace com { + namespace tns { + class NativeScriptApplication extends android.app.Application { + static getInstance(): NativeScriptApplication; + } + } +} + +@NativeClass +class BroadcastReceiver extends android.content.BroadcastReceiver { + private _onReceiveCallback: ( + context: android.content.Context, + intent: android.content.Intent + ) => void; + + constructor( + onReceiveCallback: ( + context: android.content.Context, + intent: android.content.Intent + ) => void + ) { + super(); + this._onReceiveCallback = onReceiveCallback; + + return global.__native(this); + } + + public onReceive(context: android.content.Context, intent: android.content.Intent) { + if (this._onReceiveCallback) { + this._onReceiveCallback(context, intent); + } + } +} + +@NativeClass +@JavaProxy('org.nativescript.NativeScriptLifecycleCallbacks') +class NativeScriptLifecycleCallbacks extends android.app.Application + .ActivityLifecycleCallbacks { + private activitiesCount: number = 0; + private nativescriptActivity: androidx.appcompat.app.AppCompatActivity; + + @profile + public onActivityCreated( + activity: androidx.appcompat.app.AppCompatActivity, + savedInstanceState: android.os.Bundle + ): void { + // console.log('NativeScriptLifecycleCallbacks onActivityCreated'); + this.setThemeOnLaunch(activity); + + if (!Application.android.startActivity) { + Application.android.setStartActivity(activity); + } + + if (!this.nativescriptActivity && 'isNativeScriptActivity' in activity) { + this.nativescriptActivity = activity; + } + + this.notifyActivityCreated(activity, savedInstanceState); + + if (Application.hasListeners(Application.displayedEvent)) { + this.subscribeForGlobalLayout(activity); + } + } + + @profile + public onActivityDestroyed(activity: androidx.appcompat.app.AppCompatActivity): void { + // console.log('NativeScriptLifecycleCallbacks onActivityDestroyed'); + if (activity === Application.android.foregroundActivity) { + Application.android.setForegroundActivity(undefined); + } + + if (activity === this.nativescriptActivity) { + this.nativescriptActivity = undefined; + } + + if (activity === Application.android.startActivity) { + Application.android.setStartActivity(undefined); + + // Fallback for start activity when it is destroyed but we have a known nativescript activity + if (this.nativescriptActivity) { + Application.android.setStartActivity(this.nativescriptActivity); + } + } + + Application.android.notify({ + eventName: Application.android.activityDestroyedEvent, + object: Application.android, + activity, + } as AndroidActivityEventData); + + // TODO: This is a temporary workaround to force the V8's Garbage Collector, which will force the related Java Object to be collected. + // gc(); + } + + @profile + public onActivityPaused(activity: androidx.appcompat.app.AppCompatActivity): void { + // console.log('NativeScriptLifecycleCallbacks onActivityPaused'); + if ('isNativeScriptActivity' in activity) { + Application.android.notify({ + eventName: Application.suspendEvent, + object: Application.android, + android: activity, + } as ApplicationEventData); + } + + Application.android.notify({ + eventName: Application.android.activityPausedEvent, + object: Application.android, + activity, + } as AndroidActivityEventData); + } + + @profile + public onActivityResumed(activity: androidx.appcompat.app.AppCompatActivity): void { + // console.log('NativeScriptLifecycleCallbacks onActivityResumed'); + Application.android.setForegroundActivity(activity); + + Application.android.notify({ + eventName: Application.android.activityResumedEvent, + object: Application.android, + activity, + } as AndroidActivityEventData); + } + + @profile + public onActivitySaveInstanceState( + activity: androidx.appcompat.app.AppCompatActivity, + bundle: android.os.Bundle + ): void { + // console.log('NativeScriptLifecycleCallbacks onActivitySaveInstanceState'); + + Application.android.notify({ + eventName: Application.android.saveActivityStateEvent, + object: Application.android, + activity, + bundle, + } as AndroidActivityBundleEventData); + } + + @profile + public onActivityStarted(activity: androidx.appcompat.app.AppCompatActivity): void { + // console.log('NativeScriptLifecycleCallbacks onActivityStarted'); + + this.activitiesCount++; + if (this.activitiesCount === 1) { + Application.android.notify({ + eventName: Application.foregroundEvent, + object: Application.android, + android: activity, + } as ApplicationEventData); + } + + Application.android.notify({ + eventName: Application.android.activityStartedEvent, + object: Application.android, + activity, + } as AndroidActivityEventData); + } + + @profile + public onActivityStopped(activity: androidx.appcompat.app.AppCompatActivity): void { + // console.log('NativeScriptLifecycleCallbacks onActivityStopped'); + this.activitiesCount--; + if (this.activitiesCount === 0) { + Application.android.notify({ + eventName: Application.backgroundEvent, + object: Application.android, + android: activity, + } as ApplicationEventData); + } + + Application.android.notify({ + eventName: Application.android.activityStoppedEvent, + object: Application.android, + activity, + } as AndroidActivityEventData); + } + + @profile + setThemeOnLaunch(activity: androidx.appcompat.app.AppCompatActivity) { + // Set app theme after launch screen was used during startup + const activityInfo = activity + .getPackageManager() + .getActivityInfo( + activity.getComponentName(), + android.content.pm.PackageManager.GET_META_DATA + ); + if (activityInfo.metaData) { + const setThemeOnLaunch = activityInfo.metaData.getInt('SET_THEME_ON_LAUNCH', -1); + if (setThemeOnLaunch !== -1) { + activity.setTheme(setThemeOnLaunch); + } + } + } + + @profile + notifyActivityCreated( + activity: androidx.appcompat.app.AppCompatActivity, + bundle: android.os.Bundle + ) { + Application.android.notify({ + eventName: Application.android.activityCreatedEvent, + object: Application.android, + activity, + bundle, + } as AndroidActivityBundleEventData); + } + + @profile + subscribeForGlobalLayout(activity: androidx.appcompat.app.AppCompatActivity) { + const rootView = activity.getWindow().getDecorView().getRootView(); + // store the listener not to trigger GC collection before collecting the method + global.onGlobalLayoutListener = + new android.view.ViewTreeObserver.OnGlobalLayoutListener({ + onGlobalLayout() { + Application.android.notify({ + eventName: Application.android.displayedEvent, + object: Application, + android: Application.android, + activity, + }); + const viewTreeObserver = rootView.getViewTreeObserver(); + viewTreeObserver.removeOnGlobalLayoutListener(global.onGlobalLayoutListener); + }, + }); + rootView + .getViewTreeObserver() + .addOnGlobalLayoutListener(global.onGlobalLayoutListener); + } +} + +@NativeClass +@JavaProxy('org.nativescript.NativeScriptComponentCallbacks') +class NativeScriptComponentCallbacks extends android.content.ComponentCallbacks2 { + @profile + public onLowMemory(): void { + gc(); + java.lang.System.gc(); + + Application.notify({ + eventName: Application.lowMemoryEvent, + object: Application, + android: this, + } as ApplicationEventData); + } + + @profile + public onTrimMemory(level: number): void { + // TODO: This is skipped for now, test carefully for OutOfMemory exceptions + } + + @profile + public onConfigurationChanged( + newConfiguration: android.content.res.Configuration + ): void { + Application.android.onConfigurationChanged(newConfiguration); + } +} + +export class AndroidApplication extends ApplicationCommon implements IAndroidApplication { + readonly activityCreatedEvent = 'activityCreated'; + readonly activityDestroyedEvent = 'activityDestroyed'; + readonly activityStartedEvent = 'activityStarted'; + readonly activityPausedEvent = 'activityPaused'; + readonly activityResumedEvent = 'activityResumed'; + readonly activityStoppedEvent = 'activityStopped'; + readonly saveActivityStateEvent = 'saveActivityState'; + readonly activityResultEvent = 'activityResult'; + readonly activityBackPressedEvent = 'activityBackPressed'; + readonly activityNewIntentEvent = 'activityNewIntent'; + readonly activityRequestPermissionsEvent = 'activityRequestPermissions'; + + private _nativeApp: android.app.Application; + // we are using these property to store the callbacks to avoid early GC collection which would trigger MarkReachableObjects + private lifecycleCallbacks: NativeScriptLifecycleCallbacks; + private componentCallbacks: NativeScriptComponentCallbacks; + + constructor() { + super(); + } + + init(nativeApp: android.app.Application): void { + if (this.nativeApp === nativeApp) { + return; + } + + if (this.nativeApp) { + throw new Error('Application.android already initialized.'); + } + + this._nativeApp = nativeApp; + + // we store those callbacks and add a function for clearing them later so that the objects will be eligable for GC + this.lifecycleCallbacks = new NativeScriptLifecycleCallbacks(); + this.nativeApp.registerActivityLifecycleCallbacks(this.lifecycleCallbacks); + + this.componentCallbacks = new NativeScriptComponentCallbacks(); + this.nativeApp.registerComponentCallbacks(this.componentCallbacks); + + this._registerPendingReceivers(); + } + + private _registeredReceivers = {}; + private _pendingReceiverRegistrations = new Array< + (context: android.content.Context) => void + >(); + private _registerPendingReceivers() { + this._pendingReceiverRegistrations.forEach((func) => func(this.context)); + this._pendingReceiverRegistrations.length = 0; + } + + onConfigurationChanged(configuration: android.content.res.Configuration): void { + this.setOrientation(this.getOrientationValue(configuration)); + this.setSystemAppearance(this.getSystemAppearanceValue(configuration)); + } + + getNativeApplication() { + let nativeApp = this.nativeApp; + + if (nativeApp) { + return nativeApp; + } + + // Try getting it from module - check whether application.android.init has been explicitly called + // check whether the com.tns.NativeScriptApplication type exists + if (com.tns.NativeScriptApplication) { + nativeApp = com.tns.NativeScriptApplication.getInstance(); + } + + // the getInstance might return null if com.tns.NativeScriptApplication exists but is not the starting app type + if (!nativeApp) { + // TODO: Should we handle the case when a custom application type is provided and the user has not explicitly initialized the application module? + const clazz = java.lang.Class.forName( + 'androidx.appcompat.app.AppCompatActivityThread' + ); + if (clazz) { + const method = clazz.getMethod('currentApplication', null); + if (method) { + nativeApp = method.invoke(null, null); + } + } + } + + // we cannot work without having the app instance + if (!nativeApp) { + throw new Error( + "Failed to retrieve native Android Application object. If you have a custom android.app.Application type implemented make sure that you've called the 'Application.android.init' method." + ); + } + + return nativeApp; + } + + get nativeApp(): android.app.Application { + return this._nativeApp; + } + + run(entry?: string | NavigationEntry): void { + if (this.started) { + throw new Error('Application is already started.'); + } + + this.started = true; + this.mainEntry = typeof entry === 'string' ? { moduleName: entry } : entry; + + if (!this.nativeApp) { + const nativeApp = this.getNativeApplication(); + this.init(nativeApp); + } + + initAccessibilityCssHelper(); + initAccessibilityFontScale(); + } + + /** + * todo: check if true, cause there's no such thing in Utils.android + * @deprecated Use Utils.android.getPackageName() instead. + */ + get packageName() { + return this.nativeApp.getPackageName(); + } + + private _startActivity: androidx.appcompat.app.AppCompatActivity; + private _foregroundActivity: androidx.appcompat.app.AppCompatActivity; + + get startActivity() { + return this._startActivity; + } + + get foregroundActivity() { + return this._foregroundActivity; + } + + setStartActivity(value: androidx.appcompat.app.AppCompatActivity) { + this._startActivity = value; + } + + setForegroundActivity(value: androidx.appcompat.app.AppCompatActivity) { + this._foregroundActivity = value; + } + + get context() { + return this.nativeApp.getApplicationContext(); + } + + public registerBroadcastReceiver( + intentFilter: string, + onReceiveCallback: ( + context: android.content.Context, + intent: android.content.Intent + ) => void + ): void { + const registerFunc = (context: android.content.Context) => { + const receiver: android.content.BroadcastReceiver = new BroadcastReceiver( + onReceiveCallback + ); + context.registerReceiver(receiver, new android.content.IntentFilter(intentFilter)); + this._registeredReceivers[intentFilter] = receiver; + }; + + if (this.context) { + registerFunc(this.context); + } else { + this._pendingReceiverRegistrations.push(registerFunc); + } + } + + public unregisterBroadcastReceiver(intentFilter: string): void { + const receiver = this._registeredReceivers[intentFilter]; + if (receiver) { + this.context.unregisterReceiver(receiver); + this._registeredReceivers[intentFilter] = undefined; + delete this._registeredReceivers[intentFilter]; + } + } + + public getRegisteredBroadcastReceiver( + intentFilter: string + ): android.content.BroadcastReceiver | undefined { + return this._registeredReceivers[intentFilter]; + } + + getRootView(): View { + const activity = this.foregroundActivity || this.startActivity; + if (!activity) { + return undefined; + } + const callbacks: AndroidActivityCallbacks = activity['_callbacks']; + + return callbacks ? callbacks.getRootView() : undefined; + } + + resetRootView(entry?: NavigationEntry | string): void { + super.resetRootView(entry); + + const activity = this.foregroundActivity || this.startActivity; + if (!activity) { + throw new Error('Cannot find android activity.'); + } + + // this.mainEntry = typeof entry === 'string' ? { moduleName: entry } : entry; + const callbacks: AndroidActivityCallbacks = activity['_callbacks']; + if (!callbacks) { + throw new Error('Cannot find android activity callbacks.'); + } + callbacks.resetActivityContent(activity); + } + + getSystemAppearance(): 'light' | 'dark' { + const resources = this.context.getResources(); + const configuration = resources.getConfiguration(); + return this.getSystemAppearanceValue(configuration); + } + + // https://developer.android.com/guide/topics/ui/look-and-feel/darktheme#configuration_changes + private getSystemAppearanceValue( + configuration: android.content.res.Configuration + ): 'dark' | 'light' { + const systemAppearance = + configuration.uiMode & android.content.res.Configuration.UI_MODE_NIGHT_MASK; + + switch (systemAppearance) { + case android.content.res.Configuration.UI_MODE_NIGHT_YES: + return 'dark'; + case android.content.res.Configuration.UI_MODE_NIGHT_NO: + case android.content.res.Configuration.UI_MODE_NIGHT_UNDEFINED: + return 'light'; + } + } + + getOrientation() { + const resources = this.context.getResources(); + const configuration = resources.getConfiguration(); + return this.getOrientationValue(configuration); + } + + private getOrientationValue( + configuration: android.content.res.Configuration + ): 'portrait' | 'landscape' | 'unknown' { + const orientation = configuration.orientation; + + switch (orientation) { + case android.content.res.Configuration.ORIENTATION_LANDSCAPE: + return 'landscape'; + case android.content.res.Configuration.ORIENTATION_PORTRAIT: + return 'portrait'; + default: + return 'unknown'; + } + } + + get android() { + // ensures Application.android is defined when running on iOS + return this; + } +} +export * from './application-common'; +export const Application = new AndroidApplication(); +export const iOSApplication = undefined; diff --git a/packages/core/application/application.d.ts b/packages/core/application/application.d.ts new file mode 100644 index 000000000..2728d1f44 --- /dev/null +++ b/packages/core/application/application.d.ts @@ -0,0 +1,57 @@ +import { ApplicationCommon } from './application-common'; + +export * from './application-common'; +export * from './application-interfaces'; + +export const Application: ApplicationCommon; + +export interface AndroidApplication extends ApplicationCommon { + readonly activityCreatedEvent; + readonly activityDestroyedEvent; + readonly activityStartedEvent; + readonly activityPausedEvent; + readonly activityResumedEvent; + readonly activityStoppedEvent; + readonly saveActivityStateEvent; + readonly activityResultEvent; + readonly activityBackPressedEvent; + readonly activityNewIntentEvent; + readonly activityRequestPermissionsEvent; + + getNativeApplication(): android.app.Application; + + init(nativeApp: android.app.Application): void; + + get nativeApp(): android.app.Application; + get packageName(): string; + get startActivity(): androidx.appcompat.app.AppCompatActivity; + get foregroundActivity(): androidx.appcompat.app.AppCompatActivity; + get context(): android.content.Context; + + registerBroadcastReceiver( + intentFilter: string, + onReceiveCallback: ( + context: android.content.Context, + intent: android.content.Intent + ) => void + ): void; + unregisterBroadcastReceiver(intentFilter: string): void; +} + +export interface iOSApplication extends ApplicationCommon { + get rootController(): UIViewController; + get nativeApp(): UIApplication; + get window(): UIWindow; + + get delegate(): UIApplicationDelegate; + set delegate(value: UIApplicationDelegate | unknown); + + getNativeApplication(): UIApplication; + + addNotificationObserver( + notificationName: string, + onReceiveCallback: (notification: NSNotification) => void + ): NotificationObserver; + + removeNotificationObserver(observer: any, notificationName: string); +} diff --git a/packages/core/application/application.ios.ts b/packages/core/application/application.ios.ts new file mode 100644 index 000000000..ab7e52b25 --- /dev/null +++ b/packages/core/application/application.ios.ts @@ -0,0 +1,554 @@ +import { initAccessibilityCssHelper } from '../accessibility/accessibility-css-helper'; +import { initAccessibilityFontScale } from '../accessibility/font-scale'; +import { profile } from '../profiling'; +import { View } from '../ui'; +import { Builder } from '../ui/builder'; +import { IOSHelper } from '../ui/core/view/view-helper'; +import { NavigationEntry } from '../ui/frame/frame-interfaces'; +import * as Utils from '../utils'; +import type { iOSApplication as IiOSApplication } from './'; +import { ApplicationCommon } from './application-common'; +import { + ApplicationEventData, + LaunchEventData, + LoadAppCSSEventData, +} from './application-interfaces'; + +@NativeClass +class CADisplayLinkTarget extends NSObject { + private _owner: WeakRef; + static initWithOwner(owner: WeakRef): CADisplayLinkTarget { + const target = CADisplayLinkTarget.new(); + target._owner = owner; + return target; + } + + onDisplayed(link: CADisplayLink) { + link.invalidate(); + const owner = this._owner.deref(); + + if (!owner) { + return; + } + + owner.displayedOnce = true; + owner.notify({ + eventName: owner.displayedEvent, + object: owner, + ios: owner.ios, + }); + owner.displayedLinkTarget = null; + owner.displayedLink = null; + } + + public static ObjCExposedMethods = { + onDisplayed: { returns: interop.types.void, params: [CADisplayLink] }, + }; +} + +@NativeClass +class NotificationObserver extends NSObject { + private _onReceiveCallback: (notification: NSNotification) => void; + + public static initWithCallback( + onReceiveCallback: (notification: NSNotification) => void + ): NotificationObserver { + const observer = super.new(); + observer._onReceiveCallback = onReceiveCallback; + + return observer; + } + + public onReceive(notification: NSNotification): void { + this._onReceiveCallback(notification); + } + + public static ObjCExposedMethods = { + onReceive: { returns: interop.types.void, params: [NSNotification] }, + }; +} + +@NativeClass +class Responder extends UIResponder implements UIApplicationDelegate { + get window(): UIWindow { + return Application.ios.window; + } + + set window(value: UIWindow) { + // NOOP + } + + static ObjCProtocols = [UIApplicationDelegate]; +} + +export class iOSApplication extends ApplicationCommon implements IiOSApplication { + private _delegate: UIApplicationDelegate; + private _window: UIWindow; + private _notificationObservers: NotificationObserver[] = []; + private _rootView: View; + + displayedOnce = false; + displayedLinkTarget: CADisplayLinkTarget; + displayedLink: CADisplayLink; + + constructor() { + super(); + + this.addNotificationObserver( + UIApplicationDidFinishLaunchingNotification, + this.didFinishLaunchingWithOptions.bind(this) + ); + this.addNotificationObserver( + UIApplicationDidBecomeActiveNotification, + this.didBecomeActive.bind(this) + ); + this.addNotificationObserver( + UIApplicationDidEnterBackgroundNotification, + this.didEnterBackground.bind(this) + ); + this.addNotificationObserver( + UIApplicationWillTerminateNotification, + this.willTerminate.bind(this) + ); + this.addNotificationObserver( + UIApplicationDidReceiveMemoryWarningNotification, + this.didReceiveMemoryWarning.bind(this) + ); + this.addNotificationObserver( + UIApplicationDidChangeStatusBarOrientationNotification, + this.didChangeStatusBarOrientation.bind(this) + ); + } + + getRootView(): View { + return this._rootView; + } + + resetRootView(view?: View) { + super.resetRootView(view); + this.setWindowContent(); + } + + run(entry?: string | NavigationEntry): void { + console.log('run in iOSApplication', entry); + + this.mainEntry = typeof entry === 'string' ? { moduleName: entry } : entry; + this.started = true; + + if (this.nativeApp) { + this.runAsEmbeddedApp(); + } else { + this.runAsMainApp(); + } + + initAccessibilityCssHelper(); + initAccessibilityFontScale(); + } + + private runAsMainApp() { + console.log('runAsMainApp', this.delegate); + UIApplicationMain( + 0, + null, + null, + this.delegate + ? NSStringFromClass(this.delegate as any) + : NSStringFromClass(Responder) + ); + } + + private runAsEmbeddedApp() { + // TODO: this rootView should be held alive until rootController dismissViewController is called. + const rootView = this.createRootView(); + if (!rootView) { + return; + } + // Attach to the existing iOS app + const window = + this.nativeApp.keyWindow || + (this.nativeApp.windows.count > 0 && this.nativeApp.windows[0]); + + if (!window) { + return; + } + + const rootController = window.rootViewController; + if (!rootController) { + return; + } + + const controller = this.getViewController(rootView); + const embedderDelegate = NativeScriptEmbedder.sharedInstance().delegate; + + rootView._setupAsRootView({}); + rootView.on(IOSHelper.traitCollectionColorAppearanceChangedEvent, () => { + const userInterfaceStyle = controller.traitCollection.userInterfaceStyle; + const newSystemAppearance = this.getSystemAppearanceValue(userInterfaceStyle); + this.setSystemAppearance(newSystemAppearance); + }); + + if (embedderDelegate) { + this.setViewControllerView(rootView); + embedderDelegate.presentNativeScriptApp(controller); + } else { + const visibleVC = Utils.ios.getVisibleViewController(rootController); + visibleVC.presentViewControllerAnimatedCompletion(controller, true, null); + } + + // this.setRootViewsSystemAppearanceCssClass(rootView); + this.notifyAppStarted(); + } + + private createRootView(v?: View) { + let rootView = v; + if (!rootView) { + console.log('createRootView mainEntry', this.mainEntry); + // try to navigate to the mainEntry (if specified) + if (!this.mainEntry) { + throw new Error( + 'Main entry is missing. App cannot be started. Verify app bootstrap.' + ); + } else { + // console.log('createRootView mainEntry:', mainEntry); + rootView = Builder.createViewFromEntry(this.mainEntry); + } + } + // console.log('createRootView rootView:', rootView); + + // setRootViewsCssClasses(rootView); + + return rootView; + } + + private getViewController(rootView: View): UIViewController { + let viewController: UIViewController = rootView.viewController || rootView.ios; + + if (!(viewController instanceof UIViewController)) { + // We set UILayoutViewController dynamically to the root view if it doesn't have a view controller + // At the moment the root view doesn't have its native view created. We set it in the setViewControllerView func + viewController = IOSHelper.UILayoutViewController.initWithOwner( + new WeakRef(rootView) + ) as UIViewController; + rootView.viewController = viewController; + } + + return viewController; + } + + private setViewControllerView(view: View): void { + const viewController: UIViewController = view.viewController || view.ios; + const nativeView = view.ios || view.nativeViewProtected; + + if (!nativeView || !viewController) { + throw new Error('Root should be either UIViewController or UIView'); + } + + if (viewController instanceof IOSHelper.UILayoutViewController) { + viewController.view.addSubview(nativeView); + } + } + + setMaxRefreshRate(options?: { min?: number; max?: number; preferred?: number }): void { + const adjustRefreshRate = () => { + if (!this.displayedLink) { + return; + } + const minFrameRateDisabled = NSBundle.mainBundle.objectForInfoDictionaryKey( + 'CADisableMinimumFrameDurationOnPhone' + ); + + if (minFrameRateDisabled) { + let max = 120; + const deviceMaxFrames = UIScreen.mainScreen?.maximumFramesPerSecond; + if (options?.max) { + if (deviceMaxFrames) { + // iOS 10.3 + max = options.max <= deviceMaxFrames ? options.max : deviceMaxFrames; + } else if (this.displayedLink.preferredFramesPerSecond) { + // iOS 10.0 + max = + options.max <= this.displayedLink.preferredFramesPerSecond + ? options.max + : this.displayedLink.preferredFramesPerSecond; + } + } + + if (Utils.ios.MajorVersion >= 15) { + const min = options?.min || max / 2; + const preferred = options?.preferred || max; + this.displayedLink.preferredFrameRateRange = CAFrameRateRangeMake( + min, + max, + preferred + ); + } else { + this.displayedLink.preferredFramesPerSecond = max; + } + } + }; + + if (this.displayedOnce) { + adjustRefreshRate(); + return; + } + + this.displayedLinkTarget = CADisplayLinkTarget.initWithOwner(new WeakRef(this)); + this.displayedLink = CADisplayLink.displayLinkWithTargetSelector( + this.displayedLinkTarget, + 'onDisplayed' + ); + adjustRefreshRate(); + this.displayedLink.addToRunLoopForMode(NSRunLoop.mainRunLoop, NSDefaultRunLoopMode); + this.displayedLink.addToRunLoopForMode(NSRunLoop.mainRunLoop, UITrackingRunLoopMode); + } + + get rootController() { + return this.window.rootViewController; + } + + get nativeApp() { + return UIApplication.sharedApplication; + } + + get window(): UIWindow { + if (NativeScriptEmbedder.sharedInstance().delegate && !this._window) { + this._window = UIApplication.sharedApplication.keyWindow; + } + + return this._window; + } + + get delegate(): UIApplicationDelegate { + return this._delegate; + } + + set delegate(value: UIApplicationDelegate | unknown) { + if (this._delegate !== value) { + this._delegate = value as UIApplicationDelegate; + } + } + + getNativeApplication() { + return this.nativeApp; + } + + addNotificationObserver( + notificationName: string, + onReceiveCallback: (notification: NSNotification) => void + ) { + const observer = NotificationObserver.initWithCallback(onReceiveCallback); + NSNotificationCenter.defaultCenter.addObserverSelectorNameObject( + observer, + 'onReceive', + notificationName, + null + ); + this._notificationObservers.push(observer); + + return observer; + } + + removeNotificationObserver(observer: any, notificationName: string) { + const index = this._notificationObservers.indexOf(observer); + if (index >= 0) { + this._notificationObservers.splice(index, 1); + NSNotificationCenter.defaultCenter.removeObserverNameObject( + observer, + notificationName, + null + ); + } + } + + protected getSystemAppearance(): 'light' | 'dark' { + // userInterfaceStyle is available on UITraitCollection since iOS 12. + if (Utils.ios.MajorVersion <= 11) { + return undefined; + } + + const userInterfaceStyle = this.rootController.traitCollection.userInterfaceStyle; + return this.getSystemAppearanceValue(userInterfaceStyle); + } + + private getSystemAppearanceValue(userInterfaceStyle: number): 'dark' | 'light' { + switch (userInterfaceStyle) { + case UIUserInterfaceStyle.Dark: + return 'dark'; + case UIUserInterfaceStyle.Light: + case UIUserInterfaceStyle.Unspecified: + return 'light'; + } + } + + protected getOrientation() { + const statusBarOrientation = UIApplication.sharedApplication.statusBarOrientation; + return this.getOrientationValue(statusBarOrientation); + } + + private getOrientationValue(orientation: number): 'portrait' | 'landscape' | 'unknown' { + switch (orientation) { + case UIInterfaceOrientation.LandscapeRight: + case UIInterfaceOrientation.LandscapeLeft: + return 'landscape'; + case UIInterfaceOrientation.PortraitUpsideDown: + case UIInterfaceOrientation.Portrait: + return 'portrait'; + case UIInterfaceOrientation.Unknown: + return 'unknown'; + } + } + + private notifyAppStarted(notification?: NSNotification) { + const args: LaunchEventData = { + eventName: this.launchEvent, + object: this, + ios: + notification?.userInfo?.objectForKey( + 'UIApplicationLaunchOptionsLocalNotificationKey' + ) || null, + }; + + this.notify(args); + this.notify({ + eventName: 'loadAppCss', + object: this, + cssFile: this.getCssFileName(), + }); + + if (this._window) { + if (args.root !== null && !NativeScriptEmbedder.sharedInstance().delegate) { + this.setWindowContent(args.root); + } + } else { + this._window = UIApplication.sharedApplication.keyWindow; + } + } + + private setWindowContent(view?: View): void { + if (this._rootView) { + // if we already have a root view, we reset it. + this._rootView._onRootViewReset(); + } + const rootView = this.createRootView(view); + const controller = this.getViewController(rootView); + + this._rootView = rootView; + + // setup view as styleScopeHost + rootView._setupAsRootView({}); + + this.setViewControllerView(rootView); + + const haveController = this._window.rootViewController !== null; + this._window.rootViewController = controller; + + this.setRootViewsSystemAppearanceCssClass(rootView); + + if (!haveController) { + this._window.makeKeyAndVisible(); + } + + rootView.on(IOSHelper.traitCollectionColorAppearanceChangedEvent, () => { + const userInterfaceStyle = controller.traitCollection.userInterfaceStyle; + const newSystemAppearance = this.getSystemAppearanceValue(userInterfaceStyle); + + this.setSystemAppearance(newSystemAppearance); + }); + } + + // Observers + @profile + private didFinishLaunchingWithOptions(notification: NSNotification) { + this.setMaxRefreshRate(); + + this._window = UIWindow.alloc().initWithFrame(UIScreen.mainScreen.bounds); + + // TODO: Expose Window module so that it can we styled from XML & CSS + this._window.backgroundColor = + Utils.ios.MajorVersion <= 12 || !UIColor.systemBackgroundColor + ? UIColor.whiteColor + : UIColor.systemBackgroundColor; + + this.notifyAppStarted(notification); + } + + @profile + private didBecomeActive(notification: NSNotification) { + // const ios = UIApplication.sharedApplication; + // const object = this; + // setInBackground(false); + // setSuspended(false); + this.notify({ + eventName: this.resumeEvent, + object: this, + ios: this.ios, + }); + this.notify({ + eventName: this.foregroundEvent, + object: this, + ios: this.ios, + }); + // const rootView = this._rootView; + // if (rootView && !rootView.isLoaded) { + // rootView.callLoaded(); + // } + } + + private didEnterBackground(notification: NSNotification) { + // const ios = UIApplication.sharedApplication; + // const object = this; + // setInBackground(true); + // setSuspended(true); + + this.notify({ + eventName: this.suspendEvent, + object: this, + ios: this.ios, + }); + this.notify({ + eventName: this.backgroundEvent, + object: this, + ios: this.ios, + }); + + // const rootView = this._rootView; + // if (rootView && rootView.isLoaded) { + // rootView.callUnloaded(); + // } + } + + private willTerminate(notification: NSNotification) { + this.notify({ + eventName: this.exitEvent, + object: this, + ios: this.ios, + }); + + // const rootView = this._rootView; + // if (rootView && rootView.isLoaded) { + // rootView.callUnloaded(); + // } + } + + private didReceiveMemoryWarning(notification: NSNotification) { + this.notify({ + eventName: this.lowMemoryEvent, + object: this, + ios: this.ios, + }); + } + + private didChangeStatusBarOrientation(notification: NSNotification) { + const statusBarOrientation = UIApplication.sharedApplication.statusBarOrientation; + const newOrientation = this.getOrientationValue(statusBarOrientation); + this.setOrientation(newOrientation); + } + + get ios() { + // ensures Application.ios is defined when running on iOS + return this; + } +} +export * from './application-common'; +export const Application = new iOSApplication(); +export const AndroidApplication = undefined; diff --git a/packages/core/application/index.android.ts b/packages/core/application/index.android.ts index 6edd66627..790c27458 100644 --- a/packages/core/application/index.android.ts +++ b/packages/core/application/index.android.ts @@ -1,527 +1,2 @@ -import { initAccessibilityCssHelper } from '../accessibility/accessibility-css-helper'; -import { initAccessibilityFontScale } from '../accessibility/font-scale'; -import { profile } from '../profiling'; -import { View } from '../ui'; -import { AndroidActivityCallbacks, NavigationEntry } from '../ui/frame/frame-common'; -import type { - AndroidActivityBundleEventData, - AndroidActivityEventData, - ApplicationEventData, - AndroidApplication as IAndroidApplication, -} from './'; -import { ApplicationCommon } from './application-common'; - -declare namespace com { - namespace tns { - class NativeScriptApplication extends android.app.Application { - static getInstance(): NativeScriptApplication; - } - } -} - -@NativeClass -class BroadcastReceiver extends android.content.BroadcastReceiver { - private _onReceiveCallback: ( - context: android.content.Context, - intent: android.content.Intent - ) => void; - - constructor( - onReceiveCallback: ( - context: android.content.Context, - intent: android.content.Intent - ) => void - ) { - super(); - this._onReceiveCallback = onReceiveCallback; - - return global.__native(this); - } - - public onReceive(context: android.content.Context, intent: android.content.Intent) { - if (this._onReceiveCallback) { - this._onReceiveCallback(context, intent); - } - } -} - -@NativeClass -@JavaProxy('org.nativescript.NativeScriptLifecycleCallbacks') -class NativeScriptLifecycleCallbacks extends android.app.Application - .ActivityLifecycleCallbacks { - private activitiesCount: number = 0; - private nativescriptActivity: android.app.Activity; - - @profile - public onActivityCreated( - activity: android.app.Activity, - savedInstanceState: android.os.Bundle - ): void { - // console.log('NativeScriptLifecycleCallbacks onActivityCreated'); - this.setThemeOnLaunch(activity); - - if (!Application.android.startActivity) { - Application.android.setStartActivity(activity); - } - - if (!this.nativescriptActivity && 'isNativeScriptActivity' in activity) { - this.nativescriptActivity = activity; - } - - this.notifyActivityCreated(activity, savedInstanceState); - - if (Application.hasListeners(Application.displayedEvent)) { - this.subscribeForGlobalLayout(activity); - } - } - - @profile - public onActivityDestroyed(activity: android.app.Activity): void { - // console.log('NativeScriptLifecycleCallbacks onActivityDestroyed'); - if (activity === Application.android.foregroundActivity) { - Application.android.setForegroundActivity(undefined); - } - - if (activity === this.nativescriptActivity) { - this.nativescriptActivity = undefined; - } - - if (activity === Application.android.startActivity) { - Application.android.setStartActivity(undefined); - - // Fallback for start activity when it is destroyed but we have a known nativescript activity - if (this.nativescriptActivity) { - Application.android.setStartActivity(this.nativescriptActivity); - } - } - - Application.android.notify({ - eventName: Application.android.activityDestroyedEvent, - object: Application.android, - activity, - } as AndroidActivityEventData); - - // TODO: This is a temporary workaround to force the V8's Garbage Collector, which will force the related Java Object to be collected. - // gc(); - } - - @profile - public onActivityPaused(activity: android.app.Activity): void { - // console.log('NativeScriptLifecycleCallbacks onActivityPaused'); - if ('isNativeScriptActivity' in activity) { - Application.android.notify({ - eventName: Application.suspendEvent, - object: Application.android, - android: activity, - } as ApplicationEventData); - } - - Application.android.notify({ - eventName: Application.android.activityPausedEvent, - object: Application.android, - activity, - } as AndroidActivityEventData); - } - - @profile - public onActivityResumed(activity: android.app.Activity): void { - // console.log('NativeScriptLifecycleCallbacks onActivityResumed'); - Application.android.setForegroundActivity(activity); - - Application.android.notify({ - eventName: Application.android.activityResumedEvent, - object: Application.android, - activity, - } as AndroidActivityEventData); - } - - @profile - public onActivitySaveInstanceState( - activity: android.app.Activity, - bundle: android.os.Bundle - ): void { - // console.log('NativeScriptLifecycleCallbacks onActivitySaveInstanceState'); - - Application.android.notify({ - eventName: Application.android.saveActivityStateEvent, - object: Application.android, - activity, - bundle, - } as AndroidActivityBundleEventData); - } - - @profile - public onActivityStarted(activity: android.app.Activity): void { - // console.log('NativeScriptLifecycleCallbacks onActivityStarted'); - - this.activitiesCount++; - if (this.activitiesCount === 1) { - Application.android.notify({ - eventName: Application.foregroundEvent, - object: Application.android, - android: activity, - } as ApplicationEventData); - } - - Application.android.notify({ - eventName: Application.android.activityStartedEvent, - object: Application.android, - activity, - } as AndroidActivityEventData); - } - - @profile - public onActivityStopped(activity: android.app.Activity): void { - // console.log('NativeScriptLifecycleCallbacks onActivityStopped'); - this.activitiesCount--; - if (this.activitiesCount === 0) { - Application.android.notify({ - eventName: Application.backgroundEvent, - object: Application.android, - android: activity, - } as ApplicationEventData); - } - - Application.android.notify({ - eventName: Application.android.activityStoppedEvent, - object: Application.android, - activity, - } as AndroidActivityEventData); - } - - @profile - setThemeOnLaunch(activity: android.app.Activity) { - // Set app theme after launch screen was used during startup - const activityInfo = activity - .getPackageManager() - .getActivityInfo( - activity.getComponentName(), - android.content.pm.PackageManager.GET_META_DATA - ); - if (activityInfo.metaData) { - const setThemeOnLaunch = activityInfo.metaData.getInt('SET_THEME_ON_LAUNCH', -1); - if (setThemeOnLaunch !== -1) { - activity.setTheme(setThemeOnLaunch); - } - } - } - - @profile - notifyActivityCreated(activity: android.app.Activity, bundle: android.os.Bundle) { - Application.android.notify({ - eventName: Application.android.activityCreatedEvent, - object: Application.android, - activity, - bundle, - } as AndroidActivityBundleEventData); - } - - @profile - subscribeForGlobalLayout(activity: android.app.Activity) { - const rootView = activity.getWindow().getDecorView().getRootView(); - // store the listener not to trigger GC collection before collecting the method - global.onGlobalLayoutListener = - new android.view.ViewTreeObserver.OnGlobalLayoutListener({ - onGlobalLayout() { - Application.android.notify({ - eventName: Application.android.displayedEvent, - object: Application, - android: Application.android, - activity, - }); - const viewTreeObserver = rootView.getViewTreeObserver(); - viewTreeObserver.removeOnGlobalLayoutListener(global.onGlobalLayoutListener); - }, - }); - rootView - .getViewTreeObserver() - .addOnGlobalLayoutListener(global.onGlobalLayoutListener); - } -} - -@NativeClass -@JavaProxy('org.nativescript.NativeScriptComponentCallbacks') -class NativeScriptComponentCallbacks extends android.content.ComponentCallbacks2 { - @profile - public onLowMemory(): void { - gc(); - java.lang.System.gc(); - - Application.notify({ - eventName: Application.lowMemoryEvent, - object: Application, - android: this, - } as ApplicationEventData); - } - - @profile - public onTrimMemory(level: number): void { - // TODO: This is skipped for now, test carefully for OutOfMemory exceptions - } - - @profile - public onConfigurationChanged( - newConfiguration: android.content.res.Configuration - ): void { - Application.android.onConfigurationChanged(newConfiguration); - } -} - -export class AndroidApplication extends ApplicationCommon implements IAndroidApplication { - readonly activityCreatedEvent = 'activityCreated'; - readonly activityDestroyedEvent = 'activityDestroyed'; - readonly activityStartedEvent = 'activityStarted'; - readonly activityPausedEvent = 'activityPaused'; - readonly activityResumedEvent = 'activityResumed'; - readonly activityStoppedEvent = 'activityStopped'; - readonly saveActivityStateEvent = 'saveActivityState'; - readonly activityResultEvent = 'activityResult'; - readonly activityBackPressedEvent = 'activityBackPressed'; - readonly activityNewIntentEvent = 'activityNewIntent'; - readonly activityRequestPermissionsEvent = 'activityRequestPermissions'; - - private _nativeApp: android.app.Application; - // we are using these property to store the callbacks to avoid early GC collection which would trigger MarkReachableObjects - private lifecycleCallbacks: NativeScriptLifecycleCallbacks; - private componentCallbacks: NativeScriptComponentCallbacks; - - constructor() { - super(); - } - - init(nativeApp: android.app.Application): void { - if (this.nativeApp === nativeApp) { - return; - } - - if (this.nativeApp) { - throw new Error('Application.android already initialized.'); - } - - this._nativeApp = nativeApp; - - // we store those callbacks and add a function for clearing them later so that the objects will be eligable for GC - this.lifecycleCallbacks = new NativeScriptLifecycleCallbacks(); - this.nativeApp.registerActivityLifecycleCallbacks(this.lifecycleCallbacks); - - this.componentCallbacks = new NativeScriptComponentCallbacks(); - this.nativeApp.registerComponentCallbacks(this.componentCallbacks); - - this._registerPendingReceivers(); - } - - private _registeredReceivers = {}; - private _pendingReceiverRegistrations = new Array< - (context: android.content.Context) => void - >(); - private _registerPendingReceivers() { - this._pendingReceiverRegistrations.forEach((func) => func(this.context)); - this._pendingReceiverRegistrations.length = 0; - } - - onConfigurationChanged(configuration: android.content.res.Configuration): void { - this.setOrientation(this.getOrientationValue(configuration)); - this.setSystemAppearance(this.getSystemAppearanceValue(configuration)); - } - - getNativeApplication() { - let nativeApp = this.nativeApp; - - if (nativeApp) { - return nativeApp; - } - - // Try getting it from module - check whether application.android.init has been explicitly called - // check whether the com.tns.NativeScriptApplication type exists - if (com.tns.NativeScriptApplication) { - nativeApp = com.tns.NativeScriptApplication.getInstance(); - } - - // the getInstance might return null if com.tns.NativeScriptApplication exists but is not the starting app type - if (!nativeApp) { - // TODO: Should we handle the case when a custom application type is provided and the user has not explicitly initialized the application module? - const clazz = java.lang.Class.forName('android.app.ActivityThread'); - if (clazz) { - const method = clazz.getMethod('currentApplication', null); - if (method) { - nativeApp = method.invoke(null, null); - } - } - } - - // we cannot work without having the app instance - if (!nativeApp) { - throw new Error( - "Failed to retrieve native Android Application object. If you have a custom android.app.Application type implemented make sure that you've called the 'Application.android.init' method." - ); - } - - return nativeApp; - } - - get nativeApp(): android.app.Application { - return this._nativeApp; - } - - run(entry?: string | NavigationEntry): void { - if (this.started) { - throw new Error('Application is already started.'); - } - - this.started = true; - this.mainEntry = typeof entry === 'string' ? { moduleName: entry } : entry; - - if (!this.nativeApp) { - const nativeApp = this.getNativeApplication(); - this.init(nativeApp); - } - - initAccessibilityCssHelper(); - initAccessibilityFontScale(); - } - - /** - * todo: check if true, cause there's no such thing in Utils.android - * @deprecated Use Utils.android.getPackageName() instead. - */ - get packageName() { - return this.nativeApp.getPackageName(); - } - - private _startActivity: android.app.Activity; - private _foregroundActivity: android.app.Activity; - - get startActivity() { - return this._startActivity; - } - - get foregroundActivity() { - return this._foregroundActivity; - } - - setStartActivity(value: android.app.Activity) { - this._startActivity = value; - } - - setForegroundActivity(value: android.app.Activity) { - this._foregroundActivity = value; - } - - get context() { - return this.nativeApp.getApplicationContext(); - } - - public registerBroadcastReceiver( - intentFilter: string, - onReceiveCallback: ( - context: android.content.Context, - intent: android.content.Intent - ) => void - ): void { - const registerFunc = (context: android.content.Context) => { - const receiver: android.content.BroadcastReceiver = new BroadcastReceiver( - onReceiveCallback - ); - context.registerReceiver(receiver, new android.content.IntentFilter(intentFilter)); - this._registeredReceivers[intentFilter] = receiver; - }; - - if (this.context) { - registerFunc(this.context); - } else { - this._pendingReceiverRegistrations.push(registerFunc); - } - } - - public unregisterBroadcastReceiver(intentFilter: string): void { - const receiver = this._registeredReceivers[intentFilter]; - if (receiver) { - this.context.unregisterReceiver(receiver); - this._registeredReceivers[intentFilter] = undefined; - delete this._registeredReceivers[intentFilter]; - } - } - - public getRegisteredBroadcastReceiver( - intentFilter: string - ): android.content.BroadcastReceiver | undefined { - return this._registeredReceivers[intentFilter]; - } - - getRootView(): View { - const activity = this.foregroundActivity || this.startActivity; - if (!activity) { - return undefined; - } - const callbacks: AndroidActivityCallbacks = activity['_callbacks']; - - return callbacks ? callbacks.getRootView() : undefined; - } - - resetRootView(entry?: NavigationEntry | string): void { - super.resetRootView(entry); - - const activity = this.foregroundActivity || this.startActivity; - if (!activity) { - throw new Error('Cannot find android activity.'); - } - - // this.mainEntry = typeof entry === 'string' ? { moduleName: entry } : entry; - const callbacks: AndroidActivityCallbacks = activity['_callbacks']; - if (!callbacks) { - throw new Error('Cannot find android activity callbacks.'); - } - callbacks.resetActivityContent(activity); - } - - getSystemAppearance(): 'light' | 'dark' { - const resources = this.context.getResources(); - const configuration = resources.getConfiguration(); - return this.getSystemAppearanceValue(configuration); - } - - // https://developer.android.com/guide/topics/ui/look-and-feel/darktheme#configuration_changes - private getSystemAppearanceValue( - configuration: android.content.res.Configuration - ): 'dark' | 'light' { - const systemAppearance = - configuration.uiMode & android.content.res.Configuration.UI_MODE_NIGHT_MASK; - - switch (systemAppearance) { - case android.content.res.Configuration.UI_MODE_NIGHT_YES: - return 'dark'; - case android.content.res.Configuration.UI_MODE_NIGHT_NO: - case android.content.res.Configuration.UI_MODE_NIGHT_UNDEFINED: - return 'light'; - } - } - - getOrientation() { - const resources = this.context.getResources(); - const configuration = resources.getConfiguration(); - return this.getOrientationValue(configuration); - } - - private getOrientationValue( - configuration: android.content.res.Configuration - ): 'portrait' | 'landscape' | 'unknown' { - const orientation = configuration.orientation; - - switch (orientation) { - case android.content.res.Configuration.ORIENTATION_LANDSCAPE: - return 'landscape'; - case android.content.res.Configuration.ORIENTATION_PORTRAIT: - return 'portrait'; - default: - return 'unknown'; - } - } - - get android() { - // ensures Application.android is defined when running on iOS - return this; - } -} -export * from './application-common'; -export const Application = new AndroidApplication(); -export const iOSApplication = undefined; +export * from './application'; +export * from './application-shims'; diff --git a/packages/core/application/index.d.ts b/packages/core/application/index.d.ts index f1b291491..790c27458 100644 --- a/packages/core/application/index.d.ts +++ b/packages/core/application/index.d.ts @@ -1,57 +1,2 @@ -import { ApplicationCommon } from './application-common'; - -export * from './application-common'; -export * from './application-interfaces'; - -export const Application: ApplicationCommon; - -export interface AndroidApplication extends ApplicationCommon { - readonly activityCreatedEvent; - readonly activityDestroyedEvent; - readonly activityStartedEvent; - readonly activityPausedEvent; - readonly activityResumedEvent; - readonly activityStoppedEvent; - readonly saveActivityStateEvent; - readonly activityResultEvent; - readonly activityBackPressedEvent; - readonly activityNewIntentEvent; - readonly activityRequestPermissionsEvent; - - getNativeApplication(): android.app.Application; - - init(nativeApp: android.app.Application): void; - - get nativeApp(): android.app.Application; - get packageName(): string; - get startActivity(): android.app.Activity; - get foregroundActivity(): android.app.Activity; - get context(): android.content.Context; - - registerBroadcastReceiver( - intentFilter: string, - onReceiveCallback: ( - context: android.content.Context, - intent: android.content.Intent - ) => void - ): void; - unregisterBroadcastReceiver(intentFilter: string): void; -} - -export interface iOSApplication extends ApplicationCommon { - get rootController(): UIViewController; - get nativeApp(): UIApplication; - get window(): UIWindow; - - get delegate(): UIApplicationDelegate; - set delegate(value: UIApplicationDelegate | unknown); - - getNativeApplication(): UIApplication; - - addNotificationObserver( - notificationName: string, - onReceiveCallback: (notification: NSNotification) => void - ): NotificationObserver; - - removeNotificationObserver(observer: any, notificationName: string); -} +export * from './application'; +export * from './application-shims'; diff --git a/packages/core/application/index.ios.ts b/packages/core/application/index.ios.ts index ab7e52b25..790c27458 100644 --- a/packages/core/application/index.ios.ts +++ b/packages/core/application/index.ios.ts @@ -1,554 +1,2 @@ -import { initAccessibilityCssHelper } from '../accessibility/accessibility-css-helper'; -import { initAccessibilityFontScale } from '../accessibility/font-scale'; -import { profile } from '../profiling'; -import { View } from '../ui'; -import { Builder } from '../ui/builder'; -import { IOSHelper } from '../ui/core/view/view-helper'; -import { NavigationEntry } from '../ui/frame/frame-interfaces'; -import * as Utils from '../utils'; -import type { iOSApplication as IiOSApplication } from './'; -import { ApplicationCommon } from './application-common'; -import { - ApplicationEventData, - LaunchEventData, - LoadAppCSSEventData, -} from './application-interfaces'; - -@NativeClass -class CADisplayLinkTarget extends NSObject { - private _owner: WeakRef; - static initWithOwner(owner: WeakRef): CADisplayLinkTarget { - const target = CADisplayLinkTarget.new(); - target._owner = owner; - return target; - } - - onDisplayed(link: CADisplayLink) { - link.invalidate(); - const owner = this._owner.deref(); - - if (!owner) { - return; - } - - owner.displayedOnce = true; - owner.notify({ - eventName: owner.displayedEvent, - object: owner, - ios: owner.ios, - }); - owner.displayedLinkTarget = null; - owner.displayedLink = null; - } - - public static ObjCExposedMethods = { - onDisplayed: { returns: interop.types.void, params: [CADisplayLink] }, - }; -} - -@NativeClass -class NotificationObserver extends NSObject { - private _onReceiveCallback: (notification: NSNotification) => void; - - public static initWithCallback( - onReceiveCallback: (notification: NSNotification) => void - ): NotificationObserver { - const observer = super.new(); - observer._onReceiveCallback = onReceiveCallback; - - return observer; - } - - public onReceive(notification: NSNotification): void { - this._onReceiveCallback(notification); - } - - public static ObjCExposedMethods = { - onReceive: { returns: interop.types.void, params: [NSNotification] }, - }; -} - -@NativeClass -class Responder extends UIResponder implements UIApplicationDelegate { - get window(): UIWindow { - return Application.ios.window; - } - - set window(value: UIWindow) { - // NOOP - } - - static ObjCProtocols = [UIApplicationDelegate]; -} - -export class iOSApplication extends ApplicationCommon implements IiOSApplication { - private _delegate: UIApplicationDelegate; - private _window: UIWindow; - private _notificationObservers: NotificationObserver[] = []; - private _rootView: View; - - displayedOnce = false; - displayedLinkTarget: CADisplayLinkTarget; - displayedLink: CADisplayLink; - - constructor() { - super(); - - this.addNotificationObserver( - UIApplicationDidFinishLaunchingNotification, - this.didFinishLaunchingWithOptions.bind(this) - ); - this.addNotificationObserver( - UIApplicationDidBecomeActiveNotification, - this.didBecomeActive.bind(this) - ); - this.addNotificationObserver( - UIApplicationDidEnterBackgroundNotification, - this.didEnterBackground.bind(this) - ); - this.addNotificationObserver( - UIApplicationWillTerminateNotification, - this.willTerminate.bind(this) - ); - this.addNotificationObserver( - UIApplicationDidReceiveMemoryWarningNotification, - this.didReceiveMemoryWarning.bind(this) - ); - this.addNotificationObserver( - UIApplicationDidChangeStatusBarOrientationNotification, - this.didChangeStatusBarOrientation.bind(this) - ); - } - - getRootView(): View { - return this._rootView; - } - - resetRootView(view?: View) { - super.resetRootView(view); - this.setWindowContent(); - } - - run(entry?: string | NavigationEntry): void { - console.log('run in iOSApplication', entry); - - this.mainEntry = typeof entry === 'string' ? { moduleName: entry } : entry; - this.started = true; - - if (this.nativeApp) { - this.runAsEmbeddedApp(); - } else { - this.runAsMainApp(); - } - - initAccessibilityCssHelper(); - initAccessibilityFontScale(); - } - - private runAsMainApp() { - console.log('runAsMainApp', this.delegate); - UIApplicationMain( - 0, - null, - null, - this.delegate - ? NSStringFromClass(this.delegate as any) - : NSStringFromClass(Responder) - ); - } - - private runAsEmbeddedApp() { - // TODO: this rootView should be held alive until rootController dismissViewController is called. - const rootView = this.createRootView(); - if (!rootView) { - return; - } - // Attach to the existing iOS app - const window = - this.nativeApp.keyWindow || - (this.nativeApp.windows.count > 0 && this.nativeApp.windows[0]); - - if (!window) { - return; - } - - const rootController = window.rootViewController; - if (!rootController) { - return; - } - - const controller = this.getViewController(rootView); - const embedderDelegate = NativeScriptEmbedder.sharedInstance().delegate; - - rootView._setupAsRootView({}); - rootView.on(IOSHelper.traitCollectionColorAppearanceChangedEvent, () => { - const userInterfaceStyle = controller.traitCollection.userInterfaceStyle; - const newSystemAppearance = this.getSystemAppearanceValue(userInterfaceStyle); - this.setSystemAppearance(newSystemAppearance); - }); - - if (embedderDelegate) { - this.setViewControllerView(rootView); - embedderDelegate.presentNativeScriptApp(controller); - } else { - const visibleVC = Utils.ios.getVisibleViewController(rootController); - visibleVC.presentViewControllerAnimatedCompletion(controller, true, null); - } - - // this.setRootViewsSystemAppearanceCssClass(rootView); - this.notifyAppStarted(); - } - - private createRootView(v?: View) { - let rootView = v; - if (!rootView) { - console.log('createRootView mainEntry', this.mainEntry); - // try to navigate to the mainEntry (if specified) - if (!this.mainEntry) { - throw new Error( - 'Main entry is missing. App cannot be started. Verify app bootstrap.' - ); - } else { - // console.log('createRootView mainEntry:', mainEntry); - rootView = Builder.createViewFromEntry(this.mainEntry); - } - } - // console.log('createRootView rootView:', rootView); - - // setRootViewsCssClasses(rootView); - - return rootView; - } - - private getViewController(rootView: View): UIViewController { - let viewController: UIViewController = rootView.viewController || rootView.ios; - - if (!(viewController instanceof UIViewController)) { - // We set UILayoutViewController dynamically to the root view if it doesn't have a view controller - // At the moment the root view doesn't have its native view created. We set it in the setViewControllerView func - viewController = IOSHelper.UILayoutViewController.initWithOwner( - new WeakRef(rootView) - ) as UIViewController; - rootView.viewController = viewController; - } - - return viewController; - } - - private setViewControllerView(view: View): void { - const viewController: UIViewController = view.viewController || view.ios; - const nativeView = view.ios || view.nativeViewProtected; - - if (!nativeView || !viewController) { - throw new Error('Root should be either UIViewController or UIView'); - } - - if (viewController instanceof IOSHelper.UILayoutViewController) { - viewController.view.addSubview(nativeView); - } - } - - setMaxRefreshRate(options?: { min?: number; max?: number; preferred?: number }): void { - const adjustRefreshRate = () => { - if (!this.displayedLink) { - return; - } - const minFrameRateDisabled = NSBundle.mainBundle.objectForInfoDictionaryKey( - 'CADisableMinimumFrameDurationOnPhone' - ); - - if (minFrameRateDisabled) { - let max = 120; - const deviceMaxFrames = UIScreen.mainScreen?.maximumFramesPerSecond; - if (options?.max) { - if (deviceMaxFrames) { - // iOS 10.3 - max = options.max <= deviceMaxFrames ? options.max : deviceMaxFrames; - } else if (this.displayedLink.preferredFramesPerSecond) { - // iOS 10.0 - max = - options.max <= this.displayedLink.preferredFramesPerSecond - ? options.max - : this.displayedLink.preferredFramesPerSecond; - } - } - - if (Utils.ios.MajorVersion >= 15) { - const min = options?.min || max / 2; - const preferred = options?.preferred || max; - this.displayedLink.preferredFrameRateRange = CAFrameRateRangeMake( - min, - max, - preferred - ); - } else { - this.displayedLink.preferredFramesPerSecond = max; - } - } - }; - - if (this.displayedOnce) { - adjustRefreshRate(); - return; - } - - this.displayedLinkTarget = CADisplayLinkTarget.initWithOwner(new WeakRef(this)); - this.displayedLink = CADisplayLink.displayLinkWithTargetSelector( - this.displayedLinkTarget, - 'onDisplayed' - ); - adjustRefreshRate(); - this.displayedLink.addToRunLoopForMode(NSRunLoop.mainRunLoop, NSDefaultRunLoopMode); - this.displayedLink.addToRunLoopForMode(NSRunLoop.mainRunLoop, UITrackingRunLoopMode); - } - - get rootController() { - return this.window.rootViewController; - } - - get nativeApp() { - return UIApplication.sharedApplication; - } - - get window(): UIWindow { - if (NativeScriptEmbedder.sharedInstance().delegate && !this._window) { - this._window = UIApplication.sharedApplication.keyWindow; - } - - return this._window; - } - - get delegate(): UIApplicationDelegate { - return this._delegate; - } - - set delegate(value: UIApplicationDelegate | unknown) { - if (this._delegate !== value) { - this._delegate = value as UIApplicationDelegate; - } - } - - getNativeApplication() { - return this.nativeApp; - } - - addNotificationObserver( - notificationName: string, - onReceiveCallback: (notification: NSNotification) => void - ) { - const observer = NotificationObserver.initWithCallback(onReceiveCallback); - NSNotificationCenter.defaultCenter.addObserverSelectorNameObject( - observer, - 'onReceive', - notificationName, - null - ); - this._notificationObservers.push(observer); - - return observer; - } - - removeNotificationObserver(observer: any, notificationName: string) { - const index = this._notificationObservers.indexOf(observer); - if (index >= 0) { - this._notificationObservers.splice(index, 1); - NSNotificationCenter.defaultCenter.removeObserverNameObject( - observer, - notificationName, - null - ); - } - } - - protected getSystemAppearance(): 'light' | 'dark' { - // userInterfaceStyle is available on UITraitCollection since iOS 12. - if (Utils.ios.MajorVersion <= 11) { - return undefined; - } - - const userInterfaceStyle = this.rootController.traitCollection.userInterfaceStyle; - return this.getSystemAppearanceValue(userInterfaceStyle); - } - - private getSystemAppearanceValue(userInterfaceStyle: number): 'dark' | 'light' { - switch (userInterfaceStyle) { - case UIUserInterfaceStyle.Dark: - return 'dark'; - case UIUserInterfaceStyle.Light: - case UIUserInterfaceStyle.Unspecified: - return 'light'; - } - } - - protected getOrientation() { - const statusBarOrientation = UIApplication.sharedApplication.statusBarOrientation; - return this.getOrientationValue(statusBarOrientation); - } - - private getOrientationValue(orientation: number): 'portrait' | 'landscape' | 'unknown' { - switch (orientation) { - case UIInterfaceOrientation.LandscapeRight: - case UIInterfaceOrientation.LandscapeLeft: - return 'landscape'; - case UIInterfaceOrientation.PortraitUpsideDown: - case UIInterfaceOrientation.Portrait: - return 'portrait'; - case UIInterfaceOrientation.Unknown: - return 'unknown'; - } - } - - private notifyAppStarted(notification?: NSNotification) { - const args: LaunchEventData = { - eventName: this.launchEvent, - object: this, - ios: - notification?.userInfo?.objectForKey( - 'UIApplicationLaunchOptionsLocalNotificationKey' - ) || null, - }; - - this.notify(args); - this.notify({ - eventName: 'loadAppCss', - object: this, - cssFile: this.getCssFileName(), - }); - - if (this._window) { - if (args.root !== null && !NativeScriptEmbedder.sharedInstance().delegate) { - this.setWindowContent(args.root); - } - } else { - this._window = UIApplication.sharedApplication.keyWindow; - } - } - - private setWindowContent(view?: View): void { - if (this._rootView) { - // if we already have a root view, we reset it. - this._rootView._onRootViewReset(); - } - const rootView = this.createRootView(view); - const controller = this.getViewController(rootView); - - this._rootView = rootView; - - // setup view as styleScopeHost - rootView._setupAsRootView({}); - - this.setViewControllerView(rootView); - - const haveController = this._window.rootViewController !== null; - this._window.rootViewController = controller; - - this.setRootViewsSystemAppearanceCssClass(rootView); - - if (!haveController) { - this._window.makeKeyAndVisible(); - } - - rootView.on(IOSHelper.traitCollectionColorAppearanceChangedEvent, () => { - const userInterfaceStyle = controller.traitCollection.userInterfaceStyle; - const newSystemAppearance = this.getSystemAppearanceValue(userInterfaceStyle); - - this.setSystemAppearance(newSystemAppearance); - }); - } - - // Observers - @profile - private didFinishLaunchingWithOptions(notification: NSNotification) { - this.setMaxRefreshRate(); - - this._window = UIWindow.alloc().initWithFrame(UIScreen.mainScreen.bounds); - - // TODO: Expose Window module so that it can we styled from XML & CSS - this._window.backgroundColor = - Utils.ios.MajorVersion <= 12 || !UIColor.systemBackgroundColor - ? UIColor.whiteColor - : UIColor.systemBackgroundColor; - - this.notifyAppStarted(notification); - } - - @profile - private didBecomeActive(notification: NSNotification) { - // const ios = UIApplication.sharedApplication; - // const object = this; - // setInBackground(false); - // setSuspended(false); - this.notify({ - eventName: this.resumeEvent, - object: this, - ios: this.ios, - }); - this.notify({ - eventName: this.foregroundEvent, - object: this, - ios: this.ios, - }); - // const rootView = this._rootView; - // if (rootView && !rootView.isLoaded) { - // rootView.callLoaded(); - // } - } - - private didEnterBackground(notification: NSNotification) { - // const ios = UIApplication.sharedApplication; - // const object = this; - // setInBackground(true); - // setSuspended(true); - - this.notify({ - eventName: this.suspendEvent, - object: this, - ios: this.ios, - }); - this.notify({ - eventName: this.backgroundEvent, - object: this, - ios: this.ios, - }); - - // const rootView = this._rootView; - // if (rootView && rootView.isLoaded) { - // rootView.callUnloaded(); - // } - } - - private willTerminate(notification: NSNotification) { - this.notify({ - eventName: this.exitEvent, - object: this, - ios: this.ios, - }); - - // const rootView = this._rootView; - // if (rootView && rootView.isLoaded) { - // rootView.callUnloaded(); - // } - } - - private didReceiveMemoryWarning(notification: NSNotification) { - this.notify({ - eventName: this.lowMemoryEvent, - object: this, - ios: this.ios, - }); - } - - private didChangeStatusBarOrientation(notification: NSNotification) { - const statusBarOrientation = UIApplication.sharedApplication.statusBarOrientation; - const newOrientation = this.getOrientationValue(statusBarOrientation); - this.setOrientation(newOrientation); - } - - get ios() { - // ensures Application.ios is defined when running on iOS - return this; - } -} -export * from './application-common'; -export const Application = new iOSApplication(); -export const AndroidApplication = undefined; +export * from './application'; +export * from './application-shims';