@ng-native/expo 0.16.0 → 0.17.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.
@@ -19,7 +19,9 @@ export interface NativeFile {
19
19
  overwrite?: boolean;
20
20
  intermediates?: boolean;
21
21
  }): void;
22
+ /** Synchronous up to SDK 57. From SDK 58 it answers a promise, and `writeSync` is synchronous. */
22
23
  write(content: string | Uint8Array): void;
24
+ writeSync?(content: string | Uint8Array): void;
23
25
  text(): Promise<string>;
24
26
  textSync(): string;
25
27
  /** The contents as bytes: a PDF, an image, anything that is not text. */
@@ -61,7 +63,8 @@ export declare class FileSystem {
61
63
  /**
62
64
  * Write text, or bytes for anything that is not text, whether or not the file is there already.
63
65
  * `write` alone throws on a file that has never been created, which is the first thing everyone
64
- * hits. It replaces what was there.
66
+ * hits. It replaces what was there, and has written by the time it returns: from SDK 58 through
67
+ * `writeSync`, since `write` there answers a promise.
65
68
  */
66
69
  write(file: NativeFile, content: string | Uint8Array): void;
67
70
  private installed;
@@ -60,12 +60,16 @@ export class FileSystem {
60
60
  /**
61
61
  * Write text, or bytes for anything that is not text, whether or not the file is there already.
62
62
  * `write` alone throws on a file that has never been created, which is the first thing everyone
63
- * hits. It replaces what was there.
63
+ * hits. It replaces what was there, and has written by the time it returns: from SDK 58 through
64
+ * `writeSync`, since `write` there answers a promise.
64
65
  */
65
66
  write(file, content) {
66
67
  if (!file.exists)
67
68
  file.create({ intermediates: true });
68
- file.write(content);
69
+ if (file.writeSync)
70
+ file.writeSync(content);
71
+ else
72
+ file.write(content);
69
73
  }
70
74
  installed() {
71
75
  if (!this.native) {
@@ -1 +1 @@
1
- {"version":3,"file":"file-system.js","sourceRoot":"","sources":["../src/file-system.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAChE,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;;AAiCtD;;;;;;GAMG;AAEH,MAAM,OAAO,UAAU;IACrB,kEAAkE;IAClE,MAAM,CAAU,MAAM,GAAG,IAAI,cAAc,CACzC,iCAAiC,EACjC;QACE,OAAO,EAAE,GAAG,EAAE;YACZ,MAAM,IAAI,GAAG,UAAU,CACrB,kBAAkB,EAClB,GAAG,EAAE,CAAC,OAAO,CAAC,kBAAkB,CAAsC,CACvE,CAAC;YACF,IAAI,CAAC,IAAI;gBAAE,OAAO,IAAI,CAAC;YACvB,OAAO;gBACL,6EAA6E;gBAC7E,IAAI,cAAc;oBAChB,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC;gBAC1B,CAAC;gBACD,IAAI,iBAAiB;oBACnB,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC;gBAC7B,CAAC;gBACD,IAAI,EAAE,CAAC,SAAS,EAAE,IAAI,EAAE,EAAE,CACxB,IAAI,IAAI,CAAC,IAAI,CAAC,SAAgD,EAAE,IAAI,CAAC;gBACvE,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;aACpC,CAAC;QACJ,CAAC;KACF,CACF,CAAC;IAEe,MAAM,GAAG,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;IAEpD,wEAAwE;IACxE,KAAK,CAAC,IAAY;QAChB,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;QAChC,OAAO,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,cAAc,EAAE,IAAI,CAAC,CAAC;IAClD,CAAC;IAED,wDAAwD;IACxD,QAAQ,CAAC,IAAY;QACnB,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;QAChC,OAAO,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,iBAAiB,EAAE,IAAI,CAAC,CAAC;IACrD,CAAC;IAED;;;OAGG;IACH,IAAI,CAAC,GAAW;QACd,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;QAChC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;YACnB,MAAM,IAAI,KAAK,CACb,4FAA4F,CAC7F,CAAC;QACJ,CAAC;QACD,OAAO,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAC5B,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,IAAgB,EAAE,OAA4B;QAClD,IAAI,CAAC,IAAI,CAAC,MAAM;YAAE,IAAI,CAAC,MAAM,CAAC,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QACvD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACtB,CAAC;IAEO,SAAS;QACf,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;YACjB,MAAM,WAAW,CAAC,kBAAkB,EAAE,4CAA4C,CAAC,CAAC;QACtF,CAAC;QACD,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;uGAtEU,UAAU;wGAAV,UAAU;;2FAAV,UAAU;kBADtB,OAAO","sourcesContent":["/**\n * `FileSystem`, bound to `expo-file-system`.\n *\n * Files located by what they are for rather than by path. `File` and `Paths` are Expo's own\n * classes over a native one, so they are used as published; this only decides which directory a\n * name lands in and hands the file back.\n */\nimport { InjectionToken, Service, inject } from '@angular/core';\nimport { expoModule, unavailable } from './native.ts';\n\n/**\n * A file. Structural on purpose: this is Expo's `File`, described in terms a test can satisfy, so\n * a fake needs no Expo. An app gets the real object back and may use the rest of its surface.\n */\nexport interface NativeFile {\n readonly uri: string;\n readonly exists: boolean;\n readonly size: number;\n create(options?: { overwrite?: boolean; intermediates?: boolean }): void;\n write(content: string | Uint8Array): void;\n text(): Promise<string>;\n textSync(): string;\n /** The contents as bytes: a PDF, an image, anything that is not text. */\n bytes(): Promise<Uint8Array>;\n delete(): void;\n}\n\n/** A directory, as far as this needs to know: something a file can be named inside. */\nexport type NativeDirectory = object;\n\n/** The slice of `expo-file-system` this needs. */\nexport interface NativeFiles {\n /** Cleared by the system under storage pressure. */\n readonly cacheDirectory: NativeDirectory;\n /** Survives, and is backed up. */\n readonly documentDirectory: NativeDirectory;\n file(directory: NativeDirectory, name: string): NativeFile;\n /** The file at a uri, such as one a picker answers with. */\n fileAt?(uri: string): NativeFile;\n}\n\n/**\n * The two directories an app actually chooses between, plus the create-then-write dance that\n * writing a file for the first time needs.\n *\n * ponytail: a locator, not a file API. Everything else - moving, listing, downloading, streaming\n * - is on the object this hands back, documented by Expo, and not worth restating here.\n */\n@Service()\nexport class FileSystem {\n /** Overridden in a test to write files that are not on a disk. */\n static readonly SOURCE = new InjectionToken<NativeFiles | null>(\n 'angular-native.fileSystemSource',\n {\n factory: () => {\n const expo = expoModule(\n 'expo-file-system',\n () => require('expo-file-system') as typeof import('expo-file-system'),\n );\n if (!expo) return null;\n return {\n // Both are getters on Expo's side, handing back a fresh Directory each time.\n get cacheDirectory() {\n return expo.Paths.cache;\n },\n get documentDirectory() {\n return expo.Paths.document;\n },\n file: (directory, name) =>\n new expo.File(directory as InstanceType<typeof expo.Directory>, name),\n fileAt: (uri) => new expo.File(uri),\n };\n },\n },\n );\n\n private readonly native = inject(FileSystem.SOURCE);\n\n /** A file the system may delete when the device runs low on storage. */\n cache(name: string): NativeFile {\n const native = this.installed();\n return native.file(native.cacheDirectory, name);\n }\n\n /** A file that survives, and is included in backups. */\n document(name: string): NativeFile {\n const native = this.installed();\n return native.file(native.documentDirectory, name);\n }\n\n /**\n * The file at a uri: a document the user picked, a photo from the library, a download. What a\n * picker answers with is a uri, and this is the file to read from it.\n */\n file(uri: string): NativeFile {\n const native = this.installed();\n if (!native.fileAt) {\n throw new Error(\n '[angular-native] this FileSystem.SOURCE has no fileAt(uri), which file() opens a uri with.',\n );\n }\n return native.fileAt(uri);\n }\n\n /**\n * Write text, or bytes for anything that is not text, whether or not the file is there already.\n * `write` alone throws on a file that has never been created, which is the first thing everyone\n * hits. It replaces what was there.\n */\n write(file: NativeFile, content: string | Uint8Array): void {\n if (!file.exists) file.create({ intermediates: true });\n file.write(content);\n }\n\n private installed(): NativeFiles {\n if (!this.native) {\n throw unavailable('expo-file-system', 'Provide FileSystem.SOURCE with a stand-in.');\n }\n return this.native;\n }\n}\n"]}
1
+ {"version":3,"file":"file-system.js","sourceRoot":"","sources":["../src/file-system.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAChE,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;;AAmCtD;;;;;;GAMG;AAEH,MAAM,OAAO,UAAU;IACrB,kEAAkE;IAClE,MAAM,CAAU,MAAM,GAAG,IAAI,cAAc,CACzC,iCAAiC,EACjC;QACE,OAAO,EAAE,GAAG,EAAE;YACZ,MAAM,IAAI,GAAG,UAAU,CACrB,kBAAkB,EAClB,GAAG,EAAE,CAAC,OAAO,CAAC,kBAAkB,CAAsC,CACvE,CAAC;YACF,IAAI,CAAC,IAAI;gBAAE,OAAO,IAAI,CAAC;YACvB,OAAO;gBACL,6EAA6E;gBAC7E,IAAI,cAAc;oBAChB,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC;gBAC1B,CAAC;gBACD,IAAI,iBAAiB;oBACnB,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC;gBAC7B,CAAC;gBACD,IAAI,EAAE,CAAC,SAAS,EAAE,IAAI,EAAE,EAAE,CACxB,IAAI,IAAI,CAAC,IAAI,CAAC,SAAgD,EAAE,IAAI,CAAC;gBACvE,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;aACpC,CAAC;QACJ,CAAC;KACF,CACF,CAAC;IAEe,MAAM,GAAG,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;IAEpD,wEAAwE;IACxE,KAAK,CAAC,IAAY;QAChB,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;QAChC,OAAO,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,cAAc,EAAE,IAAI,CAAC,CAAC;IAClD,CAAC;IAED,wDAAwD;IACxD,QAAQ,CAAC,IAAY;QACnB,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;QAChC,OAAO,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,iBAAiB,EAAE,IAAI,CAAC,CAAC;IACrD,CAAC;IAED;;;OAGG;IACH,IAAI,CAAC,GAAW;QACd,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;QAChC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;YACnB,MAAM,IAAI,KAAK,CACb,4FAA4F,CAC7F,CAAC;QACJ,CAAC;QACD,OAAO,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAC5B,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,IAAgB,EAAE,OAA4B;QAClD,IAAI,CAAC,IAAI,CAAC,MAAM;YAAE,IAAI,CAAC,MAAM,CAAC,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QACvD,IAAI,IAAI,CAAC,SAAS;YAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;;YACvC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC3B,CAAC;IAEO,SAAS;QACf,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;YACjB,MAAM,WAAW,CAAC,kBAAkB,EAAE,4CAA4C,CAAC,CAAC;QACtF,CAAC;QACD,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;uGAxEU,UAAU;wGAAV,UAAU;;2FAAV,UAAU;kBADtB,OAAO","sourcesContent":["/**\n * `FileSystem`, bound to `expo-file-system`.\n *\n * Files located by what they are for rather than by path. `File` and `Paths` are Expo's own\n * classes over a native one, so they are used as published; this only decides which directory a\n * name lands in and hands the file back.\n */\nimport { InjectionToken, Service, inject } from '@angular/core';\nimport { expoModule, unavailable } from './native.ts';\n\n/**\n * A file. Structural on purpose: this is Expo's `File`, described in terms a test can satisfy, so\n * a fake needs no Expo. An app gets the real object back and may use the rest of its surface.\n */\nexport interface NativeFile {\n readonly uri: string;\n readonly exists: boolean;\n readonly size: number;\n create(options?: { overwrite?: boolean; intermediates?: boolean }): void;\n /** Synchronous up to SDK 57. From SDK 58 it answers a promise, and `writeSync` is synchronous. */\n write(content: string | Uint8Array): void;\n writeSync?(content: string | Uint8Array): void;\n text(): Promise<string>;\n textSync(): string;\n /** The contents as bytes: a PDF, an image, anything that is not text. */\n bytes(): Promise<Uint8Array>;\n delete(): void;\n}\n\n/** A directory, as far as this needs to know: something a file can be named inside. */\nexport type NativeDirectory = object;\n\n/** The slice of `expo-file-system` this needs. */\nexport interface NativeFiles {\n /** Cleared by the system under storage pressure. */\n readonly cacheDirectory: NativeDirectory;\n /** Survives, and is backed up. */\n readonly documentDirectory: NativeDirectory;\n file(directory: NativeDirectory, name: string): NativeFile;\n /** The file at a uri, such as one a picker answers with. */\n fileAt?(uri: string): NativeFile;\n}\n\n/**\n * The two directories an app actually chooses between, plus the create-then-write dance that\n * writing a file for the first time needs.\n *\n * ponytail: a locator, not a file API. Everything else - moving, listing, downloading, streaming\n * - is on the object this hands back, documented by Expo, and not worth restating here.\n */\n@Service()\nexport class FileSystem {\n /** Overridden in a test to write files that are not on a disk. */\n static readonly SOURCE = new InjectionToken<NativeFiles | null>(\n 'angular-native.fileSystemSource',\n {\n factory: () => {\n const expo = expoModule(\n 'expo-file-system',\n () => require('expo-file-system') as typeof import('expo-file-system'),\n );\n if (!expo) return null;\n return {\n // Both are getters on Expo's side, handing back a fresh Directory each time.\n get cacheDirectory() {\n return expo.Paths.cache;\n },\n get documentDirectory() {\n return expo.Paths.document;\n },\n file: (directory, name) =>\n new expo.File(directory as InstanceType<typeof expo.Directory>, name),\n fileAt: (uri) => new expo.File(uri),\n };\n },\n },\n );\n\n private readonly native = inject(FileSystem.SOURCE);\n\n /** A file the system may delete when the device runs low on storage. */\n cache(name: string): NativeFile {\n const native = this.installed();\n return native.file(native.cacheDirectory, name);\n }\n\n /** A file that survives, and is included in backups. */\n document(name: string): NativeFile {\n const native = this.installed();\n return native.file(native.documentDirectory, name);\n }\n\n /**\n * The file at a uri: a document the user picked, a photo from the library, a download. What a\n * picker answers with is a uri, and this is the file to read from it.\n */\n file(uri: string): NativeFile {\n const native = this.installed();\n if (!native.fileAt) {\n throw new Error(\n '[angular-native] this FileSystem.SOURCE has no fileAt(uri), which file() opens a uri with.',\n );\n }\n return native.fileAt(uri);\n }\n\n /**\n * Write text, or bytes for anything that is not text, whether or not the file is there already.\n * `write` alone throws on a file that has never been created, which is the first thing everyone\n * hits. It replaces what was there, and has written by the time it returns: from SDK 58 through\n * `writeSync`, since `write` there answers a promise.\n */\n write(file: NativeFile, content: string | Uint8Array): void {\n if (!file.exists) file.create({ intermediates: true });\n if (file.writeSync) file.writeSync(content);\n else file.write(content);\n }\n\n private installed(): NativeFiles {\n if (!this.native) {\n throw unavailable('expo-file-system', 'Provide FileSystem.SOURCE with a stand-in.');\n }\n return this.native;\n }\n}\n"]}
@@ -15,25 +15,29 @@ import * as i0 from "@angular/core";
15
15
  export interface NativeKeepAwake {
16
16
  activate(tag: string): Promise<void>;
17
17
  deactivate(tag: string): Promise<void>;
18
+ /** Calls `listener` when a window comes to the front, to be held on again. */
19
+ onWindowFront?(listener: () => void): () => void;
18
20
  }
19
21
  export declare class KeepAwake {
20
22
  /** Overridden in a test to record a hold without keeping a screen on. */
21
23
  static readonly SOURCE: InjectionToken<NativeKeepAwake | null>;
22
24
  private readonly native;
23
- private readonly held;
25
+ /** Every app's holds: the screen is the runtime's, not an app's. */
26
+ private readonly window;
27
+ /** This app's holds not yet released, which go with it. */
28
+ private readonly own;
24
29
  /** The tags currently holding the screen on, so a debug screen can say which. */
25
30
  readonly holders: Signal<ReadonlySet<string>>;
26
31
  /** Whether anything currently holds the screen on. */
27
32
  readonly active: Signal<boolean>;
33
+ constructor();
28
34
  /**
29
35
  * Hold the screen on. Returns the function that releases it, ready for `DestroyRef.onDestroy`.
30
36
  *
31
37
  * Never a bare `activate()`, because the release is the half that gets forgotten and the half
32
- * that matters.
38
+ * that matters. A hold still there when the app goes is released with it.
33
39
  */
34
40
  hold(tag?: string): () => void;
35
- /** How many holds each tag has that are not yet released. */
36
- private readonly counts;
37
41
  static ɵfac: i0.ɵɵFactoryDeclaration<KeepAwake, never>;
38
42
  static ɵprov: i0.ɵɵInjectableDeclaration<any>;
39
43
  }
@@ -10,7 +10,8 @@
10
10
  *
11
11
  * Tags are how two screens can each hold it without either releasing the other's.
12
12
  */
13
- import { InjectionToken, Service, computed, inject, signal } from '@angular/core';
13
+ import { DestroyRef, InjectionToken, Service, computed, inject, signal, } from '@angular/core';
14
+ import { onWindowFront, sharedBySource } from '@ng-native/device';
14
15
  import { expoModule } from "./native.js";
15
16
  import * as i0 from "@angular/core";
16
17
  const DEFAULT_TAG = 'angular-native';
@@ -21,27 +22,64 @@ export class KeepAwake {
21
22
  const expo = expoModule('expo-keep-awake', () => require('expo-keep-awake'));
22
23
  if (!expo)
23
24
  return null;
24
- return {
25
+ // One per module, so every app the runtime mounts holds the same screen.
26
+ return sharedBySource(expo, () => ({
25
27
  activate: (tag) => expo.activateKeepAwakeAsync(tag),
26
28
  deactivate: (tag) => expo.deactivateKeepAwake(tag),
27
- };
29
+ onWindowFront,
30
+ }));
28
31
  },
29
32
  });
30
33
  native = inject(KeepAwake.SOURCE);
31
- held = signal(new Set(), /* @ts-ignore */
32
- ...(ngDevMode ? [{ debugName: "held" }] : /* istanbul ignore next */ []));
34
+ /** Every app's holds: the screen is the runtime's, not an app's. */
35
+ window = sharedBySource(this.native, () => new KeepAwakeWindow(this.native));
36
+ /** This app's holds not yet released, which go with it. */
37
+ own = new Set();
33
38
  /** The tags currently holding the screen on, so a debug screen can say which. */
34
- holders = this.held.asReadonly();
39
+ holders = this.window.held.asReadonly();
35
40
  /** Whether anything currently holds the screen on. */
36
- active = computed(() => this.held().size > 0, /* @ts-ignore */
41
+ active = computed(() => this.holders().size > 0, /* @ts-ignore */
37
42
  ...(ngDevMode ? [{ debugName: "active" }] : /* istanbul ignore next */ []));
43
+ constructor() {
44
+ inject(DestroyRef).onDestroy(() => {
45
+ for (const release of [...this.own])
46
+ release();
47
+ });
48
+ }
38
49
  /**
39
50
  * Hold the screen on. Returns the function that releases it, ready for `DestroyRef.onDestroy`.
40
51
  *
41
52
  * Never a bare `activate()`, because the release is the half that gets forgotten and the half
42
- * that matters.
53
+ * that matters. A hold still there when the app goes is released with it.
43
54
  */
44
55
  hold(tag = DEFAULT_TAG) {
56
+ const release = this.window.hold(tag);
57
+ const own = () => {
58
+ this.own.delete(own);
59
+ release();
60
+ };
61
+ this.own.add(own);
62
+ return own;
63
+ }
64
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: KeepAwake, deps: [], target: i0.ɵɵFactoryTarget.Service });
65
+ static ɵprov = i0.ɵɵngDeclareService({ minVersion: "22.0.0", version: "22.2.0", ngImport: i0, type: KeepAwake });
66
+ }
67
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: KeepAwake, decorators: [{
68
+ type: Service
69
+ }], ctorParameters: () => [] });
70
+ /** The screen, as every app on one module holds it. */
71
+ class KeepAwakeWindow {
72
+ held = signal(new Set(), /* @ts-ignore */
73
+ ...(ngDevMode ? [{ debugName: "held" }] : /* istanbul ignore next */ []));
74
+ /** How many holds each tag has that are not yet released. */
75
+ counts = new Map();
76
+ native;
77
+ constructor(native) {
78
+ this.native = native;
79
+ // Lives as long as the module, which is the runtime's.
80
+ native?.onWindowFront?.(() => void this.holdAgain());
81
+ }
82
+ hold(tag) {
45
83
  // Counted per tag: two screens of one kind hold the same one, and the native module keeps a
46
84
  // set of tags, so the first to release would otherwise let go for both.
47
85
  const count = this.counts.get(tag) ?? 0;
@@ -69,12 +107,22 @@ export class KeepAwake {
69
107
  void this.native?.deactivate(tag).catch(() => { });
70
108
  };
71
109
  }
72
- /** How many holds each tag has that are not yet released. */
73
- counts = new Map();
74
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: KeepAwake, deps: [], target: i0.ɵɵFactoryTarget.Service });
75
- static ɵprov = i0.ɵɵngDeclareService({ minVersion: "22.0.0", version: "22.2.0", ngImport: i0, type: KeepAwake });
110
+ /**
111
+ * A recreated activity's window starts without the flag, and the native module, which keeps its
112
+ * tags for the runtime, sets it only for the first one. So every tag is let go, which clears it,
113
+ * and taken again once it has gone, which sets it on the window now in front: the module's
114
+ * calls are not promised to run in order. A tag released in between is not taken again.
115
+ */
116
+ async holdAgain() {
117
+ const native = this.native;
118
+ if (!native)
119
+ return;
120
+ const tags = [...this.counts.keys()];
121
+ await Promise.allSettled(tags.map((tag) => native.deactivate(tag)));
122
+ for (const tag of tags) {
123
+ if (this.counts.has(tag))
124
+ void native.activate(tag).catch(() => { });
125
+ }
126
+ }
76
127
  }
