UNPKG

17.5 kBJavaScriptView Raw
1import { initAccessibilityCssHelper } from '../accessibility/accessibility-css-helper';
2import { initAccessibilityFontScale } from '../accessibility/font-scale';
3import { CoreTypes } from '../core-types';
4import { CSSUtils } from '../css/system-classes';
5import { Device, Screen } from '../platform';
6import { profile } from '../profiling';
7import { Trace } from '../trace';
8import { Builder } from '../ui/builder';
9import * as bindableResources from '../ui/core/bindable/bindable-resources';
10// prettier-ignore
11const ORIENTATION_CSS_CLASSES = [
12 `${CSSUtils.CLASS_PREFIX}${CoreTypes.DeviceOrientation.portrait}`,
13 `${CSSUtils.CLASS_PREFIX}${CoreTypes.DeviceOrientation.landscape}`,
14 `${CSSUtils.CLASS_PREFIX}${CoreTypes.DeviceOrientation.unknown}`,
15];
16// prettier-ignore
17const SYSTEM_APPEARANCE_CSS_CLASSES = [
18 `${CSSUtils.CLASS_PREFIX}${CoreTypes.SystemAppearance.light}`,
19 `${CSSUtils.CLASS_PREFIX}${CoreTypes.SystemAppearance.dark}`,
20];
21const globalEvents = global.NativeScriptGlobals.events;
22export class ApplicationCommon {
23 /**
24 * @internal - should not be constructed by the user.
25 */
26 constructor() {
27 this.launchEvent = 'launch';
28 this.suspendEvent = 'suspend';
29 this.displayedEvent = 'displayed';
30 this.backgroundEvent = 'background';
31 this.foregroundEvent = 'foreground';
32 this.resumeEvent = 'resume';
33 this.exitEvent = 'exit';
34 this.lowMemoryEvent = 'lowMemory';
35 this.uncaughtErrorEvent = 'uncaughtError';
36 this.discardedErrorEvent = 'discardedError';
37 this.orientationChangedEvent = 'orientationChanged';
38 this.systemAppearanceChangedEvent = 'systemAppearanceChanged';
39 this.fontScaleChangedEvent = 'fontScaleChanged';
40 this.livesyncEvent = 'livesync';
41 this.loadAppCssEvent = 'loadAppCss';
42 this.cssChangedEvent = 'cssChanged';
43 this.initRootViewEvent = 'initRootView';
44 // Application events go through the global events.
45 this.on = globalEvents.on.bind(globalEvents);
46 this.once = globalEvents.once.bind(globalEvents);
47 this.off = globalEvents.off.bind(globalEvents);
48 this.notify = globalEvents.notify.bind(globalEvents);
49 this.hasListeners = globalEvents.hasListeners.bind(globalEvents);
50 this.cssFile = './app.css';
51 /**
52 * Boolean to enable/disable systemAppearanceChanged
53 */
54 this.autoSystemAppearanceChanged = true;
55 this._inBackground = false;
56 this._suspended = false;
57 this.started = false;
58 global.NativeScriptGlobals.appInstanceReady = true;
59 global.__onUncaughtError = (error) => {
60 this.notify({
61 eventName: this.uncaughtErrorEvent,
62 object: this,
63 android: error,
64 ios: error,
65 error: error,
66 });
67 };
68 global.__onDiscardedError = (error) => {
69 this.notify({
70 eventName: this.discardedErrorEvent,
71 object: this,
72 error: error,
73 });
74 };
75 global.__onLiveSync = (context) => {
76 if (this.suspended) {
77 return;
78 }
79 const rootView = this.getRootView();
80 this.livesync(rootView, context);
81 };
82 }
83 /**
84 * @internal
85 */
86 livesync(rootView, context) {
87 this.notify({ eventName: this.livesyncEvent, object: this });
88 const liveSyncCore = global.__onLiveSyncCore;
89 let reapplyAppStyles = false;
90 // ModuleContext is available only for Hot Module Replacement
91 if (context && context.path) {
92 const styleExtensions = ['css', 'scss'];
93 const appStylesFullFileName = this.getCssFileName();
94 const appStylesFileName = appStylesFullFileName.substring(0, appStylesFullFileName.lastIndexOf('.') + 1);
95 reapplyAppStyles = styleExtensions.some((ext) => context.path === appStylesFileName.concat(ext));
96 }
97 // Handle application styles
98 if (rootView && reapplyAppStyles) {
99 rootView._onCssStateChange();
100 }
101 else if (liveSyncCore) {
102 liveSyncCore(context);
103 }
104 }
105 /**
106 * Applies the the `newCssClass` to the `rootView` and removes all other css classes from `cssClasses`
107 * previously applied to the `rootView`.
108 * @param rootView
109 * @param cssClasses
110 * @param newCssClass
111 * @param skipCssUpdate
112 */
113 applyCssClass(rootView, cssClasses, newCssClass, skipCssUpdate = false) {
114 if (!rootView.cssClasses.has(newCssClass)) {
115 cssClasses.forEach((cssClass) => this.removeCssClass(rootView, cssClass));
116 this.addCssClass(rootView, newCssClass);
117 this.increaseStyleScopeApplicationCssSelectorVersion(rootView);
118 if (!skipCssUpdate) {
119 rootView._onCssStateChange();
120 }
121 if (Trace.isEnabled()) {
122 const rootCssClasses = Array.from(rootView.cssClasses);
123 Trace.write(`Applying root css class: ${newCssClass}. rootView css classes: ${rootCssClasses.join(' ')}`, Trace.categories.Style);
124 }
125 }
126 }
127 addCssClass(rootView, cssClass) {
128 CSSUtils.pushToSystemCssClasses(cssClass);
129 rootView.cssClasses.add(cssClass);
130 }
131 removeCssClass(rootView, cssClass) {
132 CSSUtils.removeSystemCssClass(cssClass);
133 rootView.cssClasses.delete(cssClass);
134 }
135 increaseStyleScopeApplicationCssSelectorVersion(rootView) {
136 const styleScope = rootView._styleScope ?? rootView?.currentPage?._styleScope;
137 if (styleScope) {
138 styleScope._increaseApplicationCssSelectorVersion();
139 }
140 }
141 setRootViewCSSClasses(rootView) {
142 const platform = Device.os.toLowerCase();
143 const deviceType = Device.deviceType.toLowerCase();
144 const orientation = this.orientation();
145 const systemAppearance = this.systemAppearance();
146 if (platform) {
147 CSSUtils.pushToSystemCssClasses(`${CSSUtils.CLASS_PREFIX}${platform}`);
148 }
149 if (deviceType) {
150 CSSUtils.pushToSystemCssClasses(`${CSSUtils.CLASS_PREFIX}${deviceType}`);
151 }
152 if (orientation) {
153 CSSUtils.pushToSystemCssClasses(`${CSSUtils.CLASS_PREFIX}${orientation}`);
154 }
155 if (systemAppearance) {
156 CSSUtils.pushToSystemCssClasses(`${CSSUtils.CLASS_PREFIX}${systemAppearance}`);
157 }
158 rootView.cssClasses.add(CSSUtils.ROOT_VIEW_CSS_CLASS);
159 const rootViewCssClasses = CSSUtils.getSystemCssClasses();
160 rootViewCssClasses.forEach((c) => rootView.cssClasses.add(c));
161 this.increaseStyleScopeApplicationCssSelectorVersion(rootView);
162 rootView._onCssStateChange();
163 if (Trace.isEnabled()) {
164 const rootCssClasses = Array.from(rootView.cssClasses);
165 Trace.write(`Setting root css classes: ${rootCssClasses.join(' ')}`, Trace.categories.Style);
166 }
167 }
168 /**
169 * iOS Only
170 * Dynamically change the preferred frame rate
171 * For devices (iOS 15+) which support min/max/preferred frame rate you can specify ranges
172 * For devices (iOS < 15), you can specify the max frame rate
173 * see: https://developer.apple.com/documentation/quartzcore/optimizing_promotion_refresh_rates_for_iphone_13_pro_and_ipad_pro
174 * To use, ensure your Info.plist has:
175 * ```xml
176 * <key>CADisableMinimumFrameDurationOnPhone</key>
177 * <true/>
178 * ```
179 * @param options { min?: number; max?: number; preferred?: number }
180 */
181 setMaxRefreshRate(options) {
182 // implement in platform specific files (iOS only for now)
183 }
184 /**
185 * @returns The main entry of the application
186 */
187 getMainEntry() {
188 return this.mainEntry;
189 }
190 notifyLaunch(additionalLanchEventData) {
191 const launchArgs = {
192 eventName: this.launchEvent,
193 object: this,
194 ios: this.ios,
195 android: this.android,
196 ...additionalLanchEventData,
197 };
198 this.notify(launchArgs);
199 this.loadAppCss();
200 return launchArgs.root;
201 }
202 createRootView(view, fireLaunchEvent = false, additionalLanchEventData) {
203 let rootView = view;
204 if (!rootView) {
205 if (fireLaunchEvent) {
206 rootView = this.notifyLaunch(additionalLanchEventData);
207 // useful for integrations that would like to set rootView asynchronously after app launch
208 if (rootView === null) {
209 return null;
210 }
211 }
212 if (!rootView) {
213 // try to navigate to the mainEntry (if specified)
214 if (!this.mainEntry) {
215 throw new Error('Main entry is missing. App cannot be started. Verify app bootstrap.');
216 }
217 rootView = Builder.createViewFromEntry(this.mainEntry);
218 }
219 }
220 return rootView;
221 }
222 getRootView() {
223 throw new Error('getRootView() Not implemented.');
224 }
225 resetRootView(entry) {
226 this.mainEntry = typeof entry === 'string' ? { moduleName: entry } : entry;
227 // rest of implementation is platform specific
228 }
229 initRootView(rootView) {
230 this.setRootViewCSSClasses(rootView);
231 initAccessibilityCssHelper();
232 initAccessibilityFontScale();
233 this.notify({ eventName: this.initRootViewEvent, rootView });
234 }
235 /**
236 * Get application level static resources.
237 */
238 getResources() {
239 return bindableResources.get();
240 }
241 /**
242 * Set application level static resources.
243 */
244 setResources(res) {
245 bindableResources.set(res);
246 }
247 /**
248 * Sets css file name for the application.
249 */
250 setCssFileName(cssFileName) {
251 this.cssFile = cssFileName;
252 this.notify({
253 eventName: this.cssChangedEvent,
254 object: this,
255 cssFile: cssFileName,
256 });
257 }
258 /**
259 * Gets css file name for the application.
260 */
261 getCssFileName() {
262 return this.cssFile;
263 }
264 /**
265 * Loads immediately the app.css.
266 * By default the app.css file is loaded shortly after "loaded".
267 * For the Android snapshot the CSS can be parsed during the snapshot generation,
268 * as the CSS does not depend on runtime APIs, and loadAppCss will be called explicitly.
269 */
270 loadAppCss() {
271 try {
272 this.notify({
273 eventName: this.loadAppCssEvent,
274 object: this,
275 ios: this.ios,
276 android: this.android,
277 cssFile: this.getCssFileName(),
278 });
279 }
280 catch (e) {
281 if (Trace.isEnabled()) {
282 Trace.write(`The app CSS file ${this.getCssFileName()} couldn't be loaded!`, Trace.categories.Style, Trace.messageType.warn);
283 }
284 }
285 }
286 addCss(cssText, attributeScoped) {
287 this.notify({
288 eventName: this.cssChangedEvent,
289 object: this,
290 cssText: cssText,
291 });
292 if (!attributeScoped) {
293 const rootView = this.getRootView();
294 if (rootView) {
295 rootView._onCssStateChange();
296 }
297 }
298 }
299 run(entry) {
300 throw new Error('run() Not implemented.');
301 }
302 getOrientation() {
303 // override in platform specific Application class
304 throw new Error('getOrientation() not implemented');
305 }
306 setOrientation(value) {
307 if (this._orientation === value) {
308 return;
309 }
310 this._orientation = value;
311 // Update metrics early enough regardless of the existence of root view
312 // Also, CSS will use the correct size values during update trigger
313 Screen.mainScreen._updateMetrics();
314 this.orientationChanged(this.getRootView(), value);
315 this.notify({
316 eventName: this.orientationChangedEvent,
317 android: this.android,
318 ios: this.ios,
319 newValue: value,
320 object: this,
321 });
322 }
323 orientation() {
324 return (this._orientation ?? (this._orientation = this.getOrientation()));
325 }
326 orientationChanged(rootView, newOrientation) {
327 if (!rootView) {
328 return;
329 }
330 const newOrientationCssClass = `${CSSUtils.CLASS_PREFIX}${newOrientation}`;
331 this.applyCssClass(rootView, ORIENTATION_CSS_CLASSES, newOrientationCssClass, true);
332 const rootModalViews = rootView._getRootModalViews();
333 rootModalViews.forEach((rootModalView) => {
334 this.applyCssClass(rootModalView, ORIENTATION_CSS_CLASSES, newOrientationCssClass, true);
335 // Trigger state change for root modal view classes and media queries
336 rootModalView._onCssStateChange();
337 });
338 // Trigger state change for root view classes and media queries
339 rootView._onCssStateChange();
340 }
341 getNativeApplication() {
342 // override in platform specific Application class
343 throw new Error('getNativeApplication() not implemented');
344 }
345 hasLaunched() {
346 return global.NativeScriptGlobals && global.NativeScriptGlobals.launched;
347 }
348 getSystemAppearance() {
349 // override in platform specific Application class
350 throw new Error('getSystemAppearance() not implemented');
351 }
352 setSystemAppearance(value) {
353 if (this._systemAppearance === value) {
354 return;
355 }
356 this._systemAppearance = value;
357 this.systemAppearanceChanged(this.getRootView(), value);
358 this.notify({
359 eventName: this.systemAppearanceChangedEvent,
360 android: this.android,
361 ios: this.ios,
362 newValue: value,
363 object: this,
364 });
365 }
366 systemAppearance() {
367 // return cached value, or get it from the platform specific override
368 return (this._systemAppearance ?? (this._systemAppearance = this.getSystemAppearance()));
369 }
370 /**
371 * enable/disable systemAppearanceChanged
372 */
373 setAutoSystemAppearanceChanged(value) {
374 this.autoSystemAppearanceChanged = value;
375 }
376 /**
377 * Updates root view classes including those of modals
378 * @param rootView the root view
379 * @param newSystemAppearance the new appearance change
380 */
381 systemAppearanceChanged(rootView, newSystemAppearance) {
382 if (!rootView || !this.autoSystemAppearanceChanged) {
383 return;
384 }
385 const newSystemAppearanceCssClass = `${CSSUtils.CLASS_PREFIX}${newSystemAppearance}`;
386 this.applyCssClass(rootView, SYSTEM_APPEARANCE_CSS_CLASSES, newSystemAppearanceCssClass, true);
387 const rootModalViews = rootView._getRootModalViews();
388 rootModalViews.forEach((rootModalView) => {
389 this.applyCssClass(rootModalView, SYSTEM_APPEARANCE_CSS_CLASSES, newSystemAppearanceCssClass, true);
390 // Trigger state change for root modal view classes and media queries
391 rootModalView._onCssStateChange();
392 });
393 // Trigger state change for root view classes and media queries
394 rootView._onCssStateChange();
395 }
396 get inBackground() {
397 return this._inBackground;
398 }
399 setInBackground(value, additonalData) {
400 this._inBackground = value;
401 this.notify({
402 eventName: value ? this.backgroundEvent : this.foregroundEvent,
403 object: this,
404 ios: this.ios,
405 ...additonalData,
406 });
407 }
408 get suspended() {
409 return this._suspended;
410 }
411 setSuspended(value, additonalData) {
412 this._suspended = value;
413 this.notify({
414 eventName: value ? this.suspendEvent : this.resumeEvent,
415 object: this,
416 ios: this.ios,
417 android: this.android,
418 ...additonalData,
419 });
420 }
421 get android() {
422 return undefined;
423 }
424 get ios() {
425 return undefined;
426 }
427 get AndroidApplication() {
428 return this.android;
429 }
430 get iOSApplication() {
431 return this.ios;
432 }
433}
434// Expose statically for backwards compat on AndroidApplication.on etc.
435/**
436 * @deprecated Use `Application.android.on()` instead.
437 */
438ApplicationCommon.on = globalEvents.on.bind(globalEvents);
439/**
440 * @deprecated Use `Application.android.once()` instead.
441 */
442ApplicationCommon.once = globalEvents.once.bind(globalEvents);
443/**
444 * @deprecated Use `Application.android.off()` instead.
445 */
446ApplicationCommon.off = globalEvents.off.bind(globalEvents);
447/**
448 * @deprecated Use `Application.android.notify()` instead.
449 */
450ApplicationCommon.notify = globalEvents.notify.bind(globalEvents);
451/**
452 * @deprecated Use `Application.android.hasListeners()` instead.
453 */
454ApplicationCommon.hasListeners = globalEvents.hasListeners.bind(globalEvents);
455__decorate([
456 profile,
457 __metadata("design:type", Function),
458 __metadata("design:paramtypes", [Object]),
459 __metadata("design:returntype", Function)
460], ApplicationCommon.prototype, "notifyLaunch", null);
461__decorate([
462 profile,
463 __metadata("design:type", Function),
464 __metadata("design:paramtypes", [Function, Object, Object]),
465 __metadata("design:returntype", void 0)
466], ApplicationCommon.prototype, "createRootView", null);
467//# sourceMappingURL=application-common.js.map
\No newline at end of file