@ng-native/expo 0.6.0 → 0.8.0
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/dist/expo-ui-components.d.ts +82 -6
- package/dist/expo-ui-components.js +172 -16
- package/dist/expo-ui-components.js.map +1 -1
- package/dist/expo-ui.js +4 -0
- package/dist/expo-ui.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/permissions.d.ts +23 -3
- package/dist/permissions.js +63 -7
- package/dist/permissions.js.map +1 -1
- package/package.json +2 -2
package/dist/permissions.d.ts
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* Camera.Camera.requestCameraPermissionsAsync);
|
|
15
15
|
* ```
|
|
16
16
|
*/
|
|
17
|
-
import { type Signal } from '@angular/core';
|
|
17
|
+
import { InjectionToken, type Signal } from '@angular/core';
|
|
18
18
|
/** What every Expo permission call answers with. */
|
|
19
19
|
export interface PermissionResponse {
|
|
20
20
|
readonly status: 'granted' | 'denied' | 'undetermined';
|
|
@@ -29,9 +29,25 @@ export interface PermissionApi {
|
|
|
29
29
|
}
|
|
30
30
|
/** What a permission is without its module: refused, and never to be asked about again. */
|
|
31
31
|
export declare const UNAVAILABLE: PermissionResponse;
|
|
32
|
+
/** Calls the listener each time the app comes back to the front, and answers how to stop. */
|
|
33
|
+
export type ForegroundEvents = (listener: () => void) => () => void;
|
|
32
34
|
export declare class Permission {
|
|
35
|
+
/**
|
|
36
|
+
* Overridden in a test to bring the app back to the front without a device.
|
|
37
|
+
*
|
|
38
|
+
* Turning a permission on means going to Settings, so coming back is when an answer can have
|
|
39
|
+
* changed. Neither platform restarts the app for that.
|
|
40
|
+
*/
|
|
41
|
+
static readonly FOREGROUND: InjectionToken<ForegroundEvents | null>;
|
|
33
42
|
private readonly api;
|
|
34
43
|
private readonly last;
|
|
44
|
+
private asked;
|
|
45
|
+
private answered;
|
|
46
|
+
/**
|
|
47
|
+
* Built in an injection context, as a field of a service or a component, it checks again each
|
|
48
|
+
* time the app comes back to the front while its answer is a no, and stops when that injector is
|
|
49
|
+
* destroyed. Built outside one, it only learns what `check()`, `request()` and `ensure()` tell it.
|
|
50
|
+
*/
|
|
35
51
|
constructor(api: PermissionApi);
|
|
36
52
|
/** `unknown` until something has asked the platform, which is not the same as `undetermined`. */
|
|
37
53
|
readonly status: Signal<PermissionResponse['status'] | 'unknown'>;
|
|
@@ -49,8 +65,12 @@ export declare class Permission {
|
|
|
49
65
|
*
|
|
50
66
|
* The method most call sites want. A permission already granted needs no dialog, and one the
|
|
51
67
|
* platform has stopped asking about would return the same no while looking like the user had
|
|
52
|
-
* been consulted.
|
|
68
|
+
* been consulted. A no is checked again first, because the person can have changed it in
|
|
69
|
+
* Settings since, and checking shows nothing.
|
|
53
70
|
*/
|
|
54
71
|
ensure(): Promise<boolean>;
|
|
55
|
-
|
|
72
|
+
/** An answer to a call made before the one last answered is older, and is not recorded. */
|
|
73
|
+
private ask;
|
|
74
|
+
/** A failed check leaves the last answer as it was, as a check that never ran would. */
|
|
75
|
+
private recheckRefusal;
|
|
56
76
|
}
|
package/dist/permissions.js
CHANGED
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
* Camera.Camera.requestCameraPermissionsAsync);
|
|
15
15
|
* ```
|
|
16
16
|
*/
|
|
17
|
-
import { computed, signal } from '@angular/core';
|
|
17
|
+
import { DestroyRef, InjectionToken, computed, inject, signal } from '@angular/core';
|
|
18
|
+
import { optional } from "./native.js";
|
|
18
19
|
/** What a permission is without its module: refused, and never to be asked about again. */
|
|
19
20
|
export const UNAVAILABLE = {
|
|
20
21
|
status: 'denied',
|
|
@@ -22,11 +23,44 @@ export const UNAVAILABLE = {
|
|
|
22
23
|
canAskAgain: false,
|
|
23
24
|
};
|
|
24
25
|
export class Permission {
|
|
26
|
+
/**
|
|
27
|
+
* Overridden in a test to bring the app back to the front without a device.
|
|
28
|
+
*
|
|
29
|
+
* Turning a permission on means going to Settings, so coming back is when an answer can have
|
|
30
|
+
* changed. Neither platform restarts the app for that.
|
|
31
|
+
*/
|
|
32
|
+
static FOREGROUND = new InjectionToken('angular-native.permissionForeground', {
|
|
33
|
+
factory: () => {
|
|
34
|
+
const rn = optional(() => require('react-native'));
|
|
35
|
+
if (!rn?.AppState)
|
|
36
|
+
return null;
|
|
37
|
+
return (listener) => {
|
|
38
|
+
const subscription = rn.AppState.addEventListener('change', (state) => {
|
|
39
|
+
if (state === 'active')
|
|
40
|
+
listener();
|
|
41
|
+
});
|
|
42
|
+
return () => subscription?.remove();
|
|
43
|
+
};
|
|
44
|
+
},
|
|
45
|
+
});
|
|
25
46
|
api;
|
|
26
47
|
last = signal(null, /* @ts-ignore */
|
|
27
48
|
...(ngDevMode ? [{ debugName: "last" }] : /* istanbul ignore next */ []));
|
|
49
|
+
asked = 0;
|
|
50
|
+
answered = 0;
|
|
51
|
+
/**
|
|
52
|
+
* Built in an injection context, as a field of a service or a component, it checks again each
|
|
53
|
+
* time the app comes back to the front while its answer is a no, and stops when that injector is
|
|
54
|
+
* destroyed. Built outside one, it only learns what `check()`, `request()` and `ensure()` tell it.
|
|
55
|
+
*/
|
|
28
56
|
constructor(api) {
|
|
29
57
|
this.api = api;
|
|
58
|
+
const context = foregroundContext();
|
|
59
|
+
if (!context)
|
|
60
|
+
return;
|
|
61
|
+
const stop = context.events?.(() => this.recheckRefusal());
|
|
62
|
+
if (stop)
|
|
63
|
+
context.destroy.onDestroy(stop);
|
|
30
64
|
}
|
|
31
65
|
/** `unknown` until something has asked the platform, which is not the same as `undetermined`. */
|
|
32
66
|
status = computed(() => this.last()?.status ?? 'unknown', /* @ts-ignore */
|
|
@@ -45,21 +79,22 @@ export class Permission {
|
|
|
45
79
|
}
|
|
46
80
|
/** Ask the platform what it currently thinks, without showing anything to the user. */
|
|
47
81
|
async check() {
|
|
48
|
-
return this.
|
|
82
|
+
return this.ask(() => this.api.get());
|
|
49
83
|
}
|
|
50
84
|
/** Show the dialog. Resolves to what the user said. */
|
|
51
85
|
async request() {
|
|
52
|
-
return this.
|
|
86
|
+
return this.ask(() => this.api.request());
|
|
53
87
|
}
|
|
54
88
|
/**
|
|
55
89
|
* Have the permission if it can be had, asking only if asking would do something.
|
|
56
90
|
*
|
|
57
91
|
* The method most call sites want. A permission already granted needs no dialog, and one the
|
|
58
92
|
* platform has stopped asking about would return the same no while looking like the user had
|
|
59
|
-
* been consulted.
|
|
93
|
+
* been consulted. A no is checked again first, because the person can have changed it in
|
|
94
|
+
* Settings since, and checking shows nothing.
|
|
60
95
|
*/
|
|
61
96
|
async ensure() {
|
|
62
|
-
if (this.last()
|
|
97
|
+
if (!this.last()?.granted)
|
|
63
98
|
await this.check();
|
|
64
99
|
const current = this.last();
|
|
65
100
|
if (current?.granted)
|
|
@@ -70,9 +105,30 @@ export class Permission {
|
|
|
70
105
|
return false;
|
|
71
106
|
return this.request();
|
|
72
107
|
}
|
|
73
|
-
|
|
74
|
-
|
|
108
|
+
/** An answer to a call made before the one last answered is older, and is not recorded. */
|
|
109
|
+
async ask(call) {
|
|
110
|
+
const turn = ++this.asked;
|
|
111
|
+
const response = await call();
|
|
112
|
+
if (turn > this.answered) {
|
|
113
|
+
this.answered = turn;
|
|
114
|
+
this.last.set(response);
|
|
115
|
+
}
|
|
75
116
|
return response.granted;
|
|
76
117
|
}
|
|
118
|
+
/** A failed check leaves the last answer as it was, as a check that never ran would. */
|
|
119
|
+
recheckRefusal() {
|
|
120
|
+
const response = this.last();
|
|
121
|
+
if (response && !response.granted)
|
|
122
|
+
this.check().catch(() => { });
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/** The foreground events and the injector's end, or null outside an injection context. */
|
|
126
|
+
function foregroundContext() {
|
|
127
|
+
try {
|
|
128
|
+
return { events: inject(Permission.FOREGROUND), destroy: inject(DestroyRef) };
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
77
133
|
}
|
|
78
134
|
//# sourceMappingURL=permissions.js.map
|
package/dist/permissions.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"permissions.js","sourceRoot":"","sources":["../src/permissions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAe,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"permissions.js","sourceRoot":"","sources":["../src/permissions.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * A permission, as signals rather than a hook.\n *\n * Every Expo module that needs one exposes the same shape: `getXPermissionsAsync`,\n * `requestXPermissionsAsync`, and a `useXPermissions()` hook that is those two plus React state.\n * So this is not a facade per module - it is one class that takes the pair, and the module stays\n * the app's own dependency:\n *\n * ```ts\n * import * as Camera from 'expo-camera';\n * import { Permission } from '@ng-native/expo';\n *\n * readonly camera = Permission.of(Camera.Camera.getCameraPermissionsAsync,\n * Camera.Camera.requestCameraPermissionsAsync);\n * ```\n */\nimport { DestroyRef, InjectionToken, computed, inject, signal, type Signal } from '@angular/core';\nimport { optional } from './native.ts';\n\n/** What every Expo permission call answers with. */\nexport interface PermissionResponse {\n readonly status: 'granted' | 'denied' | 'undetermined';\n readonly granted: boolean;\n /** False once the platform will no longer show a dialog: the app must send them to Settings. */\n readonly canAskAgain: boolean;\n}\n\n/** The pair of functions a module exposes, which its hook is built out of. */\nexport interface PermissionApi {\n get(): Promise<PermissionResponse>;\n request(): Promise<PermissionResponse>;\n}\n\n/** What a permission is without its module: refused, and never to be asked about again. */\nexport const UNAVAILABLE: PermissionResponse = {\n status: 'denied',\n granted: false,\n canAskAgain: false,\n};\n\n/** Calls the listener each time the app comes back to the front, and answers how to stop. */\nexport type ForegroundEvents = (listener: () => void) => () => void;\n\nexport class Permission {\n /**\n * Overridden in a test to bring the app back to the front without a device.\n *\n * Turning a permission on means going to Settings, so coming back is when an answer can have\n * changed. Neither platform restarts the app for that.\n */\n static readonly FOREGROUND = new InjectionToken<ForegroundEvents | null>(\n 'angular-native.permissionForeground',\n {\n factory: () => {\n const rn = optional(() => require('react-native') as typeof import('react-native'));\n if (!rn?.AppState) return null;\n return (listener) => {\n const subscription = rn.AppState.addEventListener('change', (state) => {\n if (state === 'active') listener();\n });\n return () => subscription?.remove();\n };\n },\n },\n );\n\n private readonly api: PermissionApi;\n private readonly last = signal<PermissionResponse | null>(null);\n private asked = 0;\n private answered = 0;\n\n /**\n * Built in an injection context, as a field of a service or a component, it checks again each\n * time the app comes back to the front while its answer is a no, and stops when that injector is\n * destroyed. Built outside one, it only learns what `check()`, `request()` and `ensure()` tell it.\n */\n constructor(api: PermissionApi) {\n this.api = api;\n const context = foregroundContext();\n if (!context) return;\n const stop = context.events?.(() => this.recheckRefusal());\n if (stop) context.destroy.onDestroy(stop);\n }\n\n /** `unknown` until something has asked the platform, which is not the same as `undetermined`. */\n readonly status: Signal<PermissionResponse['status'] | 'unknown'> = computed(\n () => this.last()?.status ?? 'unknown',\n );\n\n readonly granted: Signal<boolean> = computed(() => this.last()?.granted ?? false);\n\n /** Refused, and the platform will not show a dialog again. Send them to Settings. */\n readonly blocked: Signal<boolean> = computed(() => {\n const response = this.last();\n return response !== null && !response.granted && !response.canAskAgain;\n });\n\n /** From a module's own two functions, which is how every one of them spells this. */\n static of(\n get: () => Promise<PermissionResponse>,\n request: () => Promise<PermissionResponse>,\n ): Permission {\n return new Permission({ get, request });\n }\n\n /** Ask the platform what it currently thinks, without showing anything to the user. */\n async check(): Promise<boolean> {\n return this.ask(() => this.api.get());\n }\n\n /** Show the dialog. Resolves to what the user said. */\n async request(): Promise<boolean> {\n return this.ask(() => this.api.request());\n }\n\n /**\n * Have the permission if it can be had, asking only if asking would do something.\n *\n * The method most call sites want. A permission already granted needs no dialog, and one the\n * platform has stopped asking about would return the same no while looking like the user had\n * been consulted. A no is checked again first, because the person can have changed it in\n * Settings since, and checking shows nothing.\n */\n async ensure(): Promise<boolean> {\n if (!this.last()?.granted) await this.check();\n\n const current = this.last();\n if (current?.granted) return true;\n // Refused for good. Requesting again resolves to the same no without the platform showing\n // anything, which reads at the call site as though the user had been asked twice.\n if (current && !current.canAskAgain) return false;\n return this.request();\n }\n\n /** An answer to a call made before the one last answered is older, and is not recorded. */\n private async ask(call: () => Promise<PermissionResponse>): Promise<boolean> {\n const turn = ++this.asked;\n const response = await call();\n if (turn > this.answered) {\n this.answered = turn;\n this.last.set(response);\n }\n return response.granted;\n }\n\n /** A failed check leaves the last answer as it was, as a check that never ran would. */\n private recheckRefusal(): void {\n const response = this.last();\n if (response && !response.granted) this.check().catch(() => {});\n }\n}\n\n/** The foreground events and the injector's end, or null outside an injection context. */\nfunction foregroundContext(): { events: ForegroundEvents | null; destroy: DestroyRef } | null {\n try {\n return { events: inject(Permission.FOREGROUND), destroy: inject(DestroyRef) };\n } catch {\n return null;\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ng-native/expo",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Expo modules as Angular services for Angular Native, plus registerExpoView for a module's native view.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"angular",
|
|
@@ -206,7 +206,7 @@
|
|
|
206
206
|
"access": "public"
|
|
207
207
|
},
|
|
208
208
|
"dependencies": {
|
|
209
|
-
"@ng-native/fabric": "0.
|
|
209
|
+
"@ng-native/fabric": "0.8.0"
|
|
210
210
|
},
|
|
211
211
|
"nx": {
|
|
212
212
|
"tags": [
|