77
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: KeepAwake, decorators: [{
78
- type: Service
79
- }] });
80
128
  //# sourceMappingURL=keep-awake.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"keep-awake.js","sourceRoot":"","sources":["../src/keep-awake.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `KeepAwake`, bound to `expo-keep-awake`.\n *\n * ```ts\n * inject(DestroyRef).onDestroy(inject(KeepAwake).hold('recording'));\n * ```\n *\n * Never a bare activate: a screen that holds the display on and never releases it is a phone that\n * never sleeps, which the user experiences as a battery fault and never attributes to the app.\n *\n * Tags are how two screens can each hold it without either releasing the other's.\n */\nimport { InjectionToken, Service, computed, inject, signal, type Signal } from '@angular/core';\nimport { expoModule } from './native.ts';\n\nexport interface NativeKeepAwake {\n activate(tag: string): Promise<void>;\n deactivate(tag: string): Promise<void>;\n}\n\nconst DEFAULT_TAG = 'angular-native';\n\n@Service()\nexport class KeepAwake {\n /** Overridden in a test to record a hold without keeping a screen on. */\n static readonly SOURCE = new InjectionToken<NativeKeepAwake | null>(\n 'angular-native.keepAwakeSource',\n {\n factory: () => {\n const expo = expoModule(\n 'expo-keep-awake',\n () => require('expo-keep-awake') as typeof import('expo-keep-awake'),\n );\n if (!expo) return null;\n return {\n activate: (tag) => expo.activateKeepAwakeAsync(tag),\n deactivate: (tag) => expo.deactivateKeepAwake(tag),\n };\n },\n },\n );\n\n private readonly native = inject(KeepAwake.SOURCE);\n private readonly held = signal<ReadonlySet<string>>(new Set());\n\n /** The tags currently holding the screen on, so a debug screen can say which. */\n readonly holders: Signal<ReadonlySet<string>> = this.held.asReadonly();\n\n /** Whether anything currently holds the screen on. */\n readonly active: Signal<boolean> = computed(() => this.held().size > 0);\n\n /**\n * Hold the screen on. Returns the function that releases it, ready for `DestroyRef.onDestroy`.\n *\n * Never a bare `activate()`, because the release is the half that gets forgotten and the half\n * that matters.\n */\n hold(tag = DEFAULT_TAG): () => void {\n // Counted per tag: two screens of one kind hold the same one, and the native module keeps a\n // set of tags, so the first to release would otherwise let go for both.\n const count = this.counts.get(tag) ?? 0;\n this.counts.set(tag, count + 1);\n if (count === 0) {\n this.held.update((tags) => new Set(tags).add(tag));\n void this.native?.activate(tag).catch(() => {});\n }\n\n let released = false;\n return () => {\n if (released) return;\n released = true;\n const left = (this.counts.get(tag) ?? 1) - 1;\n if (left > 0) {\n this.counts.set(tag, left);\n return;\n }\n this.counts.delete(tag);\n this.held.update((tags) => {\n const next = new Set(tags);\n next.delete(tag);\n return next;\n });\n void this.native?.deactivate(tag).catch(() => {});\n };\n }\n\n /** How many holds each tag has that are not yet released. */\n private readonly counts = new Map<string, number>();\n}\n"]}
