UNPKG

@nativescript/core

Version:

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

467 lines • 17.5 kB
import { initAccessibilityCssHelper } from '../accessibility/accessibility-css-helper'; import { initAccessibilityFontScale } from '../accessibility/font-scale'; import { CoreTypes } from '../core-types'; import { CSSUtils } from '../css/system-classes'; import { Device, Screen } from '../platform'; import { profile } from '../profiling'; import { Trace } from '../trace'; import { Builder } from '../ui/builder'; import * as bindableResources from '../ui/core/bindable/bindable-resources'; // prettier-ignore const ORIENTATION_CSS_CLASSES = [ `${CSSUtils.CLASS_PREFIX}${CoreTypes.DeviceOrientation.portrait}`, `${CSSUtils.CLASS_PREFIX}${CoreTypes.DeviceOrientation.landscape}`, `${CSSUtils.CLASS_PREFIX}${CoreTypes.DeviceOrientation.unknown}`, ]; // prettier-ignore const SYSTEM_APPEARANCE_CSS_CLASSES = [ `${CSSUtils.CLASS_PREFIX}${CoreTypes.SystemAppearance.light}`, `${CSSUtils.CLASS_PREFIX}${CoreTypes.SystemAppearance.dark}`, ]; const globalEvents = global.NativeScriptGlobals.events; export class ApplicationCommon { /** * @internal - should not be constructed by the user. */ constructor() { this.launchEvent = 'launch'; this.suspendEvent = 'suspend'; this.displayedEvent = 'displayed'; this.backgroundEvent = 'background'; this.foregroundEvent = 'foreground'; this.resumeEvent = 'resume'; this.exitEvent = 'exit'; this.lowMemoryEvent = 'lowMemory'; this.uncaughtErrorEvent = 'uncaughtError'; this.discardedErrorEvent = 'discardedError'; this.orientationChangedEvent = 'orientationChanged'; this.systemAppearanceChangedEvent = 'systemAppearanceChanged'; this.fontScaleChangedEvent = 'fontScaleChanged'; this.livesyncEvent = 'livesync'; this.loadAppCssEvent = 'loadAppCss'; this.cssChangedEvent = 'cssChanged'; this.initRootViewEvent = 'initRootView'; // Application events go through the global events. this.on = globalEvents.on.bind(globalEvents); this.once = globalEvents.once.bind(globalEvents); this.off = globalEvents.off.bind(globalEvents); this.notify = globalEvents.notify.bind(globalEvents); this.hasListeners = globalEvents.hasListeners.bind(globalEvents); this.cssFile = './app.css'; /** * Boolean to enable/disable systemAppearanceChanged */ this.autoSystemAppearanceChanged = true; this._inBackground = false; this._suspended = false; this.started = false; global.NativeScriptGlobals.appInstanceReady = true; global.__onUncaughtError = (error) => { this.notify({ eventName: this.uncaughtErrorEvent, object: this, android: error, ios: error, error: error, }); }; global.__onDiscardedError = (error) => { this.notify({ eventName: this.discardedErrorEvent, object: this, error: error, }); }; global.__onLiveSync = (context) => { if (this.suspended) { return; } const rootView = this.getRootView(); this.livesync(rootView, context); }; } /** * @internal */ livesync(rootView, context) { this.notify({ eventName: this.livesyncEvent, object: this }); const liveSyncCore = global.__onLiveSyncCore; let reapplyAppStyles = false; // ModuleContext is available only for Hot Module Replacement if (context && context.path) { const styleExtensions = ['css', 'scss']; const appStylesFullFileName = this.getCssFileName(); const appStylesFileName = appStylesFullFileName.substring(0, appStylesFullFileName.lastIndexOf('.') + 1); reapplyAppStyles = styleExtensions.some((ext) => context.path === appStylesFileName.concat(ext)); } // Handle application styles if (rootView && reapplyAppStyles) { rootView._onCssStateChange(); } else if (liveSyncCore) { liveSyncCore(context); } } /** * Applies the the `newCssClass` to the `rootView` and removes all other css classes from `cssClasses` * previously applied to the `rootView`. * @param rootView * @param cssClasses * @param newCssClass * @param skipCssUpdate */ applyCssClass(rootView, cssClasses, newCssClass, skipCssUpdate = false) { if (!rootView.cssClasses.has(newCssClass)) { cssClasses.forEach((cssClass) => this.removeCssClass(rootView, cssClass)); this.addCssClass(rootView, newCssClass); this.increaseStyleScopeApplicationCssSelectorVersion(rootView); if (!skipCssUpdate) { rootView._onCssStateChange(); } if (Trace.isEnabled()) { const rootCssClasses = Array.from(rootView.cssClasses); Trace.write(`Applying root css class: ${newCssClass}. rootView css classes: ${rootCssClasses.join(' ')}`, Trace.categories.Style); } } } addCssClass(rootView, cssClass) { CSSUtils.pushToSystemCssClasses(cssClass); rootView.cssClasses.add(cssClass); } removeCssClass(rootView, cssClass) { CSSUtils.removeSystemCssClass(cssClass); rootView.cssClasses.delete(cssClass); } increaseStyleScopeApplicationCssSelectorVersion(rootView) { const styleScope = rootView._styleScope ?? rootView?.currentPage?._styleScope; if (styleScope) { styleScope._increaseApplicationCssSelectorVersion(); } } setRootViewCSSClasses(rootView) { const platform = Device.os.toLowerCase(); const deviceType = Device.deviceType.toLowerCase(); const orientation = this.orientation(); const systemAppearance = this.systemAppearance(); if (platform) { CSSUtils.pushToSystemCssClasses(`${CSSUtils.CLASS_PREFIX}${platform}`); } if (deviceType) { CSSUtils.pushToSystemCssClasses(`${CSSUtils.CLASS_PREFIX}${deviceType}`); } if (orientation) { CSSUtils.pushToSystemCssClasses(`${CSSUtils.CLASS_PREFIX}${orientation}`); } if (systemAppearance) { CSSUtils.pushToSystemCssClasses(`${CSSUtils.CLASS_PREFIX}${systemAppearance}`); } rootView.cssClasses.add(CSSUtils.ROOT_VIEW_CSS_CLASS); const rootViewCssClasses = CSSUtils.getSystemCssClasses(); rootViewCssClasses.forEach((c) => rootView.cssClasses.add(c)); this.increaseStyleScopeApplicationCssSelectorVersion(rootView); rootView._onCssStateChange(); if (Trace.isEnabled()) { const rootCssClasses = Array.from(rootView.cssClasses); Trace.write(`Setting root css classes: ${rootCssClasses.join(' ')}`, Trace.categories.Style); } } /** * iOS Only * Dynamically change the preferred frame rate * For devices (iOS 15+) which support min/max/preferred frame rate you can specify ranges * For devices (iOS < 15), you can specify the max frame rate * see: https://developer.apple.com/documentation/quartzcore/optimizing_promotion_refresh_rates_for_iphone_13_pro_and_ipad_pro * To use, ensure your Info.plist has: * ```xml * <key>CADisableMinimumFrameDurationOnPhone</key> * <true/> * ``` * @param options { min?: number; max?: number; preferred?: number } */ setMaxRefreshRate(options) { // implement in platform specific files (iOS only for now) } /** * @returns The main entry of the application */ getMainEntry() { return this.mainEntry; } notifyLaunch(additionalLanchEventData) { const launchArgs = { eventName: this.launchEvent, object: this, ios: this.ios, android: this.android, ...additionalLanchEventData, }; this.notify(launchArgs); this.loadAppCss(); return launchArgs.root; } createRootView(view, fireLaunchEvent = false, additionalLanchEventData) { let rootView = view; if (!rootView) { if (fireLaunchEvent) { rootView = this.notifyLaunch(additionalLanchEventData); // useful for integrations that would like to set rootView asynchronously after app launch if (rootView === null) { return null; } } if (!rootView) { // 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.'); } rootView = Builder.createViewFromEntry(this.mainEntry); } } return rootView; } getRootView() { throw new Error('getRootView() Not implemented.'); } resetRootView(entry) { this.mainEntry = typeof entry === 'string' ? { moduleName: entry } : entry; // rest of implementation is platform specific } initRootView(rootView) { this.setRootViewCSSClasses(rootView); initAccessibilityCssHelper(); initAccessibilityFontScale(); this.notify({ eventName: this.initRootViewEvent, rootView }); } /** * Get application level static resources. */ getResources() { return bindableResources.get(); } /** * Set application level static resources. */ setResources(res) { bindableResources.set(res); } /** * Sets css file name for the application. */ setCssFileName(cssFileName) { this.cssFile = cssFileName; this.notify({ eventName: this.cssChangedEvent, object: this, cssFile: cssFileName, }); } /** * Gets css file name for the application. */ getCssFileName() { return this.cssFile; } /** * Loads immediately the app.css. * By default the app.css file is loaded shortly after "loaded". * For the Android snapshot the CSS can be parsed during the snapshot generation, * as the CSS does not depend on runtime APIs, and loadAppCss will be called explicitly. */ loadAppCss() { try { this.notify({ eventName: this.loadAppCssEvent, object: this, ios: this.ios, android: this.android, cssFile: this.getCssFileName(), }); } catch (e) { if (Trace.isEnabled()) { Trace.write(`The app CSS file ${this.getCssFileName()} couldn't be loaded!`, Trace.categories.Style, Trace.messageType.warn); } } } addCss(cssText, attributeScoped) { this.notify({ eventName: this.cssChangedEvent, object: this, cssText: cssText, }); if (!attributeScoped) { const rootView = this.getRootView(); if (rootView) { rootView._onCssStateChange(); } } } run(entry) { throw new Error('run() Not implemented.'); } getOrientation() { // override in platform specific Application class throw new Error('getOrientation() not implemented'); } setOrientation(value) { if (this._orientation === value) { return; } this._orientation = value; // Update metrics early enough regardless of the existence of root view // Also, CSS will use the correct size values during update trigger Screen.mainScreen._updateMetrics(); this.orientationChanged(this.getRootView(), value); this.notify({ eventName: this.orientationChangedEvent, android: this.android, ios: this.ios, newValue: value, object: this, }); } orientation() { return (this._orientation ?? (this._orientation = this.getOrientation())); } orientationChanged(rootView, newOrientation) { if (!rootView) { return; } const newOrientationCssClass = `${CSSUtils.CLASS_PREFIX}${newOrientation}`; this.applyCssClass(rootView, ORIENTATION_CSS_CLASSES, newOrientationCssClass, true); const rootModalViews = rootView._getRootModalViews(); rootModalViews.forEach((rootModalView) => { this.applyCssClass(rootModalView, ORIENTATION_CSS_CLASSES, newOrientationCssClass, true); // Trigger state change for root modal view classes and media queries rootModalView._onCssStateChange(); }); // Trigger state change for root view classes and media queries rootView._onCssStateChange(); } getNativeApplication() { // override in platform specific Application class throw new Error('getNativeApplication() not implemented'); } hasLaunched() { return global.NativeScriptGlobals && global.NativeScriptGlobals.launched; } getSystemAppearance() { // override in platform specific Application class throw new Error('getSystemAppearance() not implemented'); } setSystemAppearance(value) { if (this._systemAppearance === value) { return; } this._systemAppearance = value; this.systemAppearanceChanged(this.getRootView(), value); this.notify({ eventName: this.systemAppearanceChangedEvent, android: this.android, ios: this.ios, newValue: value, object: this, }); } systemAppearance() { // return cached value, or get it from the platform specific override return (this._systemAppearance ?? (this._systemAppearance = this.getSystemAppearance())); } /** * enable/disable systemAppearanceChanged */ setAutoSystemAppearanceChanged(value) { this.autoSystemAppearanceChanged = value; } /** * Updates root view classes including those of modals * @param rootView the root view * @param newSystemAppearance the new appearance change */ systemAppearanceChanged(rootView, newSystemAppearance) { if (!rootView || !this.autoSystemAppearanceChanged) { return; } const newSystemAppearanceCssClass = `${CSSUtils.CLASS_PREFIX}${newSystemAppearance}`; this.applyCssClass(rootView, SYSTEM_APPEARANCE_CSS_CLASSES, newSystemAppearanceCssClass, true); const rootModalViews = rootView._getRootModalViews(); rootModalViews.forEach((rootModalView) => { this.applyCssClass(rootModalView, SYSTEM_APPEARANCE_CSS_CLASSES, newSystemAppearanceCssClass, true); // Trigger state change for root modal view classes and media queries rootModalView._onCssStateChange(); }); // Trigger state change for root view classes and media queries rootView._onCssStateChange(); } get inBackground() { return this._inBackground; } setInBackground(value, additonalData) { this._inBackground = value; this.notify({ eventName: value ? this.backgroundEvent : this.foregroundEvent, object: this, ios: this.ios, ...additonalData, }); } get suspended() { return this._suspended; } setSuspended(value, additonalData) { this._suspended = value; this.notify({ eventName: value ? this.suspendEvent : this.resumeEvent, object: this, ios: this.ios, android: this.android, ...additonalData, }); } get android() { return undefined; } get ios() { return undefined; } get AndroidApplication() { return this.android; } get iOSApplication() { return this.ios; } } // Expose statically for backwards compat on AndroidApplication.on etc. /** * @deprecated Use `Application.android.on()` instead. */ ApplicationCommon.on = globalEvents.on.bind(globalEvents); /** * @deprecated Use `Application.android.once()` instead. */ ApplicationCommon.once = globalEvents.once.bind(globalEvents); /** * @deprecated Use `Application.android.off()` instead. */ ApplicationCommon.off = globalEvents.off.bind(globalEvents); /** * @deprecated Use `Application.android.notify()` instead. */ ApplicationCommon.notify = globalEvents.notify.bind(globalEvents); /** * @deprecated Use `Application.android.hasListeners()` instead. */ ApplicationCommon.hasListeners = globalEvents.hasListeners.bind(globalEvents); __decorate([ profile, __metadata("design:type", Function), __metadata("design:paramtypes", [Object]), __metadata("design:returntype", Function) ], ApplicationCommon.prototype, "notifyLaunch", null); __decorate([ profile, __metadata("design:type", Function), __metadata("design:paramtypes", [Function, Object, Object]), __metadata("design:returntype", void 0) ], ApplicationCommon.prototype, "createRootView", null); //# sourceMappingURL=application-common.js.map