@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
JavaScript
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