@ng-native/expo 0.0.1 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +413 -1
- package/dist/app-info.d.ts +58 -0
- package/dist/app-info.js +71 -0
- package/dist/app-info.js.map +1 -0
- package/dist/apple-sign-in-button.d.ts +16 -0
- package/dist/apple-sign-in-button.js +55 -0
- package/dist/apple-sign-in-button.js.map +1 -0
- package/dist/apple-sign-in.d.ts +68 -0
- package/dist/apple-sign-in.js +120 -0
- package/dist/apple-sign-in.js.map +1 -0
- package/dist/assets.d.ts +24 -0
- package/dist/assets.js +47 -0
- package/dist/assets.js.map +1 -0
- package/dist/background-task.d.ts +66 -0
- package/dist/background-task.js +82 -0
- package/dist/background-task.js.map +1 -0
- package/dist/battery.d.ts +46 -0
- package/dist/battery.js +76 -0
- package/dist/battery.js.map +1 -0
- package/dist/biometrics.d.ts +45 -0
- package/dist/biometrics.js +53 -0
- package/dist/biometrics.js.map +1 -0
- package/dist/brightness.d.ts +38 -0
- package/dist/brightness.js +81 -0
- package/dist/brightness.js.map +1 -0
- package/dist/browser.d.ts +42 -0
- package/dist/browser.js +42 -0
- package/dist/browser.js.map +1 -0
- package/dist/camera.d.ts +48 -0
- package/dist/camera.js +59 -0
- package/dist/camera.js.map +1 -0
- package/dist/clipboard.d.ts +43 -0
- package/dist/clipboard.js +60 -0
- package/dist/clipboard.js.map +1 -0
- package/dist/community-views.d.ts +14 -0
- package/dist/community-views.js +76 -0
- package/dist/community-views.js.map +1 -0
- package/dist/crypto.d.ts +56 -0
- package/dist/crypto.js +84 -0
- package/dist/crypto.js.map +1 -0
- package/dist/database.d.ts +47 -0
- package/dist/database.js +108 -0
- package/dist/database.js.map +1 -0
- package/dist/document-picker.d.ts +34 -0
- package/dist/document-picker.js +40 -0
- package/dist/document-picker.js.map +1 -0
- package/dist/dom-component.d.ts +87 -0
- package/dist/dom-component.js +139 -0
- package/dist/dom-component.js.map +1 -0
- package/dist/expo-image.d.ts +33 -0
- package/dist/expo-image.js +57 -0
- package/dist/expo-image.js.map +1 -0
- package/dist/expo-ui-components.d.ts +327 -0
- package/dist/expo-ui-components.js +707 -0
- package/dist/expo-ui-components.js.map +1 -0
- package/dist/expo-ui.d.ts +46 -0
- package/dist/expo-ui.js +128 -0
- package/dist/expo-ui.js.map +1 -0
- package/dist/file-system.d.ts +63 -0
- package/dist/file-system.js +67 -0
- package/dist/file-system.js.map +1 -0
- package/dist/fonts.d.ts +90 -0
- package/dist/fonts.js +118 -0
- package/dist/fonts.js.map +1 -0
- package/dist/glass.d.ts +33 -0
- package/dist/glass.js +72 -0
- package/dist/glass.js.map +1 -0
- package/dist/haptics.d.ts +49 -0
- package/dist/haptics.js +64 -0
- package/dist/haptics.js.map +1 -0
- package/dist/image-editor.d.ts +62 -0
- package/dist/image-editor.js +101 -0
- package/dist/image-editor.js.map +1 -0
- package/dist/image-picker.d.ts +49 -0
- package/dist/image-picker.js +58 -0
- package/dist/image-picker.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/keep-awake.d.ts +39 -0
- package/dist/keep-awake.js +80 -0
- package/dist/keep-awake.js.map +1 -0
- package/dist/language-model.d.ts +129 -0
- package/dist/language-model.js +225 -0
- package/dist/language-model.js.map +1 -0
- package/dist/locale.d.ts +51 -0
- package/dist/locale.js +78 -0
- package/dist/locale.js.map +1 -0
- package/dist/location.d.ts +68 -0
- package/dist/location.js +83 -0
- package/dist/location.js.map +1 -0
- package/dist/map-view.d.ts +203 -0
- package/dist/map-view.js +184 -0
- package/dist/map-view.js.map +1 -0
- package/dist/media-library.d.ts +103 -0
- package/dist/media-library.js +160 -0
- package/dist/media-library.js.map +1 -0
- package/dist/native-state.d.ts +22 -0
- package/dist/native-state.js +43 -0
- package/dist/native-state.js.map +1 -0
- package/dist/native.d.ts +42 -0
- package/dist/native.js +73 -0
- package/dist/native.js.map +1 -0
- package/dist/network.d.ts +35 -0
- package/dist/network.js +60 -0
- package/dist/network.js.map +1 -0
- package/dist/notifications.d.ts +175 -0
- package/dist/notifications.js +296 -0
- package/dist/notifications.js.map +1 -0
- package/dist/observed.d.ts +37 -0
- package/dist/observed.js +55 -0
- package/dist/observed.js.map +1 -0
- package/dist/orientation.d.ts +35 -0
- package/dist/orientation.js +78 -0
- package/dist/orientation.js.map +1 -0
- package/dist/permissions.d.ts +54 -0
- package/dist/permissions.js +72 -0
- package/dist/permissions.js.map +1 -0
- package/dist/player.d.ts +92 -0
- package/dist/player.js +141 -0
- package/dist/player.js.map +1 -0
- package/dist/register-expo-view.d.ts +55 -0
- package/dist/register-expo-view.js +110 -0
- package/dist/register-expo-view.js.map +1 -0
- package/dist/screen-capture.d.ts +58 -0
- package/dist/screen-capture.js +78 -0
- package/dist/screen-capture.js.map +1 -0
- package/dist/segmented-control.d.ts +32 -0
- package/dist/segmented-control.js +55 -0
- package/dist/segmented-control.js.map +1 -0
- package/dist/sensors.d.ts +85 -0
- package/dist/sensors.js +115 -0
- package/dist/sensors.js.map +1 -0
- package/dist/splash-screen.d.ts +73 -0
- package/dist/splash-screen.js +115 -0
- package/dist/splash-screen.js.map +1 -0
- package/dist/store-review.d.ts +43 -0
- package/dist/store-review.js +52 -0
- package/dist/store-review.js.map +1 -0
- package/dist/store.d.ts +108 -0
- package/dist/store.js +247 -0
- package/dist/store.js.map +1 -0
- package/dist/symbol.d.ts +36 -0
- package/dist/symbol.js +69 -0
- package/dist/symbol.js.map +1 -0
- package/dist/tracking.d.ts +45 -0
- package/dist/tracking.js +54 -0
- package/dist/tracking.js.map +1 -0
- package/dist/transforms.d.ts +2 -0
- package/dist/transforms.js +13 -0
- package/dist/transforms.js.map +1 -0
- package/dist/updates.d.ts +61 -0
- package/dist/updates.js +97 -0
- package/dist/updates.js.map +1 -0
- package/package.json +397 -3
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ScreenCapture`, bound to `expo-screen-capture`: keeping a screen out of screenshots and
|
|
3
|
+
* recordings, and knowing when one was taken.
|
|
4
|
+
*
|
|
5
|
+
* ```ts
|
|
6
|
+
* private readonly capture = inject(ScreenCapture);
|
|
7
|
+
*
|
|
8
|
+
* constructor() {
|
|
9
|
+
* void this.capture.prevent('card-details');
|
|
10
|
+
* inject(DestroyRef).onDestroy(() => void this.capture.allow('card-details'));
|
|
11
|
+
* }
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* `prevent()` and `allow()` take a key, and capture stays prevented while any key is still held:
|
|
15
|
+
* two screens that each prevent it do not undo each other. `screenshots` counts the screenshots
|
|
16
|
+
* the user has taken, and the listener behind it lives as long as the app does and is removed
|
|
17
|
+
* when the app is destroyed. Without the module installed, nothing is prevented and the count
|
|
18
|
+
* stays at zero.
|
|
19
|
+
*/
|
|
20
|
+
import { DestroyRef, InjectionToken, Service, inject, signal } from '@angular/core';
|
|
21
|
+
import { expoModule } from "./native.js";
|
|
22
|
+
import { Permission } from "./permissions.js";
|
|
23
|
+
import * as i0 from "@angular/core";
|
|
24
|
+
/** The permission answer without the module: nothing granted, and nothing to ask. */
|
|
25
|
+
const UNAVAILABLE = { status: 'denied', granted: false, canAskAgain: false };
|
|
26
|
+
export class ScreenCapture {
|
|
27
|
+
/** Overridden in a test to take screenshots nobody took. */
|
|
28
|
+
static SOURCE = new InjectionToken('angular-native.screenCaptureSource', {
|
|
29
|
+
factory: () => expoModule('expo-screen-capture', () => require('expo-screen-capture')),
|
|
30
|
+
});
|
|
31
|
+
native = inject(ScreenCapture.SOURCE);
|
|
32
|
+
count = signal(0, /* @ts-ignore */
|
|
33
|
+
...(ngDevMode ? [{ debugName: "count" }] : /* istanbul ignore next */ []));
|
|
34
|
+
/**
|
|
35
|
+
* The permission screenshot detection needs on Android 13 and earlier, where it reads the
|
|
36
|
+
* photo library. Later Android needs none, and iOS always answers granted.
|
|
37
|
+
*/
|
|
38
|
+
permission = Permission.of(() => this.native?.getPermissionsAsync() ?? Promise.resolve(UNAVAILABLE), () => this.native?.requestPermissionsAsync() ?? Promise.resolve(UNAVAILABLE));
|
|
39
|
+
/** How many screenshots the user has taken while the app was in front. */
|
|
40
|
+
screenshots = this.count.asReadonly();
|
|
41
|
+
constructor() {
|
|
42
|
+
const listener = this.native?.addScreenshotListener(() => this.count.update((n) => n + 1));
|
|
43
|
+
if (listener)
|
|
44
|
+
inject(DestroyRef).onDestroy(() => listener.remove());
|
|
45
|
+
}
|
|
46
|
+
/** Whether the platform can prevent capture. */
|
|
47
|
+
async available() {
|
|
48
|
+
return (await this.native?.isAvailableAsync()) ?? false;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Keeps the app out of screenshots and screen recordings until `allow()` is called with the same
|
|
52
|
+
* key, or the app restarts. Omitting the key uses one shared default key.
|
|
53
|
+
*/
|
|
54
|
+
async prevent(key) {
|
|
55
|
+
await this.native?.preventScreenCaptureAsync(key);
|
|
56
|
+
}
|
|
57
|
+
/** Releases the key; capture is allowed again once no key is held. */
|
|
58
|
+
async allow(key) {
|
|
59
|
+
await this.native?.allowScreenCaptureAsync(key);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* iOS: blurs the app while it is not in focus - in the app switcher, in the background, and
|
|
63
|
+
* during interruptions - by `blurIntensity` from 0 to 1, half by default. Android already hides
|
|
64
|
+
* it in the app switcher while capture is prevented.
|
|
65
|
+
*/
|
|
66
|
+
async protectAppSwitcher(blurIntensity) {
|
|
67
|
+
await this.native?.enableAppSwitcherProtectionAsync(blurIntensity);
|
|
68
|
+
}
|
|
69
|
+
async unprotectAppSwitcher() {
|
|
70
|
+
await this.native?.disableAppSwitcherProtectionAsync();
|
|
71
|
+
}
|
|
72
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: ScreenCapture, deps: [], target: i0.ɵɵFactoryTarget.Service });
|
|
73
|
+
static ɵprov = i0.ɵɵngDeclareService({ minVersion: "22.0.0", version: "22.2.0", ngImport: i0, type: ScreenCapture });
|
|
74
|
+
}
|
|
75
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: ScreenCapture, decorators: [{
|
|
76
|
+
type: Service
|
|
77
|
+
}], ctorParameters: () => [] });
|
|
78
|
+
//# sourceMappingURL=screen-capture.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"screen-capture.js","sourceRoot":"","sources":["../src/screen-capture.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAe,MAAM,eAAe,CAAC;AACjG,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,UAAU,EAA2B,MAAM,kBAAkB,CAAC;;AAiBvE,qFAAqF;AACrF,MAAM,WAAW,GAAuB,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC;AAGjG,MAAM,OAAO,aAAa;IACxB,4DAA4D;IAC5D,MAAM,CAAU,MAAM,GAAG,IAAI,cAAc,CACzC,oCAAoC,EACpC;QACE,OAAO,EAAE,GAAG,EAAE,CACZ,UAAU,CAAC,qBAAqB,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,qBAAqB,CAAS,CAAC;KAClF,CACF,CAAC;IAEe,MAAM,GAAG,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;IACtC,KAAK,GAAG,MAAM,CAAC,CAAC;8EAAC,CAAC;IAEnC;;;OAGG;IACM,UAAU,GAAe,UAAU,CAAC,EAAE,CAC7C,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,mBAAmB,EAAE,IAAI,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,EACxE,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,uBAAuB,EAAE,IAAI,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,CAC7E,CAAC;IAEF,0EAA0E;IACjE,WAAW,GAAmB,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC;IAE/D;QACE,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,EAAE,qBAAqB,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QAC3F,IAAI,QAAQ;YAAE,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;IACtE,CAAC;IAED,gDAAgD;IAChD,KAAK,CAAC,SAAS;QACb,OAAO,CAAC,MAAM,IAAI,CAAC,MAAM,EAAE,gBAAgB,EAAE,CAAC,IAAI,KAAK,CAAC;IAC1D,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,OAAO,CAAC,GAAY;QACxB,MAAM,IAAI,CAAC,MAAM,EAAE,yBAAyB,CAAC,GAAG,CAAC,CAAC;IACpD,CAAC;IAED,sEAAsE;IACtE,KAAK,CAAC,KAAK,CAAC,GAAY;QACtB,MAAM,IAAI,CAAC,MAAM,EAAE,uBAAuB,CAAC,GAAG,CAAC,CAAC;IAClD,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,kBAAkB,CAAC,aAAsB;QAC7C,MAAM,IAAI,CAAC,MAAM,EAAE,gCAAgC,CAAC,aAAa,CAAC,CAAC;IACrE,CAAC;IAED,KAAK,CAAC,oBAAoB;QACxB,MAAM,IAAI,CAAC,MAAM,EAAE,iCAAiC,EAAE,CAAC;IACzD,CAAC;uGA3DU,aAAa;wGAAb,aAAa;;2FAAb,aAAa;kBADzB,OAAO","sourcesContent":["/**\n * `ScreenCapture`, bound to `expo-screen-capture`: keeping a screen out of screenshots and\n * recordings, and knowing when one was taken.\n *\n * ```ts\n * private readonly capture = inject(ScreenCapture);\n *\n * constructor() {\n * void this.capture.prevent('card-details');\n * inject(DestroyRef).onDestroy(() => void this.capture.allow('card-details'));\n * }\n * ```\n *\n * `prevent()` and `allow()` take a key, and capture stays prevented while any key is still held:\n * two screens that each prevent it do not undo each other. `screenshots` counts the screenshots\n * the user has taken, and the listener behind it lives as long as the app does and is removed\n * when the app is destroyed. Without the module installed, nothing is prevented and the count\n * stays at zero.\n */\nimport { DestroyRef, InjectionToken, Service, inject, signal, type Signal } from '@angular/core';\nimport { expoModule } from './native.ts';\nimport { Permission, type PermissionResponse } from './permissions.ts';\n\ntype Expo = typeof import('expo-screen-capture');\n\n/** The module's functions this service calls. The real module is one; a test provides a fake. */\nexport type NativeScreenCapture = Pick<\n Expo,\n | 'isAvailableAsync'\n | 'preventScreenCaptureAsync'\n | 'allowScreenCaptureAsync'\n | 'enableAppSwitcherProtectionAsync'\n | 'disableAppSwitcherProtectionAsync'\n | 'addScreenshotListener'\n | 'getPermissionsAsync'\n | 'requestPermissionsAsync'\n>;\n\n/** The permission answer without the module: nothing granted, and nothing to ask. */\nconst UNAVAILABLE: PermissionResponse = { status: 'denied', granted: false, canAskAgain: false };\n\n@Service()\nexport class ScreenCapture {\n /** Overridden in a test to take screenshots nobody took. */\n static readonly SOURCE = new InjectionToken<NativeScreenCapture | null>(\n 'angular-native.screenCaptureSource',\n {\n factory: () =>\n expoModule('expo-screen-capture', () => require('expo-screen-capture') as Expo),\n },\n );\n\n private readonly native = inject(ScreenCapture.SOURCE);\n private readonly count = signal(0);\n\n /**\n * The permission screenshot detection needs on Android 13 and earlier, where it reads the\n * photo library. Later Android needs none, and iOS always answers granted.\n */\n readonly permission: Permission = Permission.of(\n () => this.native?.getPermissionsAsync() ?? Promise.resolve(UNAVAILABLE),\n () => this.native?.requestPermissionsAsync() ?? Promise.resolve(UNAVAILABLE),\n );\n\n /** How many screenshots the user has taken while the app was in front. */\n readonly screenshots: Signal<number> = this.count.asReadonly();\n\n constructor() {\n const listener = this.native?.addScreenshotListener(() => this.count.update((n) => n + 1));\n if (listener) inject(DestroyRef).onDestroy(() => listener.remove());\n }\n\n /** Whether the platform can prevent capture. */\n async available(): Promise<boolean> {\n return (await this.native?.isAvailableAsync()) ?? false;\n }\n\n /**\n * Keeps the app out of screenshots and screen recordings until `allow()` is called with the same\n * key, or the app restarts. Omitting the key uses one shared default key.\n */\n async prevent(key?: string): Promise<void> {\n await this.native?.preventScreenCaptureAsync(key);\n }\n\n /** Releases the key; capture is allowed again once no key is held. */\n async allow(key?: string): Promise<void> {\n await this.native?.allowScreenCaptureAsync(key);\n }\n\n /**\n * iOS: blurs the app while it is not in focus - in the app switcher, in the background, and\n * during interruptions - by `blurIntensity` from 0 to 1, half by default. Android already hides\n * it in the app switcher while capture is prevented.\n */\n async protectAppSwitcher(blurIntensity?: number): Promise<void> {\n await this.native?.enableAppSwitcherProtectionAsync(blurIntensity);\n }\n\n async unprotectAppSwitcher(): Promise<void> {\n await this.native?.disableAppSwitcherProtectionAsync();\n }\n}\n"]}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { NativeSyntheticEvent } from '@ng-native/fabric';
|
|
2
|
+
import * as i0 from "@angular/core";
|
|
3
|
+
/** The font the segments' titles are drawn in. */
|
|
4
|
+
export interface SegmentedControlFont {
|
|
5
|
+
readonly color?: string;
|
|
6
|
+
readonly fontSize?: number;
|
|
7
|
+
readonly fontFamily?: string;
|
|
8
|
+
readonly fontWeight?: string;
|
|
9
|
+
}
|
|
10
|
+
/** What the control sends when the user picks a segment. */
|
|
11
|
+
export type SegmentedControlChangeEvent = NativeSyntheticEvent<{
|
|
12
|
+
readonly value: string;
|
|
13
|
+
readonly selectedSegmentIndex: number;
|
|
14
|
+
}>;
|
|
15
|
+
export declare class SegmentedControl {
|
|
16
|
+
readonly values: import("@angular/core").InputSignal<readonly string[] | undefined>;
|
|
17
|
+
readonly selectedIndex: import("@angular/core").InputSignalWithTransform<number | undefined, unknown>;
|
|
18
|
+
readonly enabled: import("@angular/core").InputSignalWithTransform<boolean | undefined, unknown>;
|
|
19
|
+
/** Segments do not stay selected: each is a button. */
|
|
20
|
+
readonly momentary: import("@angular/core").InputSignalWithTransform<boolean | undefined, unknown>;
|
|
21
|
+
/** The selected segment's colour. */
|
|
22
|
+
readonly tintColor: import("@angular/core").InputSignal<string | undefined>;
|
|
23
|
+
readonly backgroundColor: import("@angular/core").InputSignal<string | undefined>;
|
|
24
|
+
readonly fontStyle: import("@angular/core").InputSignal<SegmentedControlFont | undefined>;
|
|
25
|
+
readonly activeFontStyle: import("@angular/core").InputSignal<SegmentedControlFont | undefined>;
|
|
26
|
+
/** Each segment as wide as its title rather than all equal. */
|
|
27
|
+
readonly apportionsSegmentWidthsByContent: import("@angular/core").InputSignalWithTransform<boolean | undefined, unknown>;
|
|
28
|
+
readonly accessibilityLabel: import("@angular/core").InputSignal<string | undefined>;
|
|
29
|
+
readonly change: import("@angular/core").OutputEmitterRef<SegmentedControlChangeEvent>;
|
|
30
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<SegmentedControl, never>;
|
|
31
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<SegmentedControl, "segmented-control", never, { "values": { "alias": "values"; "required": false; "isSignal": true; }; "selectedIndex": { "alias": "selectedIndex"; "required": false; "isSignal": true; }; "enabled": { "alias": "enabled"; "required": false; "isSignal": true; }; "momentary": { "alias": "momentary"; "required": false; "isSignal": true; }; "tintColor": { "alias": "tintColor"; "required": false; "isSignal": true; }; "backgroundColor": { "alias": "backgroundColor"; "required": false; "isSignal": true; }; "fontStyle": { "alias": "fontStyle"; "required": false; "isSignal": true; }; "activeFontStyle": { "alias": "activeFontStyle"; "required": false; "isSignal": true; }; "apportionsSegmentWidthsByContent": { "alias": "apportionsSegmentWidthsByContent"; "required": false; "isSignal": true; }; "accessibilityLabel": { "alias": "accessibilityLabel"; "required": false; "isSignal": true; }; }, { "change": "change"; }, never, never, true, never>;
|
|
32
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<segmented-control>` with its props and its `change` event typed.
|
|
3
|
+
*
|
|
4
|
+
* The element is `NATIVE_VIEWS`' name for `@react-native-segmented-control/segmented-control`; this
|
|
5
|
+
* only claims it so strict templates know its props. Each input goes straight to the node, and
|
|
6
|
+
* `change` is an output for its type only: the element's own event is what arrives, as with the
|
|
7
|
+
* `@expo/ui` components in `expo-ui-components.ts`.
|
|
8
|
+
*/
|
|
9
|
+
import { Component, input, output } from '@angular/core';
|
|
10
|
+
import { optionalBoolean, optionalNumber } from "./transforms.js";
|
|
11
|
+
import * as i0 from "@angular/core";
|
|
12
|
+
export class SegmentedControl {
|
|
13
|
+
values = input(/* @ts-ignore */
|
|
14
|
+
...(ngDevMode ? [undefined, { debugName: "values" }] : /* istanbul ignore next */ []));
|
|
15
|
+
selectedIndex = input(undefined, { ...(ngDevMode ? { debugName: "selectedIndex" } : /* istanbul ignore next */ {}), transform: optionalNumber });
|
|
16
|
+
enabled = input(undefined, { ...(ngDevMode ? { debugName: "enabled" } : /* istanbul ignore next */ {}), transform: optionalBoolean });
|
|
17
|
+
/** Segments do not stay selected: each is a button. */
|
|
18
|
+
momentary = input(undefined, { ...(ngDevMode ? { debugName: "momentary" } : /* istanbul ignore next */ {}), transform: optionalBoolean });
|
|
19
|
+
/** The selected segment's colour. */
|
|
20
|
+
tintColor = input(/* @ts-ignore */
|
|
21
|
+
...(ngDevMode ? [undefined, { debugName: "tintColor" }] : /* istanbul ignore next */ []));
|
|
22
|
+
backgroundColor = input(/* @ts-ignore */
|
|
23
|
+
...(ngDevMode ? [undefined, { debugName: "backgroundColor" }] : /* istanbul ignore next */ []));
|
|
24
|
+
fontStyle = input(/* @ts-ignore */
|
|
25
|
+
...(ngDevMode ? [undefined, { debugName: "fontStyle" }] : /* istanbul ignore next */ []));
|
|
26
|
+
activeFontStyle = input(/* @ts-ignore */
|
|
27
|
+
...(ngDevMode ? [undefined, { debugName: "activeFontStyle" }] : /* istanbul ignore next */ []));
|
|
28
|
+
/** Each segment as wide as its title rather than all equal. */
|
|
29
|
+
apportionsSegmentWidthsByContent = input(undefined, { ...(ngDevMode ? { debugName: "apportionsSegmentWidthsByContent" } : /* istanbul ignore next */ {}), transform: optionalBoolean });
|
|
30
|
+
accessibilityLabel = input(/* @ts-ignore */
|
|
31
|
+
...(ngDevMode ? [undefined, { debugName: "accessibilityLabel" }] : /* istanbul ignore next */ []));
|
|
32
|
+
change = output();
|
|
33
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: SegmentedControl, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
34
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.2.0", type: SegmentedControl, isStandalone: true, selector: "segmented-control", inputs: { values: { classPropertyName: "values", publicName: "values", isSignal: true, isRequired: false, transformFunction: null }, selectedIndex: { classPropertyName: "selectedIndex", publicName: "selectedIndex", isSignal: true, isRequired: false, transformFunction: null }, enabled: { classPropertyName: "enabled", publicName: "enabled", isSignal: true, isRequired: false, transformFunction: null }, momentary: { classPropertyName: "momentary", publicName: "momentary", isSignal: true, isRequired: false, transformFunction: null }, tintColor: { classPropertyName: "tintColor", publicName: "tintColor", isSignal: true, isRequired: false, transformFunction: null }, backgroundColor: { classPropertyName: "backgroundColor", publicName: "backgroundColor", isSignal: true, isRequired: false, transformFunction: null }, fontStyle: { classPropertyName: "fontStyle", publicName: "fontStyle", isSignal: true, isRequired: false, transformFunction: null }, activeFontStyle: { classPropertyName: "activeFontStyle", publicName: "activeFontStyle", isSignal: true, isRequired: false, transformFunction: null }, apportionsSegmentWidthsByContent: { classPropertyName: "apportionsSegmentWidthsByContent", publicName: "apportionsSegmentWidthsByContent", isSignal: true, isRequired: false, transformFunction: null }, accessibilityLabel: { classPropertyName: "accessibilityLabel", publicName: "accessibilityLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { change: "change" }, host: { properties: { "values": "values()", "selectedIndex": "selectedIndex()", "enabled": "enabled()", "momentary": "momentary()", "tintColor": "tintColor()", "backgroundColor": "backgroundColor()", "fontStyle": "fontStyle()", "activeFontStyle": "activeFontStyle()", "apportionsSegmentWidthsByContent": "apportionsSegmentWidthsByContent()", "accessibilityLabel": "accessibilityLabel()" } }, ngImport: i0, template: '', isInline: true });
|
|
35
|
+
}
|
|
36
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: SegmentedControl, decorators: [{
|
|
37
|
+
type: Component,
|
|
38
|
+
args: [{
|
|
39
|
+
selector: 'segmented-control',
|
|
40
|
+
template: '',
|
|
41
|
+
host: {
|
|
42
|
+
'[values]': 'values()',
|
|
43
|
+
'[selectedIndex]': 'selectedIndex()',
|
|
44
|
+
'[enabled]': 'enabled()',
|
|
45
|
+
'[momentary]': 'momentary()',
|
|
46
|
+
'[tintColor]': 'tintColor()',
|
|
47
|
+
'[backgroundColor]': 'backgroundColor()',
|
|
48
|
+
'[fontStyle]': 'fontStyle()',
|
|
49
|
+
'[activeFontStyle]': 'activeFontStyle()',
|
|
50
|
+
'[apportionsSegmentWidthsByContent]': 'apportionsSegmentWidthsByContent()',
|
|
51
|
+
'[accessibilityLabel]': 'accessibilityLabel()',
|
|
52
|
+
},
|
|
53
|
+
}]
|
|
54
|
+
}], propDecorators: { values: [{ type: i0.Input, args: [{ isSignal: true, alias: "values", required: false }] }], selectedIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedIndex", required: false }] }], enabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "enabled", required: false }] }], momentary: [{ type: i0.Input, args: [{ isSignal: true, alias: "momentary", required: false }] }], tintColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "tintColor", required: false }] }], backgroundColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "backgroundColor", required: false }] }], fontStyle: [{ type: i0.Input, args: [{ isSignal: true, alias: "fontStyle", required: false }] }], activeFontStyle: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeFontStyle", required: false }] }], apportionsSegmentWidthsByContent: [{ type: i0.Input, args: [{ isSignal: true, alias: "apportionsSegmentWidthsByContent", required: false }] }], accessibilityLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "accessibilityLabel", required: false }] }], change: [{ type: i0.Output, args: ["change"] }] } });
|
|
55
|
+
//# sourceMappingURL=segmented-control.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"segmented-control.js","sourceRoot":"","sources":["../src/segmented-control.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAEzD,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;;AAgClE,MAAM,OAAO,gBAAgB;IAClB,MAAM,GAAG,KAAK;0FAAqB,CAAC;IACpC,aAAa,GAAG,KAAK,CAAS,SAAS,qFAAI,SAAS,EAAE,cAAc,GAAG,CAAC;IACxE,OAAO,GAAG,KAAK,CAAU,SAAS,+EAAI,SAAS,EAAE,eAAe,GAAG,CAAC;IAC7E,uDAAuD;IAC9C,SAAS,GAAG,KAAK,CAAU,SAAS,iFAAI,SAAS,EAAE,eAAe,GAAG,CAAC;IAC/E,qCAAqC;IAC5B,SAAS,GAAG,KAAK;6FAAU,CAAC;IAC5B,eAAe,GAAG,KAAK;mGAAU,CAAC;IAClC,SAAS,GAAG,KAAK;6FAAwB,CAAC;IAC1C,eAAe,GAAG,KAAK;mGAAwB,CAAC;IACzD,+DAA+D;IACtD,gCAAgC,GAAG,KAAK,CAAU,SAAS,wGAClE,SAAS,EAAE,eAAe,GAC1B,CAAC;IACM,kBAAkB,GAAG,KAAK;sGAAU,CAAC;IACrC,MAAM,GAAG,MAAM,EAA+B,CAAC;uGAhB7C,gBAAgB;2FAAhB,gBAAgB,y6DAdjB,EAAE;;2FAcD,gBAAgB;kBAhB5B,SAAS;mBAAC;oBACT,QAAQ,EAAE,mBAAmB;oBAC7B,QAAQ,EAAE,EAAE;oBACZ,IAAI,EAAE;wBACJ,UAAU,EAAE,UAAU;wBACtB,iBAAiB,EAAE,iBAAiB;wBACpC,WAAW,EAAE,WAAW;wBACxB,aAAa,EAAE,aAAa;wBAC5B,aAAa,EAAE,aAAa;wBAC5B,mBAAmB,EAAE,mBAAmB;wBACxC,aAAa,EAAE,aAAa;wBAC5B,mBAAmB,EAAE,mBAAmB;wBACxC,oCAAoC,EAAE,oCAAoC;wBAC1E,sBAAsB,EAAE,sBAAsB;qBAC/C;iBACF","sourcesContent":["/**\n * `<segmented-control>` with its props and its `change` event typed.\n *\n * The element is `NATIVE_VIEWS`' name for `@react-native-segmented-control/segmented-control`; this\n * only claims it so strict templates know its props. Each input goes straight to the node, and\n * `change` is an output for its type only: the element's own event is what arrives, as with the\n * `@expo/ui` components in `expo-ui-components.ts`.\n */\nimport { Component, input, output } from '@angular/core';\nimport type { NativeSyntheticEvent } from '@ng-native/fabric';\nimport { optionalBoolean, optionalNumber } from './transforms.ts';\n\n/** The font the segments' titles are drawn in. */\nexport interface SegmentedControlFont {\n readonly color?: string;\n readonly fontSize?: number;\n readonly fontFamily?: string;\n readonly fontWeight?: string;\n}\n\n/** What the control sends when the user picks a segment. */\nexport type SegmentedControlChangeEvent = NativeSyntheticEvent<{\n readonly value: string;\n readonly selectedSegmentIndex: number;\n}>;\n\n@Component({\n selector: 'segmented-control',\n template: '',\n host: {\n '[values]': 'values()',\n '[selectedIndex]': 'selectedIndex()',\n '[enabled]': 'enabled()',\n '[momentary]': 'momentary()',\n '[tintColor]': 'tintColor()',\n '[backgroundColor]': 'backgroundColor()',\n '[fontStyle]': 'fontStyle()',\n '[activeFontStyle]': 'activeFontStyle()',\n '[apportionsSegmentWidthsByContent]': 'apportionsSegmentWidthsByContent()',\n '[accessibilityLabel]': 'accessibilityLabel()',\n },\n})\nexport class SegmentedControl {\n readonly values = input<readonly string[]>();\n readonly selectedIndex = input<number>(undefined, { transform: optionalNumber });\n readonly enabled = input<boolean>(undefined, { transform: optionalBoolean });\n /** Segments do not stay selected: each is a button. */\n readonly momentary = input<boolean>(undefined, { transform: optionalBoolean });\n /** The selected segment's colour. */\n readonly tintColor = input<string>();\n readonly backgroundColor = input<string>();\n readonly fontStyle = input<SegmentedControlFont>();\n readonly activeFontStyle = input<SegmentedControlFont>();\n /** Each segment as wide as its title rather than all equal. */\n readonly apportionsSegmentWidthsByContent = input<boolean>(undefined, {\n transform: optionalBoolean,\n });\n readonly accessibilityLabel = input<string>();\n readonly change = output<SegmentedControlChangeEvent>();\n}\n"]}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The motion sensors, bound to `expo-sensors`.
|
|
3
|
+
*
|
|
4
|
+
* ```ts
|
|
5
|
+
* private readonly motion = inject(Accelerometer);
|
|
6
|
+
*
|
|
7
|
+
* constructor() {
|
|
8
|
+
* inject(DestroyRef).onDestroy(this.motion.start(50));
|
|
9
|
+
* }
|
|
10
|
+
* ```
|
|
11
|
+
*
|
|
12
|
+
* Every one of them is the same object - `addListener`, `setUpdateInterval`, `isAvailableAsync` -
|
|
13
|
+
* so this is one class parameterised by the reading, not one per sensor. Six services sharing one
|
|
14
|
+
* class is why these are tokens rather than six decorated classes: a `@Service` class cannot be
|
|
15
|
+
* generic in the thing it reports.
|
|
16
|
+
*
|
|
17
|
+
* Nothing is subscribed until `start`, and the interval is explicit: the sensible one is a
|
|
18
|
+
* property of what the app is doing - a compass wants a tenth of a second, a shake detector less -
|
|
19
|
+
* and every event is a change-detection pass, so a sensor left at its default is a phone that
|
|
20
|
+
* never idles.
|
|
21
|
+
*/
|
|
22
|
+
import { InjectionToken, type Signal } from '@angular/core';
|
|
23
|
+
/** What a three-axis sensor reports. Barometer and light report their own shapes. */
|
|
24
|
+
export interface Vector {
|
|
25
|
+
readonly x: number;
|
|
26
|
+
readonly y: number;
|
|
27
|
+
readonly z: number;
|
|
28
|
+
}
|
|
29
|
+
export interface NativeSensor<T> {
|
|
30
|
+
addListener(listener: (reading: T) => void): {
|
|
31
|
+
remove(): void;
|
|
32
|
+
};
|
|
33
|
+
setUpdateInterval(intervalMs: number): void;
|
|
34
|
+
isAvailableAsync(): Promise<boolean>;
|
|
35
|
+
}
|
|
36
|
+
export declare class Sensor<T> {
|
|
37
|
+
private readonly native;
|
|
38
|
+
private readonly current;
|
|
39
|
+
private readonly present;
|
|
40
|
+
private subscription;
|
|
41
|
+
/** The most recent reading, or the stated zero until the sensor has said anything. */
|
|
42
|
+
readonly reading: Signal<T>;
|
|
43
|
+
/**
|
|
44
|
+
* Whether the device has this sensor. Null until the platform has answered, which is one turn
|
|
45
|
+
* away: `isAvailableAsync` is asynchronous even where the answer is a constant.
|
|
46
|
+
*
|
|
47
|
+
* A signal rather than the promise the module offers, because the question is asked in a
|
|
48
|
+
* template - `@if (barometer.available())` - and a promise there is truthy on the first frame
|
|
49
|
+
* and every frame after, which is the failure this project keeps guarding against.
|
|
50
|
+
*/
|
|
51
|
+
readonly available: Signal<boolean | null>;
|
|
52
|
+
constructor(native: NativeSensor<T> | null, zero: T);
|
|
53
|
+
/**
|
|
54
|
+
* Start reading, at `intervalMs` between readings. Returns the function that stops.
|
|
55
|
+
*
|
|
56
|
+
* Explicit rather than automatic, because the sensible interval is a property of what the app is
|
|
57
|
+
* doing with it - a compass needs a tenth of a second and a shake detector needs less - and
|
|
58
|
+
* because a sensor nobody stops is a battery nobody gets back.
|
|
59
|
+
*/
|
|
60
|
+
start(intervalMs?: number): () => void;
|
|
61
|
+
/** Every `start` not yet stopped. */
|
|
62
|
+
private readonly claims;
|
|
63
|
+
private fastest;
|
|
64
|
+
/** Stop reading for everyone who started it. */
|
|
65
|
+
stop(): void;
|
|
66
|
+
}
|
|
67
|
+
export declare const ORIGIN: Vector;
|
|
68
|
+
/** In g. Includes gravity, so a phone lying flat reads about 1 on z. */
|
|
69
|
+
export declare const Accelerometer: InjectionToken<Sensor<import("expo-sensors").AccelerometerMeasurement>>;
|
|
70
|
+
export type Accelerometer = Sensor<import('expo-sensors').AccelerometerMeasurement>;
|
|
71
|
+
/** Rotation, in radians per second. */
|
|
72
|
+
export declare const Gyroscope: InjectionToken<Sensor<import("expo-sensors").GyroscopeMeasurement>>;
|
|
73
|
+
export type Gyroscope = Sensor<import('expo-sensors').GyroscopeMeasurement>;
|
|
74
|
+
/** The magnetic field, in microteslas. What a compass is built on. */
|
|
75
|
+
export declare const Magnetometer: InjectionToken<Sensor<import("expo-sensors").MagnetometerMeasurement>>;
|
|
76
|
+
export type Magnetometer = Sensor<import('expo-sensors').MagnetometerMeasurement>;
|
|
77
|
+
/** Everything at once, plus the orientation and rotation rate derived from it. */
|
|
78
|
+
export declare const DeviceMotion: InjectionToken<Sensor<import("expo-sensors").DeviceMotionMeasurement>>;
|
|
79
|
+
export type DeviceMotion = Sensor<import('expo-sensors').DeviceMotionMeasurement>;
|
|
80
|
+
/** Air pressure, in hectopascals. Present on fewer devices than the rest; check `available`. */
|
|
81
|
+
export declare const Barometer: InjectionToken<Sensor<import("expo-sensors").BarometerMeasurement>>;
|
|
82
|
+
export type Barometer = Sensor<import('expo-sensors').BarometerMeasurement>;
|
|
83
|
+
/** Ambient light in lux. Android only. */
|
|
84
|
+
export declare const LightSensor: InjectionToken<Sensor<import("expo-sensors").LightSensorMeasurement>>;
|
|
85
|
+
export type LightSensor = Sensor<import('expo-sensors').LightSensorMeasurement>;
|
package/dist/sensors.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The motion sensors, bound to `expo-sensors`.
|
|
3
|
+
*
|
|
4
|
+
* ```ts
|
|
5
|
+
* private readonly motion = inject(Accelerometer);
|
|
6
|
+
*
|
|
7
|
+
* constructor() {
|
|
8
|
+
* inject(DestroyRef).onDestroy(this.motion.start(50));
|
|
9
|
+
* }
|
|
10
|
+
* ```
|
|
11
|
+
*
|
|
12
|
+
* Every one of them is the same object - `addListener`, `setUpdateInterval`, `isAvailableAsync` -
|
|
13
|
+
* so this is one class parameterised by the reading, not one per sensor. Six services sharing one
|
|
14
|
+
* class is why these are tokens rather than six decorated classes: a `@Service` class cannot be
|
|
15
|
+
* generic in the thing it reports.
|
|
16
|
+
*
|
|
17
|
+
* Nothing is subscribed until `start`, and the interval is explicit: the sensible one is a
|
|
18
|
+
* property of what the app is doing - a compass wants a tenth of a second, a shake detector less -
|
|
19
|
+
* and every event is a change-detection pass, so a sensor left at its default is a phone that
|
|
20
|
+
* never idles.
|
|
21
|
+
*/
|
|
22
|
+
import { InjectionToken, signal } from '@angular/core';
|
|
23
|
+
import { expoModule } from "./native.js";
|
|
24
|
+
export class Sensor {
|
|
25
|
+
native;
|
|
26
|
+
current;
|
|
27
|
+
present = signal(null, /* @ts-ignore */
|
|
28
|
+
...(ngDevMode ? [{ debugName: "present" }] : /* istanbul ignore next */ []));
|
|
29
|
+
subscription = null;
|
|
30
|
+
/** The most recent reading, or the stated zero until the sensor has said anything. */
|
|
31
|
+
reading;
|
|
32
|
+
/**
|
|
33
|
+
* Whether the device has this sensor. Null until the platform has answered, which is one turn
|
|
34
|
+
* away: `isAvailableAsync` is asynchronous even where the answer is a constant.
|
|
35
|
+
*
|
|
36
|
+
* A signal rather than the promise the module offers, because the question is asked in a
|
|
37
|
+
* template - `@if (barometer.available())` - and a promise there is truthy on the first frame
|
|
38
|
+
* and every frame after, which is the failure this project keeps guarding against.
|
|
39
|
+
*/
|
|
40
|
+
available = this.present.asReadonly();
|
|
41
|
+
constructor(native, zero) {
|
|
42
|
+
this.native = native;
|
|
43
|
+
this.current = signal(zero, /* @ts-ignore */
|
|
44
|
+
...(ngDevMode ? [{ debugName: "current" }] : /* istanbul ignore next */ []));
|
|
45
|
+
this.reading = this.current.asReadonly();
|
|
46
|
+
void (native?.isAvailableAsync() ?? Promise.resolve(false)).then((yes) => this.present.set(yes), () => this.present.set(false));
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Start reading, at `intervalMs` between readings. Returns the function that stops.
|
|
50
|
+
*
|
|
51
|
+
* Explicit rather than automatic, because the sensible interval is a property of what the app is
|
|
52
|
+
* doing with it - a compass needs a tenth of a second and a shake detector needs less - and
|
|
53
|
+
* because a sensor nobody stops is a battery nobody gets back.
|
|
54
|
+
*/
|
|
55
|
+
start(intervalMs = 100) {
|
|
56
|
+
// One sensor serves the whole app, so each start is a claim of its own, and its stop gives
|
|
57
|
+
// back that claim alone: the sensor reads while anyone still wants it, as often as the most
|
|
58
|
+
// demanding of them asks.
|
|
59
|
+
const claim = { intervalMs };
|
|
60
|
+
this.claims.add(claim);
|
|
61
|
+
this.native?.setUpdateInterval(this.fastest());
|
|
62
|
+
this.subscription ??= this.native?.addListener((reading) => this.current.set(reading)) ?? null;
|
|
63
|
+
return () => {
|
|
64
|
+
if (!this.claims.delete(claim))
|
|
65
|
+
return;
|
|
66
|
+
if (this.claims.size === 0)
|
|
67
|
+
this.stop();
|
|
68
|
+
else
|
|
69
|
+
this.native?.setUpdateInterval(this.fastest());
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/** Every `start` not yet stopped. */
|
|
73
|
+
claims = new Set();
|
|
74
|
+
fastest() {
|
|
75
|
+
return Math.min(...[...this.claims].map((claim) => claim.intervalMs));
|
|
76
|
+
}
|
|
77
|
+
/** Stop reading for everyone who started it. */
|
|
78
|
+
stop() {
|
|
79
|
+
this.claims.clear();
|
|
80
|
+
this.subscription?.remove();
|
|
81
|
+
this.subscription = null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
export const ORIGIN = { x: 0, y: 0, z: 0 };
|
|
85
|
+
/** Every reading carries the time it was taken, so the zero has to as well. */
|
|
86
|
+
const STILL = { x: 0, y: 0, z: 0, timestamp: 0 };
|
|
87
|
+
/** One token per sensor, each reading the module only if it is installed. */
|
|
88
|
+
function sensorToken(pick, zero, name) {
|
|
89
|
+
return new InjectionToken(`angular-native.${name}`, {
|
|
90
|
+
factory: () => {
|
|
91
|
+
const expo = expoModule('expo-sensors', () => require('expo-sensors'));
|
|
92
|
+
return new Sensor(expo ? pick(expo) : null, zero);
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
/** In g. Includes gravity, so a phone lying flat reads about 1 on z. */
|
|
97
|
+
export const Accelerometer = sensorToken((expo) => expo.Accelerometer, STILL, 'accelerometer');
|
|
98
|
+
/** Rotation, in radians per second. */
|
|
99
|
+
export const Gyroscope = sensorToken((expo) => expo.Gyroscope, STILL, 'gyroscope');
|
|
100
|
+
/** The magnetic field, in microteslas. What a compass is built on. */
|
|
101
|
+
export const Magnetometer = sensorToken((expo) => expo.Magnetometer, STILL, 'magnetometer');
|
|
102
|
+
/** Everything at once, plus the orientation and rotation rate derived from it. */
|
|
103
|
+
export const DeviceMotion = sensorToken((expo) => expo.DeviceMotion, {
|
|
104
|
+
acceleration: null,
|
|
105
|
+
accelerationIncludingGravity: STILL,
|
|
106
|
+
rotation: { alpha: 0, beta: 0, gamma: 0 },
|
|
107
|
+
rotationRate: null,
|
|
108
|
+
orientation: 0,
|
|
109
|
+
interval: 0,
|
|
110
|
+
}, 'deviceMotion');
|
|
111
|
+
/** Air pressure, in hectopascals. Present on fewer devices than the rest; check `available`. */
|
|
112
|
+
export const Barometer = sensorToken((expo) => expo.Barometer, { pressure: 0, relativeAltitude: 0 }, 'barometer');
|
|
113
|
+
/** Ambient light in lux. Android only. */
|
|
114
|
+
export const LightSensor = sensorToken((expo) => expo.LightSensor, { illuminance: 0 }, 'lightSensor');
|
|
115
|
+
//# sourceMappingURL=sensors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sensors.js","sourceRoot":"","sources":["../src/sensors.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The motion sensors, bound to `expo-sensors`.\n *\n * ```ts\n * private readonly motion = inject(Accelerometer);\n *\n * constructor() {\n * inject(DestroyRef).onDestroy(this.motion.start(50));\n * }\n * ```\n *\n * Every one of them is the same object - `addListener`, `setUpdateInterval`, `isAvailableAsync` -\n * so this is one class parameterised by the reading, not one per sensor. Six services sharing one\n * class is why these are tokens rather than six decorated classes: a `@Service` class cannot be\n * generic in the thing it reports.\n *\n * Nothing is subscribed until `start`, and the interval is explicit: the sensible one is a\n * property of what the app is doing - a compass wants a tenth of a second, a shake detector less -\n * and every event is a change-detection pass, so a sensor left at its default is a phone that\n * never idles.\n */\nimport { InjectionToken, signal, type Signal } from '@angular/core';\nimport { expoModule } from './native.ts';\n\n/** What a three-axis sensor reports. Barometer and light report their own shapes. */\nexport interface Vector {\n readonly x: number;\n readonly y: number;\n readonly z: number;\n}\n\nexport interface NativeSensor<T> {\n addListener(listener: (reading: T) => void): { remove(): void };\n setUpdateInterval(intervalMs: number): void;\n isAvailableAsync(): Promise<boolean>;\n}\n\nexport class Sensor<T> {\n private readonly native: NativeSensor<T> | null;\n private readonly current: ReturnType<typeof signal<T>>;\n private readonly present = signal<boolean | null>(null);\n private subscription: { remove(): void } | null = null;\n\n /** The most recent reading, or the stated zero until the sensor has said anything. */\n readonly reading: Signal<T>;\n\n /**\n * Whether the device has this sensor. Null until the platform has answered, which is one turn\n * away: `isAvailableAsync` is asynchronous even where the answer is a constant.\n *\n * A signal rather than the promise the module offers, because the question is asked in a\n * template - `@if (barometer.available())` - and a promise there is truthy on the first frame\n * and every frame after, which is the failure this project keeps guarding against.\n */\n readonly available: Signal<boolean | null> = this.present.asReadonly();\n\n constructor(native: NativeSensor<T> | null, zero: T) {\n this.native = native;\n this.current = signal(zero);\n this.reading = this.current.asReadonly();\n void (native?.isAvailableAsync() ?? Promise.resolve(false)).then(\n (yes) => this.present.set(yes),\n () => this.present.set(false),\n );\n }\n\n /**\n * Start reading, at `intervalMs` between readings. Returns the function that stops.\n *\n * Explicit rather than automatic, because the sensible interval is a property of what the app is\n * doing with it - a compass needs a tenth of a second and a shake detector needs less - and\n * because a sensor nobody stops is a battery nobody gets back.\n */\n start(intervalMs = 100): () => void {\n // One sensor serves the whole app, so each start is a claim of its own, and its stop gives\n // back that claim alone: the sensor reads while anyone still wants it, as often as the most\n // demanding of them asks.\n const claim = { intervalMs };\n this.claims.add(claim);\n this.native?.setUpdateInterval(this.fastest());\n this.subscription ??= this.native?.addListener((reading) => this.current.set(reading)) ?? null;\n return () => {\n if (!this.claims.delete(claim)) return;\n if (this.claims.size === 0) this.stop();\n else this.native?.setUpdateInterval(this.fastest());\n };\n }\n\n /** Every `start` not yet stopped. */\n private readonly claims = new Set<{ readonly intervalMs: number }>();\n\n private fastest(): number {\n return Math.min(...[...this.claims].map((claim) => claim.intervalMs));\n }\n\n /** Stop reading for everyone who started it. */\n stop(): void {\n this.claims.clear();\n this.subscription?.remove();\n this.subscription = null;\n }\n}\n\nexport const ORIGIN: Vector = { x: 0, y: 0, z: 0 };\n\n/** Every reading carries the time it was taken, so the zero has to as well. */\nconst STILL = { x: 0, y: 0, z: 0, timestamp: 0 };\n\ntype Sensors = typeof import('expo-sensors');\n\n/** One token per sensor, each reading the module only if it is installed. */\nfunction sensorToken<T>(pick: (expo: Sensors) => unknown, zero: T, name: string) {\n return new InjectionToken<Sensor<T>>(`angular-native.${name}`, {\n factory: () => {\n const expo = expoModule('expo-sensors', () => require('expo-sensors') as Sensors);\n return new Sensor<T>(expo ? (pick(expo) as NativeSensor<T>) : null, zero);\n },\n });\n}\n\n/** In g. Includes gravity, so a phone lying flat reads about 1 on z. */\nexport const Accelerometer = sensorToken<import('expo-sensors').AccelerometerMeasurement>(\n (expo) => expo.Accelerometer,\n STILL,\n 'accelerometer',\n);\nexport type Accelerometer = Sensor<import('expo-sensors').AccelerometerMeasurement>;\n\n/** Rotation, in radians per second. */\nexport const Gyroscope = sensorToken<import('expo-sensors').GyroscopeMeasurement>(\n (expo) => expo.Gyroscope,\n STILL,\n 'gyroscope',\n);\nexport type Gyroscope = Sensor<import('expo-sensors').GyroscopeMeasurement>;\n\n/** The magnetic field, in microteslas. What a compass is built on. */\nexport const Magnetometer = sensorToken<import('expo-sensors').MagnetometerMeasurement>(\n (expo) => expo.Magnetometer,\n STILL,\n 'magnetometer',\n);\nexport type Magnetometer = Sensor<import('expo-sensors').MagnetometerMeasurement>;\n\n/** Everything at once, plus the orientation and rotation rate derived from it. */\nexport const DeviceMotion = sensorToken<import('expo-sensors').DeviceMotionMeasurement>(\n (expo) => expo.DeviceMotion,\n {\n acceleration: null,\n accelerationIncludingGravity: STILL,\n rotation: { alpha: 0, beta: 0, gamma: 0 },\n rotationRate: null,\n orientation: 0,\n interval: 0,\n } as import('expo-sensors').DeviceMotionMeasurement,\n 'deviceMotion',\n);\nexport type DeviceMotion = Sensor<import('expo-sensors').DeviceMotionMeasurement>;\n\n/** Air pressure, in hectopascals. Present on fewer devices than the rest; check `available`. */\nexport const Barometer = sensorToken<import('expo-sensors').BarometerMeasurement>(\n (expo) => expo.Barometer,\n { pressure: 0, relativeAltitude: 0 } as import('expo-sensors').BarometerMeasurement,\n 'barometer',\n);\nexport type Barometer = Sensor<import('expo-sensors').BarometerMeasurement>;\n\n/** Ambient light in lux. Android only. */\nexport const LightSensor = sensorToken<import('expo-sensors').LightSensorMeasurement>(\n (expo) => expo.LightSensor,\n { illuminance: 0 } as import('expo-sensors').LightSensorMeasurement,\n 'lightSensor',\n);\nexport type LightSensor = Sensor<import('expo-sensors').LightSensorMeasurement>;\n"]}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `SplashScreen`, bound to `expo-splash-screen`. Held until the app has something worth showing.
|
|
3
|
+
*
|
|
4
|
+
* The whole point is the ordering, and it is easy to get subtly wrong: the native screen hides
|
|
5
|
+
* itself as soon as the first frame is drawn, so an app that loads fonts before mounting shows a
|
|
6
|
+
* blank window for as long as that takes. Preventing the auto-hide has to happen at module scope,
|
|
7
|
+
* before any of that, and hiding has to happen after the first real frame:
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { splashScreen } from '@ng-native/expo/splash-screen';
|
|
11
|
+
* import { loadFonts } from '@ng-native/expo/fonts';
|
|
12
|
+
*
|
|
13
|
+
* // At module scope, before anything renders.
|
|
14
|
+
* splashScreen.hold();
|
|
15
|
+
*
|
|
16
|
+
* AppRegistry.registerRunnable('main', ({ rootTag }) => {
|
|
17
|
+
* const fonts = loadFonts(styleSheetOf(GlobalStyles));
|
|
18
|
+
* mount(Number(rootTag), App, getFabricUIManager(), { globalStyles: styleSheetOf(GlobalStyles) });
|
|
19
|
+
* void splashScreen.hideWhenReady(fonts);
|
|
20
|
+
* });
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* Mounting does not wait for the fonts; the splash screen does. Text laid out before they arrive
|
|
24
|
+
* is behind the splash, so nothing is ever seen in the fallback face and nothing reflows.
|
|
25
|
+
*
|
|
26
|
+
* `hideWhenReady` is that shape as one call, which is the only thing worth wrapping here: every
|
|
27
|
+
* function underneath is already a plain promise with no React in it. The class takes its
|
|
28
|
+
* platform rather than injecting it, because the first call happens before there is an injector.
|
|
29
|
+
*/
|
|
30
|
+
import { InjectionToken } from '@angular/core';
|
|
31
|
+
/** The slice of `expo-splash-screen` this needs. */
|
|
32
|
+
export interface NativeSplashScreen {
|
|
33
|
+
preventAutoHideAsync(): Promise<boolean>;
|
|
34
|
+
hideAsync(): Promise<void>;
|
|
35
|
+
}
|
|
36
|
+
export declare function expoSplashScreen(): NativeSplashScreen | null;
|
|
37
|
+
export declare class Splash {
|
|
38
|
+
private readonly source;
|
|
39
|
+
private resolved;
|
|
40
|
+
private held;
|
|
41
|
+
/**
|
|
42
|
+
* The module, or a function that reaches it: the shared instance is created when its file is
|
|
43
|
+
* imported, and reaching the module then would fail an import rather than the first call.
|
|
44
|
+
*/
|
|
45
|
+
constructor(native: NativeSplashScreen | null | (() => NativeSplashScreen | null));
|
|
46
|
+
private get native();
|
|
47
|
+
/** Whether the module is installed. Without it the screen hides itself on the first frame. */
|
|
48
|
+
get available(): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Keep the splash up. Called at module scope, before anything is mounted.
|
|
51
|
+
*
|
|
52
|
+
* Failures are swallowed: the only ones are races with the screen already having gone, and an
|
|
53
|
+
* app that cannot hold its splash should still start.
|
|
54
|
+
*/
|
|
55
|
+
hold(): void;
|
|
56
|
+
/** Let it go. Safe to call whether or not it was ever held. */
|
|
57
|
+
hide(): Promise<void>;
|
|
58
|
+
/**
|
|
59
|
+
* Hide once `work` is done *and* the first frame after it has been drawn.
|
|
60
|
+
*
|
|
61
|
+
* Hiding the moment the work resolves uncovers the frame that was on screen while it ran, which
|
|
62
|
+
* is the empty one. Waiting a frame is what makes the handover invisible, and is why this is a
|
|
63
|
+
* method rather than two calls at a call site.
|
|
64
|
+
*/
|
|
65
|
+
hideWhenReady(work: Promise<unknown>, nextFrame?: () => Promise<void>): Promise<void>;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* The one instance, exported directly because it is needed before an injector exists: holding the
|
|
69
|
+
* splash is the first thing an entry file does.
|
|
70
|
+
*/
|
|
71
|
+
export declare const splashScreen: Splash;
|
|
72
|
+
export declare const SplashScreen: InjectionToken<Splash>;
|
|
73
|
+
export type SplashScreen = Splash;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `SplashScreen`, bound to `expo-splash-screen`. Held until the app has something worth showing.
|
|
3
|
+
*
|
|
4
|
+
* The whole point is the ordering, and it is easy to get subtly wrong: the native screen hides
|
|
5
|
+
* itself as soon as the first frame is drawn, so an app that loads fonts before mounting shows a
|
|
6
|
+
* blank window for as long as that takes. Preventing the auto-hide has to happen at module scope,
|
|
7
|
+
* before any of that, and hiding has to happen after the first real frame:
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { splashScreen } from '@ng-native/expo/splash-screen';
|
|
11
|
+
* import { loadFonts } from '@ng-native/expo/fonts';
|
|
12
|
+
*
|
|
13
|
+
* // At module scope, before anything renders.
|
|
14
|
+
* splashScreen.hold();
|
|
15
|
+
*
|
|
16
|
+
* AppRegistry.registerRunnable('main', ({ rootTag }) => {
|
|
17
|
+
* const fonts = loadFonts(styleSheetOf(GlobalStyles));
|
|
18
|
+
* mount(Number(rootTag), App, getFabricUIManager(), { globalStyles: styleSheetOf(GlobalStyles) });
|
|
19
|
+
* void splashScreen.hideWhenReady(fonts);
|
|
20
|
+
* });
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* Mounting does not wait for the fonts; the splash screen does. Text laid out before they arrive
|
|
24
|
+
* is behind the splash, so nothing is ever seen in the fallback face and nothing reflows.
|
|
25
|
+
*
|
|
26
|
+
* `hideWhenReady` is that shape as one call, which is the only thing worth wrapping here: every
|
|
27
|
+
* function underneath is already a plain promise with no React in it. The class takes its
|
|
28
|
+
* platform rather than injecting it, because the first call happens before there is an injector.
|
|
29
|
+
*/
|
|
30
|
+
import { InjectionToken } from '@angular/core';
|
|
31
|
+
import { expoModule } from "./native.js";
|
|
32
|
+
export function expoSplashScreen() {
|
|
33
|
+
const expo = expoModule('expo-splash-screen', () => require('expo-splash-screen'));
|
|
34
|
+
if (!expo)
|
|
35
|
+
return null;
|
|
36
|
+
return {
|
|
37
|
+
preventAutoHideAsync: () => expo.preventAutoHideAsync(),
|
|
38
|
+
hideAsync: () => expo.hideAsync(),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export class Splash {
|
|
42
|
+
source;
|
|
43
|
+
resolved = null;
|
|
44
|
+
held = false;
|
|
45
|
+
/**
|
|
46
|
+
* The module, or a function that reaches it: the shared instance is created when its file is
|
|
47
|
+
* imported, and reaching the module then would fail an import rather than the first call.
|
|
48
|
+
*/
|
|
49
|
+
constructor(native) {
|
|
50
|
+
this.source = native;
|
|
51
|
+
}
|
|
52
|
+
get native() {
|
|
53
|
+
this.resolved ??= {
|
|
54
|
+
native: typeof this.source === 'function' ? this.source() : this.source,
|
|
55
|
+
};
|
|
56
|
+
return this.resolved.native;
|
|
57
|
+
}
|
|
58
|
+
/** Whether the module is installed. Without it the screen hides itself on the first frame. */
|
|
59
|
+
get available() {
|
|
60
|
+
return this.native !== null;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Keep the splash up. Called at module scope, before anything is mounted.
|
|
64
|
+
*
|
|
65
|
+
* Failures are swallowed: the only ones are races with the screen already having gone, and an
|
|
66
|
+
* app that cannot hold its splash should still start.
|
|
67
|
+
*/
|
|
68
|
+
hold() {
|
|
69
|
+
if (!this.native || this.held)
|
|
70
|
+
return;
|
|
71
|
+
this.held = true;
|
|
72
|
+
void this.native.preventAutoHideAsync().catch(() => { });
|
|
73
|
+
}
|
|
74
|
+
/** Let it go. Safe to call whether or not it was ever held. */
|
|
75
|
+
async hide() {
|
|
76
|
+
if (!this.native)
|
|
77
|
+
return;
|
|
78
|
+
this.held = false;
|
|
79
|
+
await this.native.hideAsync().catch(() => { });
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Hide once `work` is done *and* the first frame after it has been drawn.
|
|
83
|
+
*
|
|
84
|
+
* Hiding the moment the work resolves uncovers the frame that was on screen while it ran, which
|
|
85
|
+
* is the empty one. Waiting a frame is what makes the handover invisible, and is why this is a
|
|
86
|
+
* method rather than two calls at a call site.
|
|
87
|
+
*/
|
|
88
|
+
async hideWhenReady(work, nextFrame = defaultFrame) {
|
|
89
|
+
try {
|
|
90
|
+
await work;
|
|
91
|
+
}
|
|
92
|
+
finally {
|
|
93
|
+
await nextFrame();
|
|
94
|
+
await this.hide();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/** One turn of the frame loop, or the macrotask queue where there is no loop to wait on. */
|
|
99
|
+
const defaultFrame = () => new Promise((resolve) => {
|
|
100
|
+
const raf = globalThis
|
|
101
|
+
.requestAnimationFrame;
|
|
102
|
+
if (raf)
|
|
103
|
+
raf(() => resolve());
|
|
104
|
+
else
|
|
105
|
+
setTimeout(resolve, 0);
|
|
106
|
+
});
|
|
107
|
+
/**
|
|
108
|
+
* The one instance, exported directly because it is needed before an injector exists: holding the
|
|
109
|
+
* splash is the first thing an entry file does.
|
|
110
|
+
*/
|
|
111
|
+
export const splashScreen = new Splash(expoSplashScreen);
|
|
112
|
+
export const SplashScreen = new InjectionToken('angular-native.splashScreen', {
|
|
113
|
+
factory: () => splashScreen,
|
|
114
|
+
});
|
|
115
|
+
//# sourceMappingURL=splash-screen.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"splash-screen.js","sourceRoot":"","sources":["../src/splash-screen.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAQzC,MAAM,UAAU,gBAAgB;IAC9B,MAAM,IAAI,GAAG,UAAU,CACrB,oBAAoB,EACpB,GAAG,EAAE,CAAC,OAAO,CAAC,oBAAoB,CAAwC,CAC3E,CAAC;IACF,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IACvB,OAAO;QACL,oBAAoB,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,oBAAoB,EAAE;QACvD,SAAS,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,SAAS,EAAE;KAClC,CAAC;AACJ,CAAC;AAED,MAAM,OAAO,MAAM;IACA,MAAM,CAAgE;IAC/E,QAAQ,GAA0D,IAAI,CAAC;IACvE,IAAI,GAAG,KAAK,CAAC;IAErB;;;OAGG;IACH,YAAY,MAAqE;QAC/E,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;IAED,IAAY,MAAM;QAChB,IAAI,CAAC,QAAQ,KAAK;YAChB,MAAM,EAAE,OAAO,IAAI,CAAC,MAAM,KAAK,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM;SACxE,CAAC;QACF,OAAO,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;IAC9B,CAAC;IAED,8FAA8F;IAC9F,IAAI,SAAS;QACX,OAAO,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC;IAC9B,CAAC;IAED;;;;;OAKG;IACH,IAAI;QACF,IAAI,CAAC,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,IAAI;YAAE,OAAO;QACtC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,KAAK,IAAI,CAAC,MAAM,CAAC,oBAAoB,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IAC1D,CAAC;IAED,+DAA+D;IAC/D,KAAK,CAAC,IAAI;QACR,IAAI,CAAC,IAAI,CAAC,MAAM;YAAE,OAAO;QACzB,IAAI,CAAC,IAAI,GAAG,KAAK,CAAC;QAClB,MAAM,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IAChD,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,aAAa,CAAC,IAAsB,EAAE,SAAS,GAAG,YAAY;QAClE,IAAI,CAAC;YACH,MAAM,IAAI,CAAC;QACb,CAAC;gBAAS,CAAC;YACT,MAAM,SAAS,EAAE,CAAC;YAClB,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QACpB,CAAC;IACH,CAAC;CACF;AAED,4FAA4F;AAC5F,MAAM,YAAY,GAAG,GAAkB,EAAE,CACvC,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;IACtB,MAAM,GAAG,GAAI,UAAmE;SAC7E,qBAAqB,CAAC;IACzB,IAAI,GAAG;QAAE,GAAG,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;;QACzB,UAAU,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;AAC9B,CAAC,CAAC,CAAC;AAEL;;;GAGG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,MAAM,CAAC,gBAAgB,CAAC,CAAC;AAEzD,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,cAAc,CAAS,6BAA6B,EAAE;IACpF,OAAO,EAAE,GAAG,EAAE,CAAC,YAAY;CAC5B,CAAC,CAAC","sourcesContent":["/**\n * `SplashScreen`, bound to `expo-splash-screen`. Held until the app has something worth showing.\n *\n * The whole point is the ordering, and it is easy to get subtly wrong: the native screen hides\n * itself as soon as the first frame is drawn, so an app that loads fonts before mounting shows a\n * blank window for as long as that takes. Preventing the auto-hide has to happen at module scope,\n * before any of that, and hiding has to happen after the first real frame:\n *\n * ```ts\n * import { splashScreen } from '@ng-native/expo/splash-screen';\n * import { loadFonts } from '@ng-native/expo/fonts';\n *\n * // At module scope, before anything renders.\n * splashScreen.hold();\n *\n * AppRegistry.registerRunnable('main', ({ rootTag }) => {\n * const fonts = loadFonts(styleSheetOf(GlobalStyles));\n * mount(Number(rootTag), App, getFabricUIManager(), { globalStyles: styleSheetOf(GlobalStyles) });\n * void splashScreen.hideWhenReady(fonts);\n * });\n * ```\n *\n * Mounting does not wait for the fonts; the splash screen does. Text laid out before they arrive\n * is behind the splash, so nothing is ever seen in the fallback face and nothing reflows.\n *\n * `hideWhenReady` is that shape as one call, which is the only thing worth wrapping here: every\n * function underneath is already a plain promise with no React in it. The class takes its\n * platform rather than injecting it, because the first call happens before there is an injector.\n */\nimport { InjectionToken } from '@angular/core';\nimport { expoModule } from './native.ts';\n\n/** The slice of `expo-splash-screen` this needs. */\nexport interface NativeSplashScreen {\n preventAutoHideAsync(): Promise<boolean>;\n hideAsync(): Promise<void>;\n}\n\nexport function expoSplashScreen(): NativeSplashScreen | null {\n const expo = expoModule(\n 'expo-splash-screen',\n () => require('expo-splash-screen') as typeof import('expo-splash-screen'),\n );\n if (!expo) return null;\n return {\n preventAutoHideAsync: () => expo.preventAutoHideAsync(),\n hideAsync: () => expo.hideAsync(),\n };\n}\n\nexport class Splash {\n private readonly source: NativeSplashScreen | null | (() => NativeSplashScreen | null);\n private resolved: { readonly native: NativeSplashScreen | null } | null = null;\n private held = false;\n\n /**\n * The module, or a function that reaches it: the shared instance is created when its file is\n * imported, and reaching the module then would fail an import rather than the first call.\n */\n constructor(native: NativeSplashScreen | null | (() => NativeSplashScreen | null)) {\n this.source = native;\n }\n\n private get native(): NativeSplashScreen | null {\n this.resolved ??= {\n native: typeof this.source === 'function' ? this.source() : this.source,\n };\n return this.resolved.native;\n }\n\n /** Whether the module is installed. Without it the screen hides itself on the first frame. */\n get available(): boolean {\n return this.native !== null;\n }\n\n /**\n * Keep the splash up. Called at module scope, before anything is mounted.\n *\n * Failures are swallowed: the only ones are races with the screen already having gone, and an\n * app that cannot hold its splash should still start.\n */\n hold(): void {\n if (!this.native || this.held) return;\n this.held = true;\n void this.native.preventAutoHideAsync().catch(() => {});\n }\n\n /** Let it go. Safe to call whether or not it was ever held. */\n async hide(): Promise<void> {\n if (!this.native) return;\n this.held = false;\n await this.native.hideAsync().catch(() => {});\n }\n\n /**\n * Hide once `work` is done *and* the first frame after it has been drawn.\n *\n * Hiding the moment the work resolves uncovers the frame that was on screen while it ran, which\n * is the empty one. Waiting a frame is what makes the handover invisible, and is why this is a\n * method rather than two calls at a call site.\n */\n async hideWhenReady(work: Promise<unknown>, nextFrame = defaultFrame): Promise<void> {\n try {\n await work;\n } finally {\n await nextFrame();\n await this.hide();\n }\n }\n}\n\n/** One turn of the frame loop, or the macrotask queue where there is no loop to wait on. */\nconst defaultFrame = (): Promise<void> =>\n new Promise((resolve) => {\n const raf = (globalThis as { requestAnimationFrame?: (cb: () => void) => void })\n .requestAnimationFrame;\n if (raf) raf(() => resolve());\n else setTimeout(resolve, 0);\n });\n\n/**\n * The one instance, exported directly because it is needed before an injector exists: holding the\n * splash is the first thing an entry file does.\n */\nexport const splashScreen = new Splash(expoSplashScreen);\n\nexport const SplashScreen = new InjectionToken<Splash>('angular-native.splashScreen', {\n factory: () => splashScreen,\n});\nexport type SplashScreen = Splash;\n"]}
|