1
+ {"version":3,"file":"keep-awake.js","sourceRoot":"","sources":["../src/keep-awake.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EACL,UAAU,EACV,cAAc,EACd,OAAO,EACP,QAAQ,EACR,MAAM,EACN,MAAM,GAEP,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAClE,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;;AASzC,MAAM,WAAW,GAAG,gBAAgB,CAAC;AAGrC,MAAM,OAAO,SAAS;IACpB,yEAAyE;IACzE,MAAM,CAAU,MAAM,GAAG,IAAI,cAAc,CACzC,gCAAgC,EAChC;QACE,OAAO,EAAE,GAAG,EAAE;YACZ,MAAM,IAAI,GAAG,UAAU,CACrB,iBAAiB,EACjB,GAAG,EAAE,CAAC,OAAO,CAAC,iBAAiB,CAAqC,CACrE,CAAC;YACF,IAAI,CAAC,IAAI;gBAAE,OAAO,IAAI,CAAC;YACvB,yEAAyE;YACzE,OAAO,cAAc,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;gBACjC,QAAQ,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,sBAAsB,CAAC,GAAG,CAAC;gBACnD,UAAU,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,mBAAmB,CAAC,GAAG,CAAC;gBAClD,aAAa;aACd,CAAC,CAAC,CAAC;QACN,CAAC;KACF,CACF,CAAC;IAEe,MAAM,GAAG,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IACnD,oEAAoE;IACnD,MAAM,GAAG,cAAc,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,IAAI,eAAe,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;IAC9F,2DAA2D;IAC1C,GAAG,GAAG,IAAI,GAAG,EAAc,CAAC;IAE7C,iFAAiF;IACxE,OAAO,GAAgC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC;IAE9E,sDAAsD;IAC7C,MAAM,GAAoB,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,IAAI,GAAG,CAAC;+EAAC,CAAC;IAE3E;QACE,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,GAAG,EAAE;YAChC,KAAK,MAAM,OAAO,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC;gBAAE,OAAO,EAAE,CAAC;QACjD,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;;;OAKG;IACH,IAAI,CAAC,GAAG,GAAG,WAAW;QACpB,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACtC,MAAM,GAAG,GAAG,GAAG,EAAE;YACf,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACrB,OAAO,EAAE,CAAC;QACZ,CAAC,CAAC;QACF,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAClB,OAAO,GAAG,CAAC;IACb,CAAC;uGArDU,SAAS;wGAAT,SAAS;;2FAAT,SAAS;kBADrB,OAAO;;AAyDR,uDAAuD;AACvD,MAAM,eAAe;IACV,IAAI,GAAG,MAAM,CAAsB,IAAI,GAAG,EAAE;6EAAC,CAAC;IACvD,6DAA6D;IAC5C,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;IACnC,MAAM,CAAyB;IAEhD,YAAY,MAA8B;QACxC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,uDAAuD;QACvD,MAAM,EAAE,aAAa,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC;IACvD,CAAC;IAED,IAAI,CAAC,GAAW;QACd,4FAA4F;QAC5F,wEAAwE;QACxE,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACxC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC;QAChC,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC;YAChB,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;YACnD,KAAK,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QAClD,CAAC;QAED,IAAI,QAAQ,GAAG,KAAK,CAAC;QACrB,OAAO,GAAG,EAAE;YACV,IAAI,QAAQ;gBAAE,OAAO;YACrB,QAAQ,GAAG,IAAI,CAAC;YAChB,MAAM,IAAI,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;YAC7C,IAAI,IAAI,GAAG,CAAC,EAAE,CAAC;gBACb,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;gBAC3B,OAAO;YACT,CAAC;YACD,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACxB,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE;gBACxB,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC;gBAC3B,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;gBACjB,OAAO,IAAI,CAAC;YACd,CAAC,CAAC,CAAC;YACH,KAAK,IAAI,CAAC,MAAM,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QACpD,CAAC,CAAC;IACJ,CAAC;IAED;;;;;OAKG;IACK,KAAK,CAAC,SAAS;QACrB,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;QAC3B,IAAI,CAAC,MAAM;YAAE,OAAO;QACpB,MAAM,IAAI,GAAG,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;QACrC,MAAM,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QACpE,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACvB,IAAI,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC;gBAAE,KAAK,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QACtE,CAAC;IACH,CAAC;CACF","sourcesContent":["/**\n * `KeepAwake`, bound to `expo-keep-awake`.\n *\n * ```ts\n * inject(DestroyRef).onDestroy(inject(KeepAwake).hold('recording'));\n * ```\n *\n * Never a bare activate: a screen that holds the display on and never releases it is a phone that\n * never sleeps, which the user experiences as a battery fault and never attributes to the app.\n *\n * Tags are how two screens can each hold it without either releasing the other's.\n */\nimport {\n DestroyRef,\n InjectionToken,\n Service,\n computed,\n inject,\n signal,\n type Signal,\n} from '@angular/core';\nimport { onWindowFront, sharedBySource } from '@ng-native/device';\nimport { expoModule } from './native.ts';\n\nexport interface NativeKeepAwake {\n activate(tag: string): Promise<void>;\n deactivate(tag: string): Promise<void>;\n /** Calls `listener` when a window comes to the front, to be held on again. */\n onWindowFront?(listener: () => void): () => void;\n}\n\nconst DEFAULT_TAG = 'angular-native';\n\n@Service()\nexport class KeepAwake {\n /** Overridden in a test to record a hold without keeping a screen on. */\n static readonly SOURCE = new InjectionToken<NativeKeepAwake | null>(\n 'angular-native.keepAwakeSource',\n {\n factory: () => {\n const expo = expoModule(\n 'expo-keep-awake',\n () => require('expo-keep-awake') as typeof import('expo-keep-awake'),\n );\n if (!expo) return null;\n // One per module, so every app the runtime mounts holds the same screen.\n return sharedBySource(expo, () => ({\n activate: (tag) => expo.activateKeepAwakeAsync(tag),\n deactivate: (tag) => expo.deactivateKeepAwake(tag),\n onWindowFront,\n }));\n },\n },\n );\n\n private readonly native = inject(KeepAwake.SOURCE);\n /** Every app's holds: the screen is the runtime's, not an app's. */\n private readonly window = sharedBySource(this.native, () => new KeepAwakeWindow(this.native));\n /** This app's holds not yet released, which go with it. */\n private readonly own = new Set<() => void>();\n\n /** The tags currently holding the screen on, so a debug screen can say which. */\n readonly holders: Signal<ReadonlySet<string>> = this.window.held.asReadonly();\n\n /** Whether anything currently holds the screen on. */\n readonly active: Signal<boolean> = computed(() => this.holders().size > 0);\n\n constructor() {\n inject(DestroyRef).onDestroy(() => {\n for (const release of [...this.own]) release();\n });\n }\n\n /**\n * Hold the screen on. Returns the function that releases it, ready for `DestroyRef.onDestroy`.\n *\n * Never a bare `activate()`, because the release is the half that gets forgotten and the half\n * that matters. A hold still there when the app goes is released with it.\n */\n hold(tag = DEFAULT_TAG): () => void {\n const release = this.window.hold(tag);\n const own = () => {\n this.own.delete(own);\n release();\n };\n this.own.add(own);\n return own;\n }\n}\n\n/** The screen, as every app on one module holds it. */\nclass KeepAwakeWindow {\n readonly held = signal<ReadonlySet<string>>(new Set());\n /** How many holds each tag has that are not yet released. */\n private readonly counts = new Map<string, number>();\n private readonly native: NativeKeepAwake | null;\n\n constructor(native: NativeKeepAwake | null) {\n this.native = native;\n // Lives as long as the module, which is the runtime's.\n native?.onWindowFront?.(() => void this.holdAgain());\n }\n\n hold(tag: string): () => void {\n // Counted per tag: two screens of one kind hold the same one, and the native module keeps a\n // set of tags, so the first to release would otherwise let go for both.\n const count = this.counts.get(tag) ?? 0;\n this.counts.set(tag, count + 1);\n if (count === 0) {\n this.held.update((tags) => new Set(tags).add(tag));\n void this.native?.activate(tag).catch(() => {});\n }\n\n let released = false;\n return () => {\n if (released) return;\n released = true;\n const left = (this.counts.get(tag) ?? 1) - 1;\n if (left > 0) {\n this.counts.set(tag, left);\n return;\n }\n this.counts.delete(tag);\n this.held.update((tags) => {\n const next = new Set(tags);\n next.delete(tag);\n return next;\n });\n void this.native?.deactivate(tag).catch(() => {});\n };\n }\n\n /**\n * A recreated activity's window starts without the flag, and the native module, which keeps its\n * tags for the runtime, sets it only for the first one. So every tag is let go, which clears it,\n * and taken again once it has gone, which sets it on the window now in front: the module's\n * calls are not promised to run in order. A tag released in between is not taken again.\n */\n private async holdAgain(): Promise<void> {\n const native = this.native;\n if (!native) return;\n const tags = [...this.counts.keys()];\n await Promise.allSettled(tags.map((tag) => native.deactivate(tag)));\n for (const tag of tags) {\n if (this.counts.has(tag)) void native.activate(tag).catch(() => {});\n }\n }\n}\n"]}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * `provideNativeKeyboard()`: the keyboard's motion on Android, from this package's native half.
3
+ *
4
+ * React Native on Android reports the keyboard once the window's insets have changed, as it starts
5
+ * to move, with no duration: a `<keyboard-avoiding-view>` jumps to where the keyboard is going and a
6
+ * `<keyboard-dock>` jumps onto it, while the keyboard slides up underneath. iOS reports the motion
7
+ * as it starts, with its duration and curve, and the same layouts move with the keyboard there.
8
+ *
9
+ * With this, Android does too. The `Keyboard` service hears of the motion as it starts, with
10
+ * Android's duration, and its `position` follows every frame of it, which a
11
+ * `<keyboard-avoiding-view>` moves with, since Android animates no layout alongside the keyboard by
12
+ * default; and a `<keyboard-dock>` follows it frame by frame on the native side, as one does on iOS
13
+ * with react-native-keyboard-controller. Off
14
+ * Android, and in a build without this package's native half, such as Expo Go, it provides nothing
15
+ * and React Native's own events are what there is.
16
+ *
17
+ * ```ts
18
+ * bootstrap(App, { providers: [provideNativeKeyboard()] });
19
+ * ```
20
+ */
21
+ import { type Provider } from '@angular/core';
22
+ import { type KeyboardSource } from '@ng-native/device';
23
+ /** What the native module sends as the keyboard starts to move. */
24
+ export interface NativeKeyboardChange {
25
+ /** Points above the system bars; zero for a keyboard going away. */
26
+ readonly height: number;
27
+ /** The keyboard's top edge on the screen, in points. Absent for a keyboard going away. */
28
+ readonly screenY?: number;
29
+ readonly duration: number;
30
+ readonly easing: string;
31
+ }
32
+ /** Where the keyboard is at one frame of its motion, or that the motion is over. */
33
+ export type NativeKeyboardFrame = {
34
+ readonly height: number;
35
+ readonly screenY?: number;
36
+ } | {
37
+ readonly end: true;
38
+ };
39
+ /** This package's native keyboard module. A test provides a stand-in. */
40
+ export interface NativeKeyboard {
41
+ addListener(event: 'onKeyboardChange', listener: (change: NativeKeyboardChange) => void): {
42
+ remove(): void;
43
+ };
44
+ addListener(event: 'onKeyboardMove', listener: (frame: NativeKeyboardFrame) => void): {
45
+ remove(): void;
46
+ };
47
+ }
48
+ /**
49
+ * The keyboard as the native module reports it, as it starts to move and at every frame of the
50
+ * motion, and as React Native's own events report it: they still come, and they are what a window
51
+ * of its own, a modal's, has. They arrive at about the moment the native one does, sometimes just
52
+ * before it, so one waits a moment and is dropped when the native one says the same; reported
53
+ * first, it would hand a layout following the frames the destination before the motion began.
54
+ */
55
+ export declare function nativeKeyboardSource(native: NativeKeyboard, fallback?: KeyboardSource): KeyboardSource;
56
+ /**
57
+ * Follow the keyboard on Android as it moves rather than once it has: see this file. Nothing to
58
+ * install beyond this package; a build made before it had this native module, and Expo Go, keep
59
+ * React Native's own events.
60
+ */
61
+ export declare function provideNativeKeyboard(): Provider[];
@@ -0,0 +1,102 @@
1
+ /**
2
+ * `provideNativeKeyboard()`: the keyboard's motion on Android, from this package's native half.
3
+ *
4
+ * React Native on Android reports the keyboard once the window's insets have changed, as it starts
5
+ * to move, with no duration: a `<keyboard-avoiding-view>` jumps to where the keyboard is going and a
6
+ * `<keyboard-dock>` jumps onto it, while the keyboard slides up underneath. iOS reports the motion
7
+ * as it starts, with its duration and curve, and the same layouts move with the keyboard there.
8
+ *
9
+ * With this, Android does too. The `Keyboard` service hears of the motion as it starts, with
10
+ * Android's duration, and its `position` follows every frame of it, which a
11
+ * `<keyboard-avoiding-view>` moves with, since Android animates no layout alongside the keyboard by
12
+ * default; and a `<keyboard-dock>` follows it frame by frame on the native side, as one does on iOS
13
+ * with react-native-keyboard-controller. Off
14
+ * Android, and in a build without this package's native half, such as Expo Go, it provides nothing
15
+ * and React Native's own events are what there is.
16
+ *
17
+ * ```ts
18
+ * bootstrap(App, { providers: [provideNativeKeyboard()] });
19
+ * ```
20
+ */
21
+ import {} from '@angular/core';
22
+ import { KEYBOARD_MOTION, Keyboard, keyboardSource, } from '@ng-native/device';
23
+ import { currentPlatform } from "./native.js";
24
+ import { expoViewName } from "./register-expo-view.js";
25
+ const MODULE = 'NgNativeKeyboard';
26
+ /** How long React Native's own event waits for the native one that says the same, in ms. */
27
+ const OVERTAKEN_MS = 100;
28
+ /** Heights closer than this, in points, are one: the two report it through different roundings. */
29
+ const SAME_HEIGHT = 0.5;
30
+ /** The module where the build has it, on Android. */
31
+ function nativeKeyboard() {
32
+ if (currentPlatform() !== 'android')
33
+ return null;
34
+ return (globalThis.expo
35
+ ?.modules?.[MODULE] ?? null);
36
+ }
37
+ /** A native height or edge as the metrics carry it: only the fields that are there. */
38
+ const metricsOf = (at) => ({
39
+ height: at.height,
40
+ ...(at.screenY !== undefined && { screenY: at.screenY }),
41
+ });
42
+ /**
43
+ * The keyboard as the native module reports it, as it starts to move and at every frame of the
44
+ * motion, and as React Native's own events report it: they still come, and they are what a window
45
+ * of its own, a modal's, has. They arrive at about the moment the native one does, sometimes just
46
+ * before it, so one waits a moment and is dropped when the native one says the same; reported
47
+ * first, it would hand a layout following the frames the destination before the motion began.
48
+ */
49
+ export function nativeKeyboardSource(native, fallback = keyboardSource()) {
50
+ return {
51
+ subscribe: (listener) => {
52
+ let last = null;
53
+ let waiting;
54
+ const report = (metrics) => {
55
+ last = metrics.height;
56
+ listener(metrics);
57
+ };
58
+ const subscription = native.addListener('onKeyboardChange', (change) => {
59
+ clearTimeout(waiting);
60
+ report({ ...metricsOf(change), duration: change.duration, easing: change.easing });
61
+ });
62
+ const stop = fallback.subscribe((metrics) => {
63
+ clearTimeout(waiting);
64
+ waiting = setTimeout(() => {
65
+ if (last === null || Math.abs(metrics.height - last) >= SAME_HEIGHT)
66
+ report(metrics);
67
+ }, OVERTAKEN_MS);
68
+ });
69
+ return () => {
70
+ clearTimeout(waiting);
71
+ subscription.remove();
72
+ stop();
73
+ };
74
+ },
75
+ subscribeMotion: (listener) => {
76
+ const subscription = native.addListener('onKeyboardMove', (frame) => listener('end' in frame ? null : metricsOf(frame)));
77
+ return () => subscription.remove();
78
+ },
79
+ dismiss: () => fallback.dismiss(),
80
+ };
81
+ }
82
+ /**
83
+ * Follow the keyboard on Android as it moves rather than once it has: see this file. Nothing to
84
+ * install beyond this package; a build made before it had this native module, and Expo Go, keep
85
+ * React Native's own events.
86
+ */
87
+ export function provideNativeKeyboard() {
88
+ return [
89
+ {
90
+ provide: Keyboard.SOURCE,
91
+ useFactory: () => {
92
+ const native = nativeKeyboard();
93
+ return native ? nativeKeyboardSource(native) : keyboardSource();
94
+ },
95
+ },
96
+ {
97
+ provide: KEYBOARD_MOTION,
98
+ useFactory: () => (nativeKeyboard() ? { viewName: expoViewName(MODULE) } : null),
99
+ },
100
+ ];
101
+ }
102
+ //# sourceMappingURL=keyboard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keyboard.js","sourceRoot":"","sources":["../src/keyboard.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `provideNativeKeyboard()`: the keyboard's motion on Android, from this package's native half.\n *\n * React Native on Android reports the keyboard once the window's insets have changed, as it starts\n * to move, with no duration: a `<keyboard-avoiding-view>` jumps to where the keyboard is going and a\n * `<keyboard-dock>` jumps onto it, while the keyboard slides up underneath. iOS reports the motion\n * as it starts, with its duration and curve, and the same layouts move with the keyboard there.\n *\n * With this, Android does too. The `Keyboard` service hears of the motion as it starts, with\n * Android's duration, and its `position` follows every frame of it, which a\n * `<keyboard-avoiding-view>` moves with, since Android animates no layout alongside the keyboard by\n * default; and a `<keyboard-dock>` follows it frame by frame on the native side, as one does on iOS\n * with react-native-keyboard-controller. Off\n * Android, and in a build without this package's native half, such as Expo Go, it provides nothing\n * and React Native's own events are what there is.\n *\n * ```ts\n * bootstrap(App, { providers: [provideNativeKeyboard()] });\n * ```\n */\nimport { type Provider } from '@angular/core';\nimport {\n KEYBOARD_MOTION,\n Keyboard,\n keyboardSource,\n type KeyboardMetrics,\n type KeyboardSource,\n} from '@ng-native/device';\nimport { currentPlatform } from './native.ts';\nimport { expoViewName } from './register-expo-view.ts';\n\nconst MODULE = 'NgNativeKeyboard';\n\n/** What the native module sends as the keyboard starts to move. */\nexport interface NativeKeyboardChange {\n /** Points above the system bars; zero for a keyboard going away. */\n readonly height: number;\n /** The keyboard's top edge on the screen, in points. Absent for a keyboard going away. */\n readonly screenY?: number;\n readonly duration: number;\n readonly easing: string;\n}\n\n/** Where the keyboard is at one frame of its motion, or that the motion is over. */\nexport type NativeKeyboardFrame =\n { readonly height: number; readonly screenY?: number } | { readonly end: true };\n\n/** This package's native keyboard module. A test provides a stand-in. */\nexport interface NativeKeyboard {\n addListener(\n event: 'onKeyboardChange',\n listener: (change: NativeKeyboardChange) => void,\n ): { remove(): void };\n addListener(\n event: 'onKeyboardMove',\n listener: (frame: NativeKeyboardFrame) => void,\n ): { remove(): void };\n}\n\n/** How long React Native's own event waits for the native one that says the same, in ms. */\nconst OVERTAKEN_MS = 100;\n\n/** Heights closer than this, in points, are one: the two report it through different roundings. */\nconst SAME_HEIGHT = 0.5;\n\n/** The module where the build has it, on Android. */\nfunction nativeKeyboard(): NativeKeyboard | null {\n if (currentPlatform() !== 'android') return null;\n return (\n (globalThis as { expo?: { modules?: Record<string, NativeKeyboard | undefined> } }).expo\n ?.modules?.[MODULE] ?? null\n );\n}\n\n/** A native height or edge as the metrics carry it: only the fields that are there. */\nconst metricsOf = (at: {\n readonly height: number;\n readonly screenY?: number;\n}): KeyboardMetrics => ({\n height: at.height,\n ...(at.screenY !== undefined && { screenY: at.screenY }),\n});\n\n/**\n * The keyboard as the native module reports it, as it starts to move and at every frame of the\n * motion, and as React Native's own events report it: they still come, and they are what a window\n * of its own, a modal's, has. They arrive at about the moment the native one does, sometimes just\n * before it, so one waits a moment and is dropped when the native one says the same; reported\n * first, it would hand a layout following the frames the destination before the motion began.\n */\nexport function nativeKeyboardSource(\n native: NativeKeyboard,\n fallback: KeyboardSource = keyboardSource(),\n): KeyboardSource {\n return {\n subscribe: (listener) => {\n let last: number | null = null;\n let waiting: ReturnType<typeof setTimeout> | undefined;\n const report = (metrics: KeyboardMetrics) => {\n last = metrics.height;\n listener(metrics);\n };\n const subscription = native.addListener('onKeyboardChange', (change) => {\n clearTimeout(waiting);\n report({ ...metricsOf(change), duration: change.duration, easing: change.easing });\n });\n const stop = fallback.subscribe((metrics) => {\n clearTimeout(waiting);\n waiting = setTimeout(() => {\n if (last === null || Math.abs(metrics.height - last) >= SAME_HEIGHT) report(metrics);\n }, OVERTAKEN_MS);\n });\n return () => {\n clearTimeout(waiting);\n subscription.remove();\n stop();\n };\n },\n subscribeMotion: (listener) => {\n const subscription = native.addListener('onKeyboardMove', (frame) =>\n listener('end' in frame ? null : metricsOf(frame)),\n );\n return () => subscription.remove();\n },\n dismiss: () => fallback.dismiss(),\n };\n}\n\n/**\n * Follow the keyboard on Android as it moves rather than once it has: see this file. Nothing to\n * install beyond this package; a build made before it had this native module, and Expo Go, keep\n * React Native's own events.\n */\nexport function provideNativeKeyboard(): Provider[] {\n return [\n {\n provide: Keyboard.SOURCE,\n useFactory: () => {\n const native = nativeKeyboard();\n return native ? nativeKeyboardSource(native) : keyboardSource();\n },\n },\n {\n provide: KEYBOARD_MOTION,\n useFactory: () => (nativeKeyboard() ? { viewName: expoViewName(MODULE) } : null),\n },\n ];\n}\n"]}
@@ -36,7 +36,6 @@ export interface GenerateOptions {
36
36
  /** System instructions: who the model is and how it answers. Kept apart from the prompt. */
37
37
  readonly instructions?: string;
38
38
  readonly temperature?: number;
39
- /** Capped at 256 on Android. */
40
39
  readonly maxTokens?: number;
41
40
  readonly topK?: number;
42
41
  }
@@ -110,7 +109,7 @@ export declare class LanguageModel {
110
109
  generate(prompt: string, options?: GenerateOptions): Promise<string>;
111
110
  /**
112
111
  * An answer shaped by `schema`, parsed. iOS constrains the model's decoding to the schema, so
113
- * the answer always fits it; Android only asks the model to follow it, so check what comes back.
112
+ * the answer always fits it. Android rejects: `expo-local-llm` does not support a schema there.
114
113
  */
115
114
  generateObject<T = unknown>(prompt: string, schema: LanguageModelSchema, options?: GenerateOptions): Promise<T>;
116
115
  /**
@@ -48,7 +48,7 @@ export class LanguageModel {
48
48
  const llm = expoModule('expo-local-llm', () => {
49
49
  const loaded = require('expo-local-llm');
50
50
  return loaded.ExpoLocalLlmModule ? loaded : null;
51
- }, ['ios']);
51
+ });
52
52
  // Missing from the build: the stand-in, to say so when it is used.
53
53
  if (llm && !present(llm))
54
54
  return llm;
@@ -122,7 +122,7 @@ export class LanguageModel {
122
122
  }
123
123
  /**
124
124
  * An answer shaped by `schema`, parsed. iOS constrains the model's decoding to the schema, so
125
- * the answer always fits it; Android only asks the model to follow it, so check what comes back.
125
+ * the answer always fits it. Android rejects: `expo-local-llm` does not support a schema there.
126
126
  */
127
127
  async generateObject(prompt, schema, options = {}) {
128
128
  const text = await this.once({ ...config(options), responseFormat: 'json', schema }, prompt);
@@ -1 +1 @@
1
- {"version":3,"file":"language-model.js","sourceRoot":"","sources":["../src/language-model.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `LanguageModel`, bound to `expo-local-llm`.\n *\n * The model the operating system already has: Apple's Foundation Models on iOS 26 with Apple\n * Intelligence, Gemini Nano on the Android phones that ship it. Nothing is bundled and nothing is\n * sent anywhere; the prompt and the answer stay on the device.\n *\n * ```ts\n * private readonly model = inject(LanguageModel);\n * protected readonly answer = signal<LanguageModelStream | null>(null);\n * ask(prompt: string) { this.answer.set(this.model.stream(prompt)); }\n * ```\n *\n * Every call is a conversation of its own: a session is made, asked once and released, so one\n * prompt never sees the last one. The module runs one stream at a time, so starting a stream\n * cancels the one before it.\n */\nimport {\n DestroyRef,\n InjectionToken,\n Service,\n computed,\n inject,\n signal,\n type Signal,\n} from '@angular/core';\nimport { expoModule, present } from './native.ts';\n\n/**\n * What `expo-local-llm` reports, plus `notInstalled` for an app without the module or a build\n * without its native code (Expo Go, or installed without a rebuild).\n *\n * - `available`: ready to answer.\n * - `notEligible`: this device or OS version cannot run it (before iOS 26, older iPhones, most\n * Android phones).\n * - `notEnabled`: the device can, but Apple Intelligence is switched off in Settings.\n * - `notReady`: the model is still being prepared by the system, usually downloading after\n * Apple Intelligence was switched on.\n * - `downloadRequired` / `downloading`: Android, where the app asks for Gemini Nano with\n * `download()`.\n * - `unknown`: the platform said something the module does not name.\n */\nexport type LanguageModelAvailability =\n | 'available'\n | 'notEligible'\n | 'notEnabled'\n | 'notReady'\n | 'downloadRequired'\n | 'downloading'\n | 'unknown'\n | 'notInstalled';\n\nconst REPORTED = new Set<string>([\n 'available',\n 'notEligible',\n 'notEnabled',\n 'notReady',\n 'downloadRequired',\n 'downloading',\n 'unknown',\n]);\n\nexport interface GenerateOptions {\n /** System instructions: who the model is and how it answers. Kept apart from the prompt. */\n readonly instructions?: string;\n readonly temperature?: number;\n /** Capped at 256 on Android. */\n readonly maxTokens?: number;\n readonly topK?: number;\n}\n\n/** A structured answer's shape: `expo-local-llm`'s own schema, a field per property. */\nexport type LanguageModelSchema = import('expo-local-llm').Schema;\n\ntype SessionConfig = import('expo-local-llm').SessionConfig;\n\n/** The slice of an `expo-local-llm` session this needs, with its token event unwrapped. */\nexport interface NativeLanguageSession {\n respond(prompt: string): Promise<string>;\n /** Resolves with the whole text, or the text so far when cancelled; rejects on failure. */\n stream(prompt: string, onText: (textSoFar: string) => void): Promise<string>;\n cancel(): Promise<void>;\n release(): void;\n}\n\n/** The slice of `expo-local-llm` this needs. */\nexport interface NativeLanguageModel {\n availability(): string;\n onAvailabilityChange(listener: (availability: string) => void): { remove(): void };\n onDownloadProgress(listener: (progress: number) => void): { remove(): void };\n download(): Promise<void>;\n session(config: SessionConfig): NativeLanguageSession;\n}\n\n/** Why a call was refused before it reached the model. */\nexport class LanguageModelUnavailableError extends Error {\n readonly reason: Exclude<LanguageModelAvailability, 'available'>;\n\n constructor(reason: Exclude<LanguageModelAvailability, 'available'>) {\n super(`The on-device language model is unavailable: ${reason}`);\n this.name = 'LanguageModelUnavailableError';\n this.reason = reason;\n }\n}\n\nexport type LanguageModelStreamStatus = 'streaming' | 'done' | 'cancelled' | 'failed';\n\n/** One streamed answer, as signals a template binds. */\nexport interface LanguageModelStream {\n /** The text so far; the whole answer once `status` is `done`. */\n readonly text: Signal<string>;\n readonly status: Signal<LanguageModelStreamStatus>;\n /** The failure's message while `status` is `failed`, otherwise null. */\n readonly error: Signal<string | null>;\n /** The final text (the partial text if cancelled). Rejects if the stream fails. */\n readonly result: Promise<string>;\n /** Stop generating. The text so far is kept. Does nothing once the stream has ended. */\n cancel(): void;\n}\n\n/**\n * The availability and download listeners follow the model for the life of the app, and are\n * removed when the app is destroyed.\n */\n@Service()\nexport class LanguageModel {\n /** Overridden in a test to answer without a model. */\n static readonly SOURCE = new InjectionToken<NativeLanguageModel | null>(\n 'angular-native.languageModelSource',\n {\n factory: () => {\n // The package loads without its native half, so that half is what decides.\n const llm = expoModule(\n 'expo-local-llm',\n () => {\n const loaded = require('expo-local-llm') as typeof import('expo-local-llm');\n return loaded.ExpoLocalLlmModule ? loaded : null;\n },\n ['ios'],\n );\n // Missing from the build: the stand-in, to say so when it is used.\n if (llm && !present(llm)) return llm as never;\n const module = llm?.ExpoLocalLlmModule;\n if (!llm || !module) return null;\n return {\n availability: () => module.getAvailability(),\n onAvailabilityChange: (listener) =>\n module.addListener('availabilityChange', (event) => listener(event.availability)),\n onDownloadProgress: (listener) =>\n module.addListener('downloadProgress', (event) => listener(event.progress)),\n download: () => module.downloadModel(),\n session: (config) => {\n const session = llm.createLLMSession(config);\n return {\n respond: (prompt) => session.respond(prompt),\n stream: async (prompt, onText) => {\n const tokens = session.addListener('token', (event) => onText(event.accumulated));\n try {\n return await session.streamResponse(prompt);\n } finally {\n tokens.remove();\n }\n },\n cancel: () => session.cancelStream(),\n release: () => session.release(),\n };\n },\n };\n },\n },\n );\n\n private readonly native = inject(LanguageModel.SOURCE);\n private readonly state = signal<LanguageModelAvailability>(this.read());\n private readonly progress = signal<number | null>(null);\n private streaming: LanguageModelStream | null = null;\n\n /** Whether the model can answer, and if not, the platform's reason. */\n readonly availability: Signal<LanguageModelAvailability> = this.state.asReadonly();\n\n readonly available: Signal<boolean> = computed(() => this.state() === 'available');\n\n /** Android's download of Gemini Nano, 0 to 1. Null until a download reports; iOS never does. */\n readonly downloadProgress: Signal<number | null> = this.progress.asReadonly();\n\n constructor() {\n const native = present(this.native);\n if (!native) return;\n const availability = native.onAvailabilityChange((value) => this.state.set(known(value)));\n const progress = native.onDownloadProgress((value) => this.progress.set(value));\n inject(DestroyRef).onDestroy(() => {\n availability.remove();\n progress.remove();\n });\n }\n\n /**\n * Ask the platform again. iOS announces a change when the app comes back to the front, which\n * covers Apple Intelligence being switched on in Settings; it does not announce the model\n * finishing its preparation while the app is open.\n */\n refresh(): LanguageModelAvailability {\n this.state.set(this.read());\n return this.state();\n }\n\n /** Ask Android to fetch Gemini Nano. Does nothing on iOS, where the system manages the model. */\n async download(): Promise<void> {\n await this.native?.download();\n }\n\n /** The whole answer at once. Rejects with the platform's reason when the model is unavailable. */\n async generate(prompt: string, options: GenerateOptions = {}): Promise<string> {\n return this.once(config(options), prompt);\n }\n\n /**\n * An answer shaped by `schema`, parsed. iOS constrains the model's decoding to the schema, so\n * the answer always fits it; Android only asks the model to follow it, so check what comes back.\n */\n async generateObject<T = unknown>(\n prompt: string,\n schema: LanguageModelSchema,\n options: GenerateOptions = {},\n ): Promise<T> {\n const text = await this.once({ ...config(options), responseFormat: 'json', schema }, prompt);\n return JSON.parse(text) as T;\n }\n\n /**\n * The answer as it is written, into a signal. Cancels any stream still running, because the\n * module runs one at a time. Never throws: an unavailable model is a stream that has already\n * failed, with the reason as its error.\n */\n stream(prompt: string, options: GenerateOptions = {}): LanguageModelStream {\n this.streaming?.cancel();\n\n const text = signal('');\n const status = signal<LanguageModelStreamStatus>('streaming');\n const error = signal<string | null>(null);\n let session: NativeLanguageSession | null = null;\n let cancelled = false;\n\n const fail = (failure: unknown): never => {\n status.set('failed');\n error.set(failure instanceof Error ? failure.message : String(failure));\n throw failure;\n };\n\n let result: Promise<string>;\n try {\n this.refuseUnlessAvailable();\n const running = this.native!.session(config(options));\n session = running;\n result = running\n .stream(prompt, (soFar) => text.set(soFar))\n .then((final) => {\n text.set(final);\n status.set(cancelled ? 'cancelled' : 'done');\n return final;\n }, fail)\n .finally(() => running.release());\n } catch (refusal) {\n result = Promise.reject(refusal);\n try {\n fail(refusal);\n } catch {\n // Recorded on the signals at once, so a template shows the reason on its first render.\n }\n }\n // Handled here so a stream whose result nobody awaits is not an unhandled rejection; anyone\n // who does await it still sees the failure.\n result.catch(() => {});\n\n const stream: LanguageModelStream = {\n text: text.asReadonly(),\n status: status.asReadonly(),\n error: error.asReadonly(),\n result,\n cancel: () => {\n if (status() !== 'streaming' || !session) return;\n cancelled = true;\n void session.cancel();\n },\n };\n this.streaming = session ? stream : null;\n return stream;\n }\n\n private async once(sessionConfig: SessionConfig, prompt: string): Promise<string> {\n this.refuseUnlessAvailable();\n const session = this.native!.session(sessionConfig);\n try {\n return await session.respond(prompt);\n } finally {\n session.release();\n }\n }\n\n /** Re-reads rather than trusting the signal, which only moves when the platform announces it. */\n private refuseUnlessAvailable(): void {\n const availability = this.refresh();\n if (availability !== 'available') throw new LanguageModelUnavailableError(availability);\n }\n\n private read(): LanguageModelAvailability {\n const native = present(this.native);\n return native ? known(native.availability()) : 'notInstalled';\n }\n}\n\nfunction known(value: string): LanguageModelAvailability {\n return REPORTED.has(value) ? (value as LanguageModelAvailability) : 'unknown';\n}\n\n/** Only the keys that were given, so the native side sees its own defaults for the rest. */\nfunction config({ instructions, ...sampling }: GenerateOptions): SessionConfig {\n const options = Object.fromEntries(\n Object.entries(sampling).filter(([, value]) => value !== undefined),\n );\n return {\n ...(instructions === undefined ? {} : { instructions }),\n ...(Object.keys(options).length === 0 ? {} : { options }),\n };\n}\n"]}
1
+ {"version":3,"file":"language-model.js","sourceRoot":"","sources":["../src/language-model.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `LanguageModel`, bound to `expo-local-llm`.\n *\n * The model the operating system already has: Apple's Foundation Models on iOS 26 with Apple\n * Intelligence, Gemini Nano on the Android phones that ship it. Nothing is bundled and nothing is\n * sent anywhere; the prompt and the answer stay on the device.\n *\n * ```ts\n * private readonly model = inject(LanguageModel);\n * protected readonly answer = signal<LanguageModelStream | null>(null);\n * ask(prompt: string) { this.answer.set(this.model.stream(prompt)); }\n * ```\n *\n * Every call is a conversation of its own: a session is made, asked once and released, so one\n * prompt never sees the last one. The module runs one stream at a time, so starting a stream\n * cancels the one before it.\n */\nimport {\n DestroyRef,\n InjectionToken,\n Service,\n computed,\n inject,\n signal,\n type Signal,\n} from '@angular/core';\nimport { expoModule, present } from './native.ts';\n\n/**\n * What `expo-local-llm` reports, plus `notInstalled` for an app without the module or a build\n * without its native code (Expo Go, or installed without a rebuild).\n *\n * - `available`: ready to answer.\n * - `notEligible`: this device or OS version cannot run it (before iOS 26, older iPhones, most\n * Android phones).\n * - `notEnabled`: the device can, but Apple Intelligence is switched off in Settings.\n * - `notReady`: the model is still being prepared by the system, usually downloading after\n * Apple Intelligence was switched on.\n * - `downloadRequired` / `downloading`: Android, where the app asks for Gemini Nano with\n * `download()`.\n * - `unknown`: the platform said something the module does not name.\n */\nexport type LanguageModelAvailability =\n | 'available'\n | 'notEligible'\n | 'notEnabled'\n | 'notReady'\n | 'downloadRequired'\n | 'downloading'\n | 'unknown'\n | 'notInstalled';\n\nconst REPORTED = new Set<string>([\n 'available',\n 'notEligible',\n 'notEnabled',\n 'notReady',\n 'downloadRequired',\n 'downloading',\n 'unknown',\n]);\n\nexport interface GenerateOptions {\n /** System instructions: who the model is and how it answers. Kept apart from the prompt. */\n readonly instructions?: string;\n readonly temperature?: number;\n readonly maxTokens?: number;\n readonly topK?: number;\n}\n\n/** A structured answer's shape: `expo-local-llm`'s own schema, a field per property. */\nexport type LanguageModelSchema = import('expo-local-llm').Schema;\n\ntype SessionConfig = import('expo-local-llm').SessionConfig;\n\n/** The slice of an `expo-local-llm` session this needs, with its token event unwrapped. */\nexport interface NativeLanguageSession {\n respond(prompt: string): Promise<string>;\n /** Resolves with the whole text, or the text so far when cancelled; rejects on failure. */\n stream(prompt: string, onText: (textSoFar: string) => void): Promise<string>;\n cancel(): Promise<void>;\n release(): void;\n}\n\n/** The slice of `expo-local-llm` this needs. */\nexport interface NativeLanguageModel {\n availability(): string;\n onAvailabilityChange(listener: (availability: string) => void): { remove(): void };\n onDownloadProgress(listener: (progress: number) => void): { remove(): void };\n download(): Promise<void>;\n session(config: SessionConfig): NativeLanguageSession;\n}\n\n/** Why a call was refused before it reached the model. */\nexport class LanguageModelUnavailableError extends Error {\n readonly reason: Exclude<LanguageModelAvailability, 'available'>;\n\n constructor(reason: Exclude<LanguageModelAvailability, 'available'>) {\n super(`The on-device language model is unavailable: ${reason}`);\n this.name = 'LanguageModelUnavailableError';\n this.reason = reason;\n }\n}\n\nexport type LanguageModelStreamStatus = 'streaming' | 'done' | 'cancelled' | 'failed';\n\n/** One streamed answer, as signals a template binds. */\nexport interface LanguageModelStream {\n /** The text so far; the whole answer once `status` is `done`. */\n readonly text: Signal<string>;\n readonly status: Signal<LanguageModelStreamStatus>;\n /** The failure's message while `status` is `failed`, otherwise null. */\n readonly error: Signal<string | null>;\n /** The final text (the partial text if cancelled). Rejects if the stream fails. */\n readonly result: Promise<string>;\n /** Stop generating. The text so far is kept. Does nothing once the stream has ended. */\n cancel(): void;\n}\n\n/**\n * The availability and download listeners follow the model for the life of the app, and are\n * removed when the app is destroyed.\n */\n@Service()\nexport class LanguageModel {\n /** Overridden in a test to answer without a model. */\n static readonly SOURCE = new InjectionToken<NativeLanguageModel | null>(\n 'angular-native.languageModelSource',\n {\n factory: () => {\n // The package loads without its native half, so that half is what decides.\n const llm = expoModule('expo-local-llm', () => {\n const loaded = require('expo-local-llm') as typeof import('expo-local-llm');\n return loaded.ExpoLocalLlmModule ? loaded : null;\n });\n // Missing from the build: the stand-in, to say so when it is used.\n if (llm && !present(llm)) return llm as never;\n const module = llm?.ExpoLocalLlmModule;\n if (!llm || !module) return null;\n return {\n availability: () => module.getAvailability(),\n onAvailabilityChange: (listener) =>\n module.addListener('availabilityChange', (event) => listener(event.availability)),\n onDownloadProgress: (listener) =>\n module.addListener('downloadProgress', (event) => listener(event.progress)),\n download: () => module.downloadModel(),\n session: (config) => {\n const session = llm.createLLMSession(config);\n return {\n respond: (prompt) => session.respond(prompt),\n stream: async (prompt, onText) => {\n const tokens = session.addListener('token', (event) => onText(event.accumulated));\n try {\n return await session.streamResponse(prompt);\n } finally {\n tokens.remove();\n }\n },\n cancel: () => session.cancelStream(),\n release: () => session.release(),\n };\n },\n };\n },\n },\n );\n\n private readonly native = inject(LanguageModel.SOURCE);\n private readonly state = signal<LanguageModelAvailability>(this.read());\n private readonly progress = signal<number | null>(null);\n private streaming: LanguageModelStream | null = null;\n\n /** Whether the model can answer, and if not, the platform's reason. */\n readonly availability: Signal<LanguageModelAvailability> = this.state.asReadonly();\n\n readonly available: Signal<boolean> = computed(() => this.state() === 'available');\n\n /** Android's download of Gemini Nano, 0 to 1. Null until a download reports; iOS never does. */\n readonly downloadProgress: Signal<number | null> = this.progress.asReadonly();\n\n constructor() {\n const native = present(this.native);\n if (!native) return;\n const availability = native.onAvailabilityChange((value) => this.state.set(known(value)));\n const progress = native.onDownloadProgress((value) => this.progress.set(value));\n inject(DestroyRef).onDestroy(() => {\n availability.remove();\n progress.remove();\n });\n }\n\n /**\n * Ask the platform again. iOS announces a change when the app comes back to the front, which\n * covers Apple Intelligence being switched on in Settings; it does not announce the model\n * finishing its preparation while the app is open.\n */\n refresh(): LanguageModelAvailability {\n this.state.set(this.read());\n return this.state();\n }\n\n /** Ask Android to fetch Gemini Nano. Does nothing on iOS, where the system manages the model. */\n async download(): Promise<void> {\n await this.native?.download();\n }\n\n /** The whole answer at once. Rejects with the platform's reason when the model is unavailable. */\n async generate(prompt: string, options: GenerateOptions = {}): Promise<string> {\n return this.once(config(options), prompt);\n }\n\n /**\n * An answer shaped by `schema`, parsed. iOS constrains the model's decoding to the schema, so\n * the answer always fits it. Android rejects: `expo-local-llm` does not support a schema there.\n */\n async generateObject<T = unknown>(\n prompt: string,\n schema: LanguageModelSchema,\n options: GenerateOptions = {},\n ): Promise<T> {\n const text = await this.once({ ...config(options), responseFormat: 'json', schema }, prompt);\n return JSON.parse(text) as T;\n }\n\n /**\n * The answer as it is written, into a signal. Cancels any stream still running, because the\n * module runs one at a time. Never throws: an unavailable model is a stream that has already\n * failed, with the reason as its error.\n */\n stream(prompt: string, options: GenerateOptions = {}): LanguageModelStream {\n this.streaming?.cancel();\n\n const text = signal('');\n const status = signal<LanguageModelStreamStatus>('streaming');\n const error = signal<string | null>(null);\n let session: NativeLanguageSession | null = null;\n let cancelled = false;\n\n const fail = (failure: unknown): never => {\n status.set('failed');\n error.set(failure instanceof Error ? failure.message : String(failure));\n throw failure;\n };\n\n let result: Promise<string>;\n try {\n this.refuseUnlessAvailable();\n const running = this.native!.session(config(options));\n session = running;\n result = running\n .stream(prompt, (soFar) => text.set(soFar))\n .then((final) => {\n text.set(final);\n status.set(cancelled ? 'cancelled' : 'done');\n return final;\n }, fail)\n .finally(() => running.release());\n } catch (refusal) {\n result = Promise.reject(refusal);\n try {\n fail(refusal);\n } catch {\n // Recorded on the signals at once, so a template shows the reason on its first render.\n }\n }\n // Handled here so a stream whose result nobody awaits is not an unhandled rejection; anyone\n // who does await it still sees the failure.\n result.catch(() => {});\n\n const stream: LanguageModelStream = {\n text: text.asReadonly(),\n status: status.asReadonly(),\n error: error.asReadonly(),\n result,\n cancel: () => {\n if (status() !== 'streaming' || !session) return;\n cancelled = true;\n void session.cancel();\n },\n };\n this.streaming = session ? stream : null;\n return stream;\n }\n\n private async once(sessionConfig: SessionConfig, prompt: string): Promise<string> {\n this.refuseUnlessAvailable();\n const session = this.native!.session(sessionConfig);\n try {\n return await session.respond(prompt);\n } finally {\n session.release();\n }\n }\n\n /** Re-reads rather than trusting the signal, which only moves when the platform announces it. */\n private refuseUnlessAvailable(): void {\n const availability = this.refresh();\n if (availability !== 'available') throw new LanguageModelUnavailableError(availability);\n }\n\n private read(): LanguageModelAvailability {\n const native = present(this.native);\n return native ? known(native.availability()) : 'notInstalled';\n }\n}\n\nfunction known(value: string): LanguageModelAvailability {\n return REPORTED.has(value) ? (value as LanguageModelAvailability) : 'unknown';\n}\n\n/** Only the keys that were given, so the native side sees its own defaults for the rest. */\nfunction config({ instructions, ...sampling }: GenerateOptions): SessionConfig {\n const options = Object.fromEntries(\n Object.entries(sampling).filter(([, value]) => value !== undefined),\n );\n return {\n ...(instructions === undefined ? {} : { instructions }),\n ...(Object.keys(options).length === 0 ? {} : { options }),\n };\n}\n"]}
@@ -16,6 +16,8 @@ export interface OrientationSource {
16
16
  readonly reported: Observed<Orientation> | null;
17
17
  lock(lock: OrientationLock): Promise<void>;
18
18
  unlock(): Promise<void>;
19
+ /** Calls `listener` when a window comes to the front, to be locked again. */
20
+ onWindowFront?(listener: () => void): () => void;
19
21
  }
20
22
  export declare class DeviceOrientation {
21
23
  /** Overridden in a test to rotate a device that is not there. */
@@ -23,13 +25,16 @@ export declare class DeviceOrientation {
23
25
  private readonly source;
24
26
  readonly orientation: Signal<Orientation>;
25
27
  readonly landscape: Signal<boolean>;
28
+ /** Every app's locks: the screen is the runtime's, not an app's. */
29
+ private readonly window;
30
+ /** This app, among the apps locking the screen. */
31
+ private readonly owner;
32
+ constructor();
26
33
  /**
27
34
  * Pin the screen. Returns the function that unpins it, so a screen that wants landscape while
28
35
  * it is up can hand it to `DestroyRef.onDestroy` and not think about it again.
29
36
  */
30
37
  lock(lock: OrientationLock): () => void;
31
- /** Every lock not yet released, the one in force last. */
32
- private readonly locks;
33
38
  static ɵfac: i0.ɵɵFactoryDeclaration<DeviceOrientation, never>;
34
39
  static ɵprov: i0.ɵɵInjectableDeclaration<any>;
35
40
  }