@symbiote-native/brightness 0.0.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.
Files changed (56) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +290 -0
  3. package/build/angular/index.d.ts +2 -0
  4. package/build/angular/index.js +4 -0
  5. package/build/angular/services/permissions.service/index.d.ts +8 -0
  6. package/build/angular/services/permissions.service/index.js +78 -0
  7. package/build/core/brightness.d.ts +56 -0
  8. package/build/core/brightness.js +137 -0
  9. package/build/core/index.d.ts +3 -0
  10. package/build/core/index.js +2 -0
  11. package/build/core/native-module.d.ts +16 -0
  12. package/build/core/native-module.js +3 -0
  13. package/build/core/types.d.ts +16 -0
  14. package/build/core/types.js +16 -0
  15. package/build/react/hooks/use-permissions/index.d.ts +6 -0
  16. package/build/react/hooks/use-permissions/index.js +30 -0
  17. package/build/react/index.d.ts +2 -0
  18. package/build/react/index.js +6 -0
  19. package/build/vue/composables/use-permissions/index.d.ts +7 -0
  20. package/build/vue/composables/use-permissions/index.js +23 -0
  21. package/build/vue/index.d.ts +2 -0
  22. package/build/vue/index.js +6 -0
  23. package/build-ngc/angular/index.d.ts +2 -0
  24. package/build-ngc/angular/index.js +5 -0
  25. package/build-ngc/angular/index.js.map +1 -0
  26. package/build-ngc/angular/services/permissions.service/index.d.ts +11 -0
  27. package/build-ngc/angular/services/permissions.service/index.js +37 -0
  28. package/build-ngc/angular/services/permissions.service/index.js.map +1 -0
  29. package/build-ngc/core/brightness.d.ts +56 -0
  30. package/build-ngc/core/brightness.js +138 -0
  31. package/build-ngc/core/brightness.js.map +1 -0
  32. package/build-ngc/core/index.d.ts +3 -0
  33. package/build-ngc/core/index.js +3 -0
  34. package/build-ngc/core/index.js.map +1 -0
  35. package/build-ngc/core/native-module.d.ts +16 -0
  36. package/build-ngc/core/native-module.js +4 -0
  37. package/build-ngc/core/native-module.js.map +1 -0
  38. package/build-ngc/core/types.d.ts +16 -0
  39. package/build-ngc/core/types.js +17 -0
  40. package/build-ngc/core/types.js.map +1 -0
  41. package/native-link.json +12 -0
  42. package/package.json +107 -0
  43. package/src/angular/index.ts +23 -0
  44. package/src/angular/services/permissions.service/index.ts +37 -0
  45. package/src/angular/services/permissions.service/permissions.service.test.ts +107 -0
  46. package/src/core/brightness.test.ts +334 -0
  47. package/src/core/brightness.ts +155 -0
  48. package/src/core/index.ts +22 -0
  49. package/src/core/native-module.ts +32 -0
  50. package/src/core/types.ts +27 -0
  51. package/src/react/hooks/use-permissions/index.ts +41 -0
  52. package/src/react/hooks/use-permissions/use-permissions.test.tsx +100 -0
  53. package/src/react/index.ts +26 -0
  54. package/src/vue/composables/use-permissions/index.ts +36 -0
  55. package/src/vue/composables/use-permissions/use-permissions.test.ts +99 -0
  56. package/src/vue/index.ts +26 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 A. Prokopenko
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,290 @@
1
+ # @symbiote-native/brightness
2
+
3
+ A wrapper package for [SymbioteNative](../../README.md) that makes
4
+ [`expo-brightness`](https://github.com/expo/expo/tree/main/packages/expo-brightness) — screen
5
+ brightness get/set, Android-only system-brightness-mode control, an iOS-only brightness-change
6
+ listener, and permission get/request — usable from **every** adapter, React, Vue, and Angular, not
7
+ just React. Unlike [`@symbiote-native/sensors`](../sensors) — a `DeviceSensor` class per instance,
8
+ subscribed through a hook/composable/service — this package's surface is mostly stateless free
9
+ async functions plus one listener; only the permission surface gets its own lifecycle hook/
10
+ composable/service, mirroring upstream's own `usePermissions`. Built the same way as
11
+ [`@symbiote-native/local-auth`](../local-auth), [`@symbiote-native/battery`](../battery), and
12
+ [`@symbiote-native/haptics`](../haptics) — an `expo-modules-core`-based wrapper (see the
13
+ `symbiote-expo-native-module` project skill for the full mechanism: why `expo-modules-core` is
14
+ depended on directly and never the `expo` meta-package, why the upstream JS is hand-ported into
15
+ `core/` rather than imported, and how autolinking picks up the native module).
16
+
17
+ ## Install
18
+
19
+ ```bash
20
+ npm install @symbiote-native/brightness
21
+ ```
22
+
23
+ `expo-brightness` and `expo-modules-core` come along as regular, pinned dependencies — never
24
+ install either yourself, and never add the `expo` meta-package to this project (it bundles its own
25
+ Metro/Babel pipeline that conflicts with this project's own).
26
+
27
+ ### Required one-time step: native autolinking wiring
28
+
29
+ Unlike a plain RN native module, `expo-brightness`'s native code is discovered by
30
+ `expo-modules-autolinking`, not the `react-native.config.cjs`/podspec mechanism other wrappers in
31
+ this repo use — this needs wiring into the native host app **once**, covering this package and
32
+ every other `expo-modules-core` package with zero further changes:
33
+
34
+ | Platform | Touches |
35
+ |---|---|
36
+ | iOS | `ios/Podfile` — add `use_expo_modules!` |
37
+ | iOS | `AppDelegate.swift` — Expo's runtime-bootstrap hook |
38
+ | Android | `settings.gradle` / `app/build.gradle` — resolve and include the Expo Gradle projects |
39
+ | Android | `MainApplication.kt` — Expo's bootstrap hook, plus a hand-written native-module name map (there's no `expo` meta-package here to auto-generate one) |
40
+ | Android | `AndroidManifest.xml` — `android.permission.WRITE_SETTINGS`, required to set the system-wide brightness (`setSystemBrightnessAsync`/`setSystemBrightnessModeAsync`) |
41
+
42
+ Full mechanics — the Podfile pieces that normally ship inside the `expo` package, the `expo`
43
+ peer-dependency exclusion list — live in the `symbiote-expo-native-module` skill. Reference
44
+ implementation: `examples/expo-react/android/app/src/main/AndroidManifest.xml` (the
45
+ `WRITE_SETTINGS` permission) and
46
+ `examples/expo-react/android/app/src/main/java/com/canaryexpo/MainApplication.kt` (the
47
+ `BrightnessModule` registration) — the Vue and Angular Expo canaries wire the same three layers.
48
+
49
+ ## Shape
50
+
51
+ ```
52
+ src/core/ isAvailableAsync, get/setBrightnessAsync, the Android-only
53
+ system-brightness-mode surface, permission get/request, and
54
+ addBrightnessListener (iOS only); native-module.ts resolves
55
+ ExpoBrightness through expo-modules-core's requireNativeModule.
56
+ src/react/hooks/ @symbiote-native/brightness/react — usePermissions
57
+ src/vue/composables/ @symbiote-native/brightness/vue — usePermissions (same name)
58
+ src/angular/services/ @symbiote-native/brightness/angular — PermissionsService
59
+ ```
60
+
61
+ Each adapter's hook/composable/service wraps the same one-shot `getPermissionsAsync`/
62
+ `requestPermissionsAsync` pair in its own lifecycle idiom (auto-fetch on mount, exposed as a
63
+ tuple/object/signal). Every other export — brightness get/set, the system-brightness-mode surface,
64
+ the listener — is a stateless free-function re-export, written once in `core/` and shared verbatim
65
+ by all three adapters.
66
+
67
+ ## Use it
68
+
69
+ ```tsx
70
+ // React — trimmed from examples/expo-react/screens/BrightnessScreen.tsx
71
+ import { useEffect, useState } from 'react';
72
+ import { Pressable, Text, View } from '@symbiote-native/react';
73
+ import {
74
+ addBrightnessListener,
75
+ getBrightnessAsync,
76
+ setBrightnessAsync,
77
+ } from '@symbiote-native/brightness';
78
+ import { usePermissions } from '@symbiote-native/brightness/react';
79
+
80
+ function BrightnessScreen() {
81
+ const [brightness, setBrightness] = useState<number | null>(null);
82
+ const [permissionStatus, requestPermission] = usePermissions();
83
+
84
+ useEffect(() => {
85
+ getBrightnessAsync().then(setBrightness);
86
+ // iOS only — never fires on Android, so there the value only changes via setBrightnessAsync.
87
+ const subscription = addBrightnessListener(event => setBrightness(event.brightness));
88
+ return () => subscription.remove();
89
+ }, []);
90
+
91
+ return (
92
+ <View>
93
+ <Text>{brightness === null ? 'checking…' : `${Math.round(brightness * 100)}%`}</Text>
94
+ <Pressable onPress={() => setBrightnessAsync(0.5)}>
95
+ <Text>Set to 50%</Text>
96
+ </Pressable>
97
+ <Text>{permissionStatus?.status ?? 'checking…'}</Text>
98
+ <Pressable onPress={() => requestPermission()}>
99
+ <Text>Request permission</Text>
100
+ </Pressable>
101
+ </View>
102
+ );
103
+ }
104
+ ```
105
+
106
+ ```vue
107
+ <!-- Vue — trimmed from examples/expo-vue-sfc/screens/BrightnessScreen.vue -->
108
+ <script setup lang="ts">
109
+ import { onMounted, onUnmounted, ref } from 'vue';
110
+ import { Pressable, Text, View } from '@symbiote-native/vue';
111
+ import {
112
+ addBrightnessListener,
113
+ getBrightnessAsync,
114
+ setBrightnessAsync,
115
+ type EventSubscription,
116
+ } from '@symbiote-native/brightness';
117
+ import { usePermissions } from '@symbiote-native/brightness/vue';
118
+
119
+ const brightness = ref<number | null>(null);
120
+ const { status: permissionStatus, request: requestPermission } = usePermissions();
121
+
122
+ let subscription: EventSubscription | undefined;
123
+
124
+ onMounted(() => {
125
+ void getBrightnessAsync().then(value => (brightness.value = value));
126
+ subscription = addBrightnessListener(event => (brightness.value = event.brightness));
127
+ });
128
+ onUnmounted(() => subscription?.remove());
129
+ </script>
130
+
131
+ <template>
132
+ <View>
133
+ <Text>{{ brightness === null ? 'checking…' : `${Math.round(brightness * 100)}%` }}</Text>
134
+ <Pressable @press="setBrightnessAsync(0.5)">
135
+ <Text>Set to 50%</Text>
136
+ </Pressable>
137
+ <Text>{{ permissionStatus?.status ?? 'checking…' }}</Text>
138
+ <Pressable @press="requestPermission()">
139
+ <Text>Request permission</Text>
140
+ </Pressable>
141
+ </View>
142
+ </template>
143
+ ```
144
+
145
+ ```ts
146
+ // Angular — trimmed from examples/expo-angular/src/screens/BrightnessScreen.ts
147
+ import { Component, inject, signal } from '@angular/core';
148
+ import { Pressable, Text, View } from '@symbiote-native/angular';
149
+ import {
150
+ PermissionsService,
151
+ addBrightnessListener,
152
+ getBrightnessAsync,
153
+ setBrightnessAsync,
154
+ } from '@symbiote-native/brightness/angular';
155
+
156
+ @Component({
157
+ selector: 'BrightnessScreen',
158
+ standalone: true,
159
+ imports: [Pressable, Text, View],
160
+ template: `
161
+ <View>
162
+ <Text>{{ brightnessLabel() }}</Text>
163
+ <Pressable (press)="setBrightnessAsync(0.5)"><Text>Set to 50%</Text></Pressable>
164
+ <Text>{{ permissionStatus()?.status ?? 'checking…' }}</Text>
165
+ <Pressable (press)="permissionsService.request()"><Text>Request permission</Text></Pressable>
166
+ </View>
167
+ `,
168
+ })
169
+ export class BrightnessScreen {
170
+ private readonly permissionsService = inject(PermissionsService);
171
+ readonly permissionStatus = this.permissionsService.connect();
172
+ readonly brightness = signal<number | null>(null);
173
+
174
+ constructor() {
175
+ getBrightnessAsync().then(value => this.brightness.set(value));
176
+ addBrightnessListener(event => this.brightness.set(event.brightness));
177
+ }
178
+
179
+ brightnessLabel(): string {
180
+ const value = this.brightness();
181
+ return value === null ? 'checking…' : `${Math.round(value * 100)}%`;
182
+ }
183
+
184
+ setBrightnessAsync(value: number): void {
185
+ setBrightnessAsync(value).then(() => getBrightnessAsync().then(result => this.brightness.set(result)));
186
+ }
187
+ }
188
+ ```
189
+
190
+ The real demo screens (`examples/expo-react/screens/BrightnessScreen.tsx` and its Vue/Angular
191
+ twins in `examples/expo-vue-sfc`, `examples/expo-vue-tsx`, `examples/expo-angular`) additionally
192
+ gate an Android-only system-brightness-mode card behind `Platform.OS === 'android'` — see
193
+ [API](#api) below for that surface's full signatures.
194
+
195
+ ## API
196
+
197
+ Mostly stateless async functions, plus one iOS-only listener and a permission surface that gets
198
+ its own lifecycle hook/composable/service (matching upstream's own `usePermissions`).
199
+
200
+ ```ts
201
+ isAvailableAsync(): Promise<boolean>
202
+ // Whether the brightness API is available — !!native.getBrightnessAsync.
203
+
204
+ getBrightnessAsync(): Promise<number>
205
+ setBrightnessAsync(brightnessValue: number): Promise<void>
206
+ // The app-local screen brightness, 0..1 (clamped; NaN throws a TypeError).
207
+
208
+ getSystemBrightnessAsync(): Promise<number>
209
+ setSystemBrightnessAsync(brightnessValue: number): Promise<void>
210
+ // System-wide brightness. On iOS these delegate straight to get/setBrightnessAsync — there is
211
+ // no separate system-level value on that platform.
212
+
213
+ restoreSystemBrightnessAsync(): Promise<void>
214
+ isUsingSystemBrightnessAsync(): Promise<boolean>
215
+ getSystemBrightnessModeAsync(): Promise<BrightnessMode> // UNKNOWN / AUTOMATIC / MANUAL
216
+ setSystemBrightnessModeAsync(mode: BrightnessMode): Promise<void>
217
+ // Android only — every one of these is a no-op (or resolves a fixed fallback) on iOS/other
218
+ // platforms. setSystemBrightnessModeAsync(UNKNOWN) is also always a no-op, even on Android.
219
+
220
+ getPermissionsAsync(): Promise<PermissionResponse>
221
+ requestPermissionsAsync(): Promise<PermissionResponse>
222
+ // Plain passthroughs to the native module — no platform gating.
223
+
224
+ addBrightnessListener(listener: (event: BrightnessEvent) => void): EventSubscription
225
+ // @platform ios — fires only on iOS; never fires on Android or web.
226
+ ```
227
+
228
+ Plus `BrightnessMode` (enum: `UNKNOWN`/`AUTOMATIC`/`MANUAL`), `BrightnessEvent`, and
229
+ `PermissionStatus`/`PermissionResponse`/`PermissionExpiration` — the latter three re-exported
230
+ verbatim from `expo-modules-core`, never the `expo` meta-package.
231
+
232
+ ```ts
233
+ import { getBrightnessAsync, setBrightnessAsync } from '@symbiote-native/brightness';
234
+
235
+ // framework-scoped entry points re-export the same free functions, plus a usePermissions
236
+ // hook/composable/service:
237
+ import { usePermissions } from '@symbiote-native/brightness/react';
238
+ import { usePermissions } from '@symbiote-native/brightness/vue';
239
+ import { PermissionsService } from '@symbiote-native/brightness/angular';
240
+ ```
241
+
242
+ `usePermissions` (React/Vue) auto-fetches the current permission status on mount and returns a
243
+ `[status, request, get]` tuple (React) / `{ status, request, get }` object (Vue); Angular's
244
+ `PermissionsService.connect()` does the same, returning a readonly `Signal`:
245
+
246
+ ```ts
247
+ // React
248
+ const [status, request] = usePermissions();
249
+
250
+ // Vue
251
+ const { status, request } = usePermissions();
252
+
253
+ // Angular
254
+ readonly status = inject(PermissionsService).connect();
255
+ ```
256
+
257
+ ## Platform notes
258
+
259
+ - **System-brightness surface is Android only.** `getSystemBrightnessAsync`/
260
+ `setSystemBrightnessAsync` fall back to the app-local `get`/`setBrightnessAsync` on iOS (there
261
+ is no separate system-level brightness there); `restoreSystemBrightnessAsync`/
262
+ `setSystemBrightnessModeAsync` no-op, `isUsingSystemBrightnessAsync` resolves `false`, and
263
+ `getSystemBrightnessModeAsync` resolves `BrightnessMode.UNKNOWN` on every other platform.
264
+ - **`addBrightnessListener` is iOS only.** It never fires on Android or web — an app relying on
265
+ it for cross-platform behavior needs its own fallback (e.g. polling `getBrightnessAsync`).
266
+ - **The iOS Simulator never round-trips brightness at all.** `UIScreen.main.brightness` — what
267
+ `getBrightnessAsync`/`setBrightnessAsync` read and write on iOS — isn't implemented by the
268
+ Simulator: `set` is a silent no-op and `get` always resolves a constant (observed `1.0`),
269
+ regardless of what was written. This is a long-standing Apple Simulator limitation reproducible
270
+ in any app, including stock Expo Go — not a bug in this wrapper. It only reproduces on a real
271
+ iOS device; Android's emulator has no equivalent gap (see the `symbiote-expo-native-module`
272
+ skill for the verified symptom and the underlying `BrightnessModule.swift` call).
273
+
274
+ ## Test it
275
+
276
+ No Fabric/Descriptor angle at all — brightness is a pure async-function + listener + permission
277
+ surface, never a view. Tests inject a fake native-module object in place of the real
278
+ `requireNativeModule` resolution (`src/core/*.test.ts`, `src/{react,vue,angular}/**/*.test.{ts,tsx}`,
279
+ `vitest`), the same pattern `expo-brightness` itself uses upstream — no `installFabric()`, no
280
+ ViewConfig. Native rendering itself is verified on-device (see the parent
281
+ [README](../../README.md) for the project's testing model).
282
+
283
+ ## Known gaps
284
+
285
+ - Not yet wired into the public, non-Expo `examples/react` canary — only in `examples/expo-react`
286
+ (and its Vue/Angular Expo twins, `examples/expo-vue-sfc`/`examples/expo-vue-tsx`/
287
+ `examples/expo-angular`, all of which already have full three-layer native wiring: Android
288
+ `MainApplication.kt` registration + `WRITE_SETTINGS`, plus the iOS Podfile/pod install).
289
+ - Tests exercise the JS layer only (fake native module, `vitest`) — no on-device/simulator
290
+ automated smoke test yet, only manual verification.
@@ -0,0 +1,2 @@
1
+ export { PermissionsService } from './services/permissions.service';
2
+ export { isAvailableAsync, getBrightnessAsync, setBrightnessAsync, getSystemBrightnessAsync, setSystemBrightnessAsync, restoreSystemBrightnessAsync, isUsingSystemBrightnessAsync, getSystemBrightnessModeAsync, setSystemBrightnessModeAsync, getPermissionsAsync, requestPermissionsAsync, addBrightnessListener, BrightnessMode, PermissionStatus, type BrightnessEvent, type PermissionExpiration, type PermissionResponse, type EventSubscription, } from '../core';
@@ -0,0 +1,4 @@
1
+ // PermissionsService is the Angular-only lifecycle half; the free functions and event
2
+ // subscription plumbing all live in core, shared with React/Vue.
3
+ export { PermissionsService } from './services/permissions.service/index.js';
4
+ export { isAvailableAsync, getBrightnessAsync, setBrightnessAsync, getSystemBrightnessAsync, setSystemBrightnessAsync, restoreSystemBrightnessAsync, isUsingSystemBrightnessAsync, getSystemBrightnessModeAsync, setSystemBrightnessModeAsync, getPermissionsAsync, requestPermissionsAsync, addBrightnessListener, BrightnessMode, PermissionStatus, } from '../core/index.js';
@@ -0,0 +1,8 @@
1
+ import { type Signal } from '@angular/core';
2
+ import { type PermissionResponse } from '../../../core';
3
+ export declare class PermissionsService {
4
+ private readonly status;
5
+ connect(): Signal<PermissionResponse | null>;
6
+ get(): Promise<PermissionResponse>;
7
+ request(): Promise<PermissionResponse>;
8
+ }
@@ -0,0 +1,78 @@
1
+ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
2
+ function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
3
+ var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
4
+ var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
5
+ var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
6
+ var _, done = false;
7
+ for (var i = decorators.length - 1; i >= 0; i--) {
8
+ var context = {};
9
+ for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
10
+ for (var p in contextIn.access) context.access[p] = contextIn.access[p];
11
+ context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
12
+ var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
13
+ if (kind === "accessor") {
14
+ if (result === void 0) continue;
15
+ if (result === null || typeof result !== "object") throw new TypeError("Object expected");
16
+ if (_ = accept(result.get)) descriptor.get = _;
17
+ if (_ = accept(result.set)) descriptor.set = _;
18
+ if (_ = accept(result.init)) initializers.unshift(_);
19
+ }
20
+ else if (_ = accept(result)) {
21
+ if (kind === "field") initializers.unshift(_);
22
+ else descriptor[key] = _;
23
+ }
24
+ }
25
+ if (target) Object.defineProperty(target, contextIn.name, descriptor);
26
+ done = true;
27
+ };
28
+ var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
29
+ var useValue = arguments.length > 2;
30
+ for (var i = 0; i < initializers.length; i++) {
31
+ value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
32
+ }
33
+ return useValue ? value : void 0;
34
+ };
35
+ // Angular twin of React's usePermissions hook and Vue's usePermissions composable.
36
+ // `connect()` auto-fetches once (no ongoing subscription to clean up, so no Injector/effect
37
+ // needed, unlike BatteryStateService) and returns a readonly signal; get()/request() are
38
+ // imperative one-shot methods.
39
+ //
40
+ // readonly status = inject(PermissionsService).connect();
41
+ // // template: {{ status()?.granted }}
42
+ import { Injectable, signal } from '@angular/core';
43
+ import { getPermissionsAsync, requestPermissionsAsync, } from '../../../core';
44
+ let PermissionsService = (() => {
45
+ let _classDecorators = [Injectable({ providedIn: 'root' })];
46
+ let _classDescriptor;
47
+ let _classExtraInitializers = [];
48
+ let _classThis;
49
+ var PermissionsService = class {
50
+ static { _classThis = this; }
51
+ static {
52
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
53
+ __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
54
+ PermissionsService = _classThis = _classDescriptor.value;
55
+ if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
56
+ __runInitializers(_classThis, _classExtraInitializers);
57
+ }
58
+ status = signal(null);
59
+ connect() {
60
+ if (this.status() === null) {
61
+ void this.get();
62
+ }
63
+ return this.status.asReadonly();
64
+ }
65
+ async get() {
66
+ const response = await getPermissionsAsync();
67
+ this.status.set(response);
68
+ return response;
69
+ }
70
+ async request() {
71
+ const response = await requestPermissionsAsync();
72
+ this.status.set(response);
73
+ return response;
74
+ }
75
+ };
76
+ return PermissionsService = _classThis;
77
+ })();
78
+ export { PermissionsService };
@@ -0,0 +1,56 @@
1
+ import type { EventSubscription } from 'expo-modules-core';
2
+ import { BrightnessMode, type BrightnessEvent, type PermissionResponse } from './types';
3
+ /** Whether the brightness API is available on the current device. */
4
+ export declare function isAvailableAsync(): Promise<boolean>;
5
+ /** Gets the current brightness level of the device's main screen, between `0` and `1`. */
6
+ export declare function getBrightnessAsync(): Promise<number>;
7
+ /**
8
+ * Sets the current screen brightness. On iOS this only affects the screen while the app is
9
+ * foregrounded; on Android it persists until changed again.
10
+ */
11
+ export declare function setBrightnessAsync(brightnessValue: number): Promise<void>;
12
+ /**
13
+ * Gets the system-wide brightness. On iOS the app-local and system brightness are the same
14
+ * value, so this delegates to `getBrightnessAsync`.
15
+ * @platform android
16
+ */
17
+ export declare function getSystemBrightnessAsync(): Promise<number>;
18
+ /**
19
+ * Sets the system-wide brightness. On iOS this delegates to `setBrightnessAsync`, since there is
20
+ * no separate system-level brightness to set.
21
+ * @platform android
22
+ */
23
+ export declare function setSystemBrightnessAsync(brightnessValue: number): Promise<void>;
24
+ /**
25
+ * Resets the system brightness to the value it had before this app started controlling it. A
26
+ * no-op on every platform except Android.
27
+ * @platform android
28
+ */
29
+ export declare function restoreSystemBrightnessAsync(): Promise<void>;
30
+ /**
31
+ * Whether the app is currently controlling the system brightness (as opposed to only its own
32
+ * window's brightness). Always `false` on every platform except Android.
33
+ * @platform android
34
+ */
35
+ export declare function isUsingSystemBrightnessAsync(): Promise<boolean>;
36
+ /**
37
+ * Gets the system brightness mode (`AUTOMATIC` / `MANUAL`). Always resolves `UNKNOWN` on every
38
+ * platform except Android.
39
+ * @platform android
40
+ */
41
+ export declare function getSystemBrightnessModeAsync(): Promise<BrightnessMode>;
42
+ /**
43
+ * Sets the system brightness mode. A no-op on every platform except Android, and also a no-op
44
+ * when passed `BrightnessMode.UNKNOWN` (there is nothing meaningful to set it to).
45
+ * @platform android
46
+ */
47
+ export declare function setSystemBrightnessModeAsync(brightnessMode: BrightnessMode): Promise<void>;
48
+ /** Checks user's permissions for accessing the system brightness. */
49
+ export declare function getPermissionsAsync(): Promise<PermissionResponse>;
50
+ /** Asks the user for permission to access the system brightness. */
51
+ export declare function requestPermissionsAsync(): Promise<PermissionResponse>;
52
+ /**
53
+ * Subscribes to brightness changes. Only fires on iOS — never on Android or web.
54
+ * @platform ios
55
+ */
56
+ export declare function addBrightnessListener(listener: (event: BrightnessEvent) => void): EventSubscription;
@@ -0,0 +1,137 @@
1
+ // Hand-ported from .vendors/expo/packages/expo-brightness/src/Brightness.ts (sdk-57). The
2
+ // system-brightness surface (mode, "using system brightness", restore) is Android-only —
3
+ // upstream branches every one of those on `Platform.OS !== 'android'` before ever reaching the
4
+ // native module, so those branches are preserved verbatim rather than guessed.
5
+ import { Platform, UnavailabilityError } from 'expo-modules-core';
6
+ import { expoBrightness } from './native-module.js';
7
+ import { BrightnessMode } from './types.js';
8
+ const NATIVE_MODULE_NAME = 'expo-brightness';
9
+ function clampBrightness(brightnessValue, callerName) {
10
+ const clamped = Math.max(0, Math.min(brightnessValue, 1));
11
+ if (Number.isNaN(clamped)) {
12
+ throw new TypeError(`${callerName} cannot be called with ${brightnessValue}`);
13
+ }
14
+ return clamped;
15
+ }
16
+ /** Whether the brightness API is available on the current device. */
17
+ export async function isAvailableAsync() {
18
+ return !!expoBrightness.getBrightnessAsync;
19
+ }
20
+ /** Gets the current brightness level of the device's main screen, between `0` and `1`. */
21
+ export async function getBrightnessAsync() {
22
+ if (!expoBrightness.getBrightnessAsync) {
23
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'getBrightnessAsync');
24
+ }
25
+ return expoBrightness.getBrightnessAsync();
26
+ }
27
+ /**
28
+ * Sets the current screen brightness. On iOS this only affects the screen while the app is
29
+ * foregrounded; on Android it persists until changed again.
30
+ */
31
+ export async function setBrightnessAsync(brightnessValue) {
32
+ const clamped = clampBrightness(brightnessValue, 'setBrightnessAsync');
33
+ if (!expoBrightness.setBrightnessAsync) {
34
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'setBrightnessAsync');
35
+ }
36
+ await expoBrightness.setBrightnessAsync(clamped);
37
+ }
38
+ /**
39
+ * Gets the system-wide brightness. On iOS the app-local and system brightness are the same
40
+ * value, so this delegates to `getBrightnessAsync`.
41
+ * @platform android
42
+ */
43
+ export async function getSystemBrightnessAsync() {
44
+ if (Platform.OS !== 'android') {
45
+ return getBrightnessAsync();
46
+ }
47
+ if (!expoBrightness.getSystemBrightnessAsync) {
48
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'getSystemBrightnessAsync');
49
+ }
50
+ return expoBrightness.getSystemBrightnessAsync();
51
+ }
52
+ /**
53
+ * Sets the system-wide brightness. On iOS this delegates to `setBrightnessAsync`, since there is
54
+ * no separate system-level brightness to set.
55
+ * @platform android
56
+ */
57
+ export async function setSystemBrightnessAsync(brightnessValue) {
58
+ const clamped = clampBrightness(brightnessValue, 'setSystemBrightnessAsync');
59
+ if (Platform.OS !== 'android') {
60
+ return setBrightnessAsync(clamped);
61
+ }
62
+ if (!expoBrightness.setSystemBrightnessAsync) {
63
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'setSystemBrightnessAsync');
64
+ }
65
+ await expoBrightness.setSystemBrightnessAsync(clamped);
66
+ }
67
+ /**
68
+ * Resets the system brightness to the value it had before this app started controlling it. A
69
+ * no-op on every platform except Android.
70
+ * @platform android
71
+ */
72
+ export async function restoreSystemBrightnessAsync() {
73
+ if (Platform.OS !== 'android') {
74
+ return;
75
+ }
76
+ if (!expoBrightness.restoreSystemBrightnessAsync) {
77
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'restoreSystemBrightnessAsync');
78
+ }
79
+ await expoBrightness.restoreSystemBrightnessAsync();
80
+ }
81
+ /**
82
+ * Whether the app is currently controlling the system brightness (as opposed to only its own
83
+ * window's brightness). Always `false` on every platform except Android.
84
+ * @platform android
85
+ */
86
+ export async function isUsingSystemBrightnessAsync() {
87
+ if (Platform.OS !== 'android') {
88
+ return false;
89
+ }
90
+ if (!expoBrightness.isUsingSystemBrightnessAsync) {
91
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'isUsingSystemBrightnessAsync');
92
+ }
93
+ return expoBrightness.isUsingSystemBrightnessAsync();
94
+ }
95
+ /**
96
+ * Gets the system brightness mode (`AUTOMATIC` / `MANUAL`). Always resolves `UNKNOWN` on every
97
+ * platform except Android.
98
+ * @platform android
99
+ */
100
+ export async function getSystemBrightnessModeAsync() {
101
+ if (Platform.OS !== 'android') {
102
+ return BrightnessMode.UNKNOWN;
103
+ }
104
+ if (!expoBrightness.getSystemBrightnessModeAsync) {
105
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'getSystemBrightnessModeAsync');
106
+ }
107
+ return expoBrightness.getSystemBrightnessModeAsync();
108
+ }
109
+ /**
110
+ * Sets the system brightness mode. A no-op on every platform except Android, and also a no-op
111
+ * when passed `BrightnessMode.UNKNOWN` (there is nothing meaningful to set it to).
112
+ * @platform android
113
+ */
114
+ export async function setSystemBrightnessModeAsync(brightnessMode) {
115
+ if (Platform.OS !== 'android' || brightnessMode === BrightnessMode.UNKNOWN) {
116
+ return;
117
+ }
118
+ if (!expoBrightness.setSystemBrightnessModeAsync) {
119
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'setSystemBrightnessModeAsync');
120
+ }
121
+ await expoBrightness.setSystemBrightnessModeAsync(brightnessMode);
122
+ }
123
+ /** Checks user's permissions for accessing the system brightness. */
124
+ export async function getPermissionsAsync() {
125
+ return expoBrightness.getPermissionsAsync();
126
+ }
127
+ /** Asks the user for permission to access the system brightness. */
128
+ export async function requestPermissionsAsync() {
129
+ return expoBrightness.requestPermissionsAsync();
130
+ }
131
+ /**
132
+ * Subscribes to brightness changes. Only fires on iOS — never on Android or web.
133
+ * @platform ios
134
+ */
135
+ export function addBrightnessListener(listener) {
136
+ return expoBrightness.addListener('Expo.brightnessDidChange', listener);
137
+ }
@@ -0,0 +1,3 @@
1
+ export { isAvailableAsync, getBrightnessAsync, setBrightnessAsync, getSystemBrightnessAsync, setSystemBrightnessAsync, restoreSystemBrightnessAsync, isUsingSystemBrightnessAsync, getSystemBrightnessModeAsync, setSystemBrightnessModeAsync, getPermissionsAsync, requestPermissionsAsync, addBrightnessListener, } from './brightness';
2
+ export { BrightnessMode, PermissionStatus, type BrightnessEvent, type PermissionExpiration, type PermissionResponse, } from './types';
3
+ export type { EventSubscription } from 'expo-modules-core';
@@ -0,0 +1,2 @@
1
+ export { isAvailableAsync, getBrightnessAsync, setBrightnessAsync, getSystemBrightnessAsync, setSystemBrightnessAsync, restoreSystemBrightnessAsync, isUsingSystemBrightnessAsync, getSystemBrightnessModeAsync, setSystemBrightnessModeAsync, getPermissionsAsync, requestPermissionsAsync, addBrightnessListener, } from './brightness.js';
2
+ export { BrightnessMode, PermissionStatus, } from './types.js';
@@ -0,0 +1,16 @@
1
+ import type { EventSubscription } from 'expo-modules-core';
2
+ import type { BrightnessEvent, BrightnessMode, PermissionResponse } from './types';
3
+ export type INativeBrightnessModule = {
4
+ getBrightnessAsync?(): Promise<number>;
5
+ setBrightnessAsync?(value: number): Promise<void>;
6
+ getSystemBrightnessAsync?(): Promise<number>;
7
+ setSystemBrightnessAsync?(value: number): Promise<void>;
8
+ restoreSystemBrightnessAsync?(): Promise<void>;
9
+ isUsingSystemBrightnessAsync?(): Promise<boolean>;
10
+ getSystemBrightnessModeAsync?(): Promise<BrightnessMode>;
11
+ setSystemBrightnessModeAsync?(mode: BrightnessMode): Promise<void>;
12
+ getPermissionsAsync(): Promise<PermissionResponse>;
13
+ requestPermissionsAsync(): Promise<PermissionResponse>;
14
+ addListener(eventName: 'Expo.brightnessDidChange', listener: (event: BrightnessEvent) => void): EventSubscription;
15
+ };
16
+ export declare const expoBrightness: INativeBrightnessModule;
@@ -0,0 +1,3 @@
1
+ import { requireNativeModule } from 'expo-modules-core';
2
+ const EXPO_BRIGHTNESS_MODULE_NAME = 'ExpoBrightness';
3
+ export const expoBrightness = requireNativeModule(EXPO_BRIGHTNESS_MODULE_NAME);
@@ -0,0 +1,16 @@
1
+ import { PermissionStatus, type PermissionExpiration, type PermissionResponse } from 'expo-modules-core';
2
+ export { PermissionStatus, type PermissionExpiration, type PermissionResponse };
3
+ /** Enum representing the brightness mode of the device. */
4
+ export declare enum BrightnessMode {
5
+ /** Returned when brightness mode cannot be determined. */
6
+ UNKNOWN = 0,
7
+ /** Automatic brightness mode, tracking the ambient light sensor. */
8
+ AUTOMATIC = 1,
9
+ /** Manual brightness mode, set by the user or by `setSystemBrightnessAsync`. */
10
+ MANUAL = 2
11
+ }
12
+ /** Fired when the screen brightness changes, on iOS only. */
13
+ export type BrightnessEvent = {
14
+ /** The current brightness value between `0` and `1`, inclusive. */
15
+ brightness: number;
16
+ };