rn-system-bar 3.2.2 → 3.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,797 +1,327 @@
1
- # rn-system-bar v3
1
+ # rn-system-bar
2
2
 
3
- > Control Android & iOS system bars, brightness, volume, orientation, theme, and screen casting from React Native.
4
-
5
- Supports **React Native CLI · Expo Dev Client · TypeScript · New Architecture (TurboModules)**
3
+ Control Android & iOS system bars, brightness, volume, orientation, network, battery, haptics, font scale and screen flags from React Native — with zero Expo dependency.
6
4
 
7
5
  ---
8
6
 
9
- ## 📦 Installation
7
+ ## Installation
10
8
 
11
- ```bash
9
+ ```sh
12
10
  npm install rn-system-bar
13
11
  # or
14
12
  yarn add rn-system-bar
15
13
  ```
16
14
 
17
- **iOS** — run after install:
15
+ iOS — run pod install after installing:
18
16
 
19
- ```bash
17
+ ```sh
20
18
  cd ios && pod install
21
19
  ```
22
20
 
23
- **Android** — auto-linked, no manual steps required.
24
-
25
21
  ---
26
22
 
27
- ## 🏗 Architecture
28
-
29
- ```
30
- React Native App
31
-
32
-
33
- rn-system-bar (TypeScript API + Platform guard)
34
-
35
- ├─ Android ──► SystemBarModule.kt
36
- │ ├ WindowInsetsController (API 30+)
37
- │ ├ AudioManager
38
- │ ├ ActivityInfo
39
- │ ├ DisplayManager ← system screencast
40
- │ └ MediaRouter ← app-only cast
41
-
42
- └─ iOS ──────► SystemBarModule.swift
43
- UIKit / AVFoundation
44
- ├ UIScreen.screens ← system screencast
45
- └ (AirPlay = system-managed, no public API)
46
- ```
23
+ ## Platform coverage
24
+
25
+ | Feature | Android | iOS |
26
+ | --------------------------------------------- | ------- | -------- |
27
+ | Navigation bar color / visibility / style | ✅ | stub |
28
+ | Status bar color / style / visibility | ✅ | ✅ |
29
+ | Brightness get / set / listen | ✅ | ✅ |
30
+ | Volume get / set / listen | ✅ | get only |
31
+ | Keep screen on | ✅ | ✅ |
32
+ | Immersive mode | ✅ | stub |
33
+ | Secure screen | ✅ | stub |
34
+ | Orientation | ✅ | ✅ |
35
+ | Network info + listener | ✅ | ✅ |
36
+ | Battery info + listener | ✅ | ✅ |
37
+ | Haptic feedback | ✅ | ✅ |
38
+ | System screencast (HDMI / Miracast / AirPlay) | ✅ | ✅ |
39
+ | Font scale + listener | ✅ | ✅ |
47
40
 
48
41
  ---
49
42
 
50
- ## 📁 Folder Structure
43
+ ## API Reference
51
44
 
52
- ```
53
- rn-system-bar/
54
- ├ index.ts
55
- ├ rn-system-bar.podspec
56
-
57
- ├ src/
58
- │ ├ SystemBar.ts ← all imperative JS/TS APIs
59
- │ ├ types.ts ← all TypeScript types
60
- │ ├ useSystemBar.ts ← React hooks
61
- │ └ useTheme.ts ← theme singleton + useTheme hook
62
-
63
- ├ specs/
64
- │ └ NativeSystemBar.ts ← TurboModule spec (New Architecture)
65
-
66
- ├ android/
67
- │ └ src/main/java/com/systembar/
68
- │ ├ SystemBarModule.kt
69
- │ └ SystemBarPackage.kt
70
-
71
- └ ios/
72
- ├ SystemBarModule.swift
73
- └ SystemBarModule.m
74
- ```
75
-
76
- ---
77
-
78
- ## 🚀 Quick Start
79
-
80
- ```tsx
81
- import React, { useEffect } from "react";
82
- import { View, Button } from "react-native";
83
- import * as SystemBar from "rn-system-bar";
84
- import { useSystemBar, useTheme } from "rn-system-bar";
85
-
86
- export default function App() {
87
- const { isDark } = useTheme();
88
-
89
- useSystemBar({
90
- navigationBarColor: isDark ? "#000000" : "#ffffff",
91
- navigationBarButtonStyle: isDark ? "light" : "dark",
92
- statusBarStyle: isDark ? "light" : "dark",
93
- keepScreenOn: true,
94
- });
95
-
96
- return (
97
- <View>
98
- <Button title="Immersive" onPress={() => SystemBar.immersiveMode(true)} />
99
- <Button
100
- title="Landscape"
101
- onPress={() => SystemBar.setOrientation("landscape")}
102
- />
103
- </View>
104
- );
105
- }
106
- ```
107
-
108
- ---
109
-
110
- ## 🎨 Theme
111
-
112
- ### `useTheme()`
113
-
114
- Reads the OS colour scheme and lets you override it manually. All `useTheme()` consumers across the app share one singleton — changing the mode in one place updates every subscriber instantly.
115
-
116
- ```tsx
117
- import { useTheme } from "rn-system-bar";
118
-
119
- const { isDark, mode, setMode } = useTheme();
120
-
121
- // isDark → true | false (resolved, always concrete)
122
- // mode → "system" | "dark" | "light"
123
- // setMode → (mode: ThemeMode) => void
124
-
125
- // Follow OS (default)
126
- setMode("system");
127
-
128
- // Force dark
129
- setMode("dark");
130
-
131
- // Force light
132
- setMode("light");
133
- ```
134
-
135
- ### `setGlobalThemeMode(mode)` — imperative, outside components
136
-
137
- ```ts
138
- import { setGlobalThemeMode } from "rn-system-bar";
139
-
140
- // Call from anywhere — navigation handlers, async callbacks, etc.
141
- setGlobalThemeMode("dark");
142
- ```
143
-
144
- ### `useThemeSystemBar(config)` — theme + system bar in one hook
145
-
146
- Automatically re-applies the correct config whenever the theme changes (OS switch or manual `setMode`). Returns the full `ThemeState` so you can also read `isDark` / `mode` / `setMode`.
147
-
148
- ```tsx
149
- import { useThemeSystemBar } from "rn-system-bar";
150
-
151
- const { isDark, mode, setMode } = useThemeSystemBar({
152
- // Applied when dark
153
- dark: {
154
- navigationBarColor: "#000000",
155
- navigationBarButtonStyle: "light",
156
- statusBarColor: "#000000",
157
- statusBarStyle: "light",
158
- },
159
- // Applied when light
160
- light: {
161
- navigationBarColor: "#ffffff",
162
- navigationBarButtonStyle: "dark",
163
- statusBarColor: "#ffffff",
164
- statusBarStyle: "dark",
165
- },
166
- // Always applied on both themes (theme-specific values win)
167
- base: {
168
- keepScreenOn: true,
169
- orientation: "portrait",
170
- },
171
- });
172
-
173
- // Manual toggle buttons
174
- <Button title="Dark" onPress={() => setMode("dark")} />
175
- <Button title="Light" onPress={() => setMode("light")} />
176
- <Button title="System" onPress={() => setMode("system")} />
177
- ```
178
-
179
- ---
180
-
181
- ## 🔵 Navigation Bar — `Android only`
182
-
183
- > All navigation bar APIs are automatically guarded with `Platform.OS === "android"`.
184
- > Calling them on iOS prints a dev-only warning and is a no-op — no crash.
185
-
186
- ### `setNavigationBarColor(color)`
187
-
188
- | Value | Result |
189
- | --------------- | ------------------------------------------------------------- |
190
- | `"#rrggbb"` | Solid colour |
191
- | `"transparent"` | Fully transparent — content draws edge-to-edge behind the bar |
192
- | `"translucent"` | Semi-transparent system scrim — frosted-glass effect |
45
+ ### Navigation Bar (Android only)
193
46
 
194
47
  ```ts
195
- import { setNavigationBarColor } from "rn-system-bar";
48
+ import {
49
+ setNavigationBarColor,
50
+ setNavigationBarVisibility,
51
+ setNavigationBarButtonStyle,
52
+ setNavigationBarStyle,
53
+ setNavigationBarBehavior,
54
+ } from "rn-system-bar";
196
55
 
197
56
  setNavigationBarColor("#1a1a2e"); // solid hex
198
- setNavigationBarColor("transparent"); // edge-to-edge / glass
57
+ setNavigationBarColor("transparent"); // edge-to-edge
199
58
  setNavigationBarColor("translucent"); // frosted glass
200
- ```
201
-
202
- ### `setNavigationBarVisibility(mode)`
203
59
 
204
- ```ts
205
- import { setNavigationBarVisibility } from "rn-system-bar";
206
-
207
- setNavigationBarVisibility("hidden"); // hide nav bar
208
- setNavigationBarVisibility("visible"); // show nav bar
209
- ```
210
-
211
- ### `setNavigationBarButtonStyle(style)`
212
-
213
- Controls the icon colour of the navigation bar buttons (back, home, recents).
214
-
215
- ```ts
216
- import { setNavigationBarButtonStyle } from "rn-system-bar";
217
-
218
- setNavigationBarButtonStyle("light"); // white icons — use on dark backgrounds
219
- setNavigationBarButtonStyle("dark"); // dark icons — use on light backgrounds
220
- ```
221
-
222
- ### `setNavigationBarStyle(style)`
223
-
224
- Higher-level wrapper that maps to button style — `"dark"` / `"auto"` → dark icons; `"light"` / `"inverted"` → light icons.
225
-
226
- ```ts
227
- import { setNavigationBarStyle } from "rn-system-bar";
228
-
229
- setNavigationBarStyle("dark"); // dark icons
230
- setNavigationBarStyle("light"); // light icons
231
- setNavigationBarStyle("auto"); // follows system
232
- setNavigationBarStyle("inverted"); // opposite of system
60
+ setNavigationBarVisibility("hidden"); // "visible" | "hidden"
61
+ setNavigationBarButtonStyle("light"); // "light" | "dark"
62
+ setNavigationBarStyle("dark"); // "auto" | "inverted" | "light" | "dark"
63
+ setNavigationBarBehavior("overlay-swipe"); // "overlay-swipe" | "inset-swipe" | "inset-touch"
233
64
  ```
234
65
 
235
- ### `setNavigationBarBehavior(behavior)`
66
+ ---
236
67
 
237
- Controls how hidden bars re-appear after a swipe gesture.
68
+ ### Status Bar
238
69
 
239
70
  ```ts
240
- import { setNavigationBarBehavior } from "rn-system-bar";
71
+ import {
72
+ setStatusBarColor,
73
+ setStatusBarStyle,
74
+ setStatusBarVisibility,
75
+ } from "rn-system-bar";
241
76
 
242
- setNavigationBarBehavior("overlay-swipe"); // bar appears temporarily on swipe (API 30+)
243
- setNavigationBarBehavior("inset-swipe"); // bar pushes content on swipe
244
- setNavigationBarBehavior("inset-touch"); // bar pushes content on touch
77
+ setStatusBarColor("#000000"); // Android only hex | "transparent" | "translucent"
78
+ setStatusBarStyle("light"); // "light" | "dark"
79
+ setStatusBarVisibility(false); // hide status bar
245
80
  ```
246
81
 
247
82
  ---
248
83
 
249
- ## 🔴 Status Bar
250
-
251
- ### `setStatusBarColor(color)` — Android only
252
-
253
- | Value | Result |
254
- | --------------- | -------------------------------------------------- |
255
- | `"#rrggbb"` | Solid colour |
256
- | `"transparent"` | Fully transparent — content draws under status bar |
257
- | `"translucent"` | Semi-transparent system scrim |
258
-
259
- ```ts
260
- import { setStatusBarColor } from "rn-system-bar";
261
-
262
- setStatusBarColor("#000000"); // solid black
263
- setStatusBarColor("transparent"); // content visible behind status bar
264
- setStatusBarColor("translucent"); // frosted glass
265
- ```
266
-
267
- ### `setStatusBarStyle(style)` — Android + iOS
84
+ ### Brightness
268
85
 
269
86
  ```ts
270
- import { setStatusBarStyle } from "rn-system-bar";
271
-
272
- setStatusBarStyle("light"); // white text/icons — use on dark backgrounds
273
- setStatusBarStyle("dark"); // dark text/icons — use on light backgrounds
274
- ```
275
-
276
- ### `setStatusBarVisibility(visible)` — Android + iOS
87
+ import {
88
+ setBrightness,
89
+ getBrightness,
90
+ onBrightnessChange,
91
+ } from "rn-system-bar";
277
92
 
278
- ```ts
279
- import { setStatusBarVisibility } from "rn-system-bar";
93
+ setBrightness(0.5); // 0.0–1.0
94
+ const level = await getBrightness(); // returns 0.0–1.0
280
95
 
281
- setStatusBarVisibility(false); // hide status bar (fullscreen)
282
- setStatusBarVisibility(true); // show status bar
96
+ const unsub = onBrightnessChange((brightness) => {
97
+ console.log("Brightness:", brightness);
98
+ });
99
+ unsub(); // stop listening
283
100
  ```
284
101
 
285
102
  ---
286
103
 
287
- ## ☀️ Brightness
288
-
289
- ### `setBrightness(level)`
290
-
291
- ```ts
292
- import { setBrightness } from "rn-system-bar";
293
-
294
- setBrightness(1.0); // maximum brightness
295
- setBrightness(0.5); // 50%
296
- setBrightness(0.01); // minimum (0.0 is clamped to 0.01)
297
- ```
298
-
299
- ### `getBrightness()` → `Promise<number>`
104
+ ### Volume
300
105
 
301
106
  ```ts
302
- import { getBrightness } from "rn-system-bar";
303
-
304
- const level = await getBrightness(); // 0.0 – 1.0
305
- console.log("Brightness:", level);
306
- ```
307
-
308
- ### `onBrightnessChange(callback)` → unsubscribe
107
+ import {
108
+ setVolume,
109
+ getVolume,
110
+ setVolumeHUDVisible,
111
+ onVolumeChange,
112
+ } from "rn-system-bar";
309
113
 
310
- Fires on every brightness change. On Android, polls every 500 ms since `Settings.System` doesn't broadcast changes.
114
+ setVolume(0.8); // 0.0–1.0, defaults to "music" stream
115
+ setVolume(0.5, "ring"); // "music" | "ring" | "notification" | "alarm" | "system"
116
+ const vol = await getVolume("music");
311
117
 
312
- ```ts
313
- import { onBrightnessChange } from "rn-system-bar";
118
+ setVolumeHUDVisible(false); // Android only — suppress system volume UI
314
119
 
315
- const unsub = onBrightnessChange((brightness) => {
316
- console.log("New brightness:", brightness); // 0.0 – 1.0
120
+ const unsub = onVolumeChange((volume, stream) => {
121
+ console.log(`${stream} volume: ${volume}`);
317
122
  });
318
-
319
- // Stop listening
320
123
  unsub();
321
124
  ```
322
125
 
323
126
  ---
324
127
 
325
- ## 🔊 Volume
326
-
327
- ### `setVolume(level, stream?)`
328
-
329
- > **iOS note:** `setVolume()` is a no-op on iOS due to Apple restrictions. `getVolume()` works normally.
128
+ ### Screen
330
129
 
331
130
  ```ts
332
- import { setVolume } from "rn-system-bar";
131
+ import { keepScreenOn, immersiveMode, setSecureScreen } from "rn-system-bar";
333
132
 
334
- setVolume(0.8); // 80% defaults to "music" stream
335
- setVolume(0.5, "ring"); // 50% ring volume
336
- setVolume(1.0, "alarm"); // max alarm volume
337
- setVolume(0.3, "notification");
338
- setVolume(0.6, "system");
133
+ keepScreenOn(true); // prevent display sleep
134
+ immersiveMode(true); // Android only — hide both bars
135
+ setSecureScreen(true); // Android only — block screenshots / screen recording
339
136
  ```
340
137
 
341
- | Stream | Description |
342
- | ---------------- | ----------------------- |
343
- | `"music"` | Media / music (default) |
344
- | `"ring"` | Ringtone |
345
- | `"notification"` | Notifications |
346
- | `"alarm"` | Alarms |
347
- | `"system"` | UI sounds |
138
+ ---
348
139
 
349
- ### `getVolume(stream?)` → `Promise<number>`
140
+ ### Orientation
350
141
 
351
142
  ```ts
352
- import { getVolume } from "rn-system-bar";
143
+ import { setOrientation } from "rn-system-bar";
353
144
 
354
- const vol = await getVolume("music"); // 0.0 1.0
145
+ // "portrait" | "landscape" | "landscape-left" | "landscape-right" | "auto"
146
+ setOrientation("landscape");
147
+ setOrientation("auto"); // follow system auto-rotate
355
148
  ```
356
149
 
357
- ### `setVolumeHUDVisible(visible)` — Android only
150
+ ---
358
151
 
359
- Show or hide the system volume popup when `setVolume` is called.
152
+ ### Network
360
153
 
361
154
  ```ts
362
- import { setVolumeHUDVisible } from "rn-system-bar";
363
-
364
- setVolumeHUDVisible(false); // silent volume change — no popup
365
- setVolumeHUDVisible(true); // show the popup (default behaviour)
366
- ```
367
-
368
- ### `onVolumeChange(callback)` → unsubscribe
155
+ import { getNetworkInfo, onNetworkChange } from "rn-system-bar";
369
156
 
370
- Fires when the volume changes via hardware buttons or another app.
371
-
372
- ```ts
373
- import { onVolumeChange } from "rn-system-bar";
157
+ const info = await getNetworkInfo();
158
+ // {
159
+ // type: "wifi" | "cellular" | "none" | "unknown",
160
+ // isConnected: boolean,
161
+ // isAirplaneMode: boolean, // Android only
162
+ // ssid: string | null, // Android only, requires location permission
163
+ // cellularGeneration: string | null, // Android only
164
+ // }
374
165
 
375
- const unsub = onVolumeChange((volume, stream) => {
376
- console.log(`${stream} volume: ${volume}`); // e.g. "music volume: 0.7"
166
+ const unsub = onNetworkChange((info) => {
167
+ console.log("Network:", info.type, info.isConnected);
377
168
  });
378
-
379
- // Stop listening
380
169
  unsub();
381
170
  ```
382
171
 
383
172
  ---
384
173
 
385
- ## 📺 Screen
386
-
387
- ### `keepScreenOn(enable)` — Android + iOS
388
-
389
- Prevents the screen from sleeping while the app is in the foreground.
390
-
391
- ```ts
392
- import { keepScreenOn } from "rn-system-bar";
393
-
394
- keepScreenOn(true); // prevent sleep
395
- keepScreenOn(false); // allow sleep (default)
396
- ```
397
-
398
- ### `immersiveMode(enable)` — Android only
399
-
400
- Hides both status bar and navigation bar. Bars temporarily reappear on swipe.
174
+ ### Battery
401
175
 
402
176
  ```ts
403
- import { immersiveMode } from "rn-system-bar";
404
-
405
- immersiveMode(true); // full immersive — both bars hidden
406
- immersiveMode(false); // restore both bars
407
- ```
408
-
409
- ### `setSecureScreen(enable)` — Android only
410
-
411
- Prevents the screen from being captured via screenshots or screen recording. Applies `FLAG_SECURE` to the window.
177
+ import { getBatteryInfo, onBatteryChange } from "rn-system-bar";
412
178
 
413
- ```ts
414
- import { setSecureScreen } from "rn-system-bar";
179
+ const info = await getBatteryInfo();
180
+ // {
181
+ // level: number, // 0–100, -1 if unknown
182
+ // state: "charging" | "discharging" | "full" | "unknown",
183
+ // isCharging: boolean,
184
+ // isLow: boolean, // true when level <= 20 and not charging
185
+ // }
415
186
 
416
- setSecureScreen(true); // block screenshots and screen recording
417
- setSecureScreen(false); // allow captures (default)
187
+ const unsub = onBatteryChange((info) => {
188
+ if (info.isLow) console.warn("Battery low:", info.level);
189
+ });
190
+ unsub();
418
191
  ```
419
192
 
420
193
  ---
421
194
 
422
- ## 🔄 Orientation
423
-
424
- ### `setOrientation(mode)` — Android + iOS
195
+ ### Haptics
425
196
 
426
197
  ```ts
427
- import { setOrientation } from "rn-system-bar";
198
+ import { haptic } from "rn-system-bar";
428
199
 
429
- setOrientation("portrait"); // lock portrait
430
- setOrientation("landscape"); // lock landscape (either side)
431
- setOrientation("landscape-left"); // lock landscape — specific side
432
- setOrientation("landscape-right"); // lock landscape specific side
433
- setOrientation("auto"); // unlockfollows device sensor
200
+ haptic("light"); // subtle tap
201
+ haptic("medium"); // default
202
+ haptic("heavy"); // strong tap
203
+ haptic("success"); // notificationsuccess
204
+ haptic("warning"); // notificationwarning
205
+ haptic("error"); // notification — error
206
+ haptic("selection"); // selection tick
434
207
  ```
435
208
 
436
- > **Android note:** When the device's auto-rotate toggle is **ON**, `"portrait"` / `"landscape"` use sensor-based variants so the device can still rotate within the axis. When auto-rotate is **OFF**, orientation is hard-locked.
437
-
438
209
  ---
439
210
 
440
- ## 📡 System Screencast
441
-
442
- Detects OS-level external display state — HDMI, Miracast (Android), AirPlay mirror (iOS). This is the **system** casting state, not your app specifically.
211
+ ### System Screencast (HDMI / Miracast / AirPlay)
443
212
 
444
- ### `getSystemScreencastInfo()` `Promise<SystemScreencastInfo>`
213
+ Detects OS-level external displays — HDMI, Miracast on Android; AirPlay mirror on iOS.
445
214
 
446
215
  ```ts
447
- import { getSystemScreencastInfo } from "rn-system-bar";
216
+ import {
217
+ getSystemScreencastInfo,
218
+ onSystemScreencastChange,
219
+ } from "rn-system-bar";
448
220
 
449
221
  const info = await getSystemScreencastInfo();
450
222
  // {
451
- // isCasting: true,
452
- // displayName: "Living Room TV",
453
- // displays: [{ id: 1, name: "Living Room TV", isValid: true }]
223
+ // isCasting: boolean,
224
+ // displayName: string | null,
225
+ // displays: Array<{ id: number, name: string, isValid: boolean }>,
454
226
  // }
455
- ```
456
-
457
- ### `onSystemScreencastChange(callback)` → unsubscribe
458
-
459
- ```ts
460
- import { onSystemScreencastChange } from "rn-system-bar";
461
227
 
462
228
  const unsub = onSystemScreencastChange((info) => {
463
- if (info.isCasting) {
464
- console.log("Now casting to:", info.displayName);
465
- } else {
466
- console.log("Casting stopped");
467
- }
229
+ console.log("Casting:", info.isCasting, info.displayName);
468
230
  });
469
-
470
- // Stop listening
471
231
  unsub();
472
232
  ```
473
233
 
474
- ### `useSystemScreencast()` hook
475
-
476
- ```tsx
477
- import { useSystemScreencast } from "rn-system-bar";
478
-
479
- const { isCasting, displayName, displays } = useSystemScreencast();
480
-
481
- return <Text>{isCasting ? `Casting to ${displayName}` : "Not casting"}</Text>;
482
- ```
483
-
484
234
  ---
485
235
 
486
- ## 📲 App-Only Cast (Android)
487
-
488
- Cast **only this app's screen** to a nearby TV or Chromecast — the whole system is not mirrored. Uses Android `MediaRouter` internally, no Google Play Services required.
489
-
490
- > **iOS:** AirPlay is fully system-managed on iOS. Apple provides no public API for app-only casting. All `AppCast` functions are safe to call on iOS — they are no-ops that return idle state immediately.
491
-
492
- ### State machine
493
-
494
- ```
495
- idle → scanning → connecting → connected → disconnecting → idle
496
- ↕ error at any point
497
- ```
498
-
499
- ### `useAppCast()` hook — recommended
500
-
501
- The easiest way to manage casting. Handles initial state fetch, event subscription, and auto-stops the scan on unmount (battery-safe).
502
-
503
- ```tsx
504
- import { useAppCast } from "rn-system-bar";
505
-
506
- export default function CastScreen() {
507
- const {
508
- state, // "idle" | "scanning" | "connecting" | "connected" | "disconnecting"
509
- devices, // AppCastDevice[] — found nearby devices
510
- connectedDevice, // AppCastDevice | null — currently active
511
- error, // string | null — last error message
512
- scan, // () => void — start discovery
513
- stopScan, // () => void — stop discovery
514
- connect, // (deviceId: string, pin?: string) => void
515
- disconnect, // () => void
516
- } = useAppCast();
517
-
518
- return (
519
- <View>
520
- {state === "idle" && <Button title="Find TVs" onPress={scan} />}
521
-
522
- {state === "scanning" && (
523
- <>
524
- <Text>Scanning…</Text>
525
- <Button title="Stop" onPress={stopScan} />
526
- </>
527
- )}
528
-
529
- {devices.map((device) => (
530
- <View key={device.id}>
531
- <Text>{device.name}</Text>
532
- {device.description && <Text>{device.description}</Text>}
533
- <Button
534
- title={`Cast to ${device.name}`}
535
- onPress={() => connect(device.id)}
536
- />
537
- </View>
538
- ))}
539
-
540
- {state === "connecting" && (
541
- <Text>Connecting to {connectedDevice?.name}…</Text>
542
- )}
543
-
544
- {state === "connected" && (
545
- <>
546
- <Text>Casting to {connectedDevice?.name}</Text>
547
- <Button title="Stop Casting" onPress={disconnect} />
548
- </>
549
- )}
550
-
551
- {error && <Text style={{ color: "red" }}>Error: {error}</Text>}
552
- </View>
553
- );
554
- }
555
- ```
556
-
557
- ### With pairing PIN
558
-
559
- ```tsx
560
- const { devices, connect } = useAppCast();
561
-
562
- // If requiresPairing is true, pass the PIN as the second argument
563
- connect(device.id, "1234");
564
- ```
565
-
566
- ### Imperative API (without hook)
236
+ ### Font Scale
567
237
 
568
238
  ```ts
569
- import {
570
- startAppCastScan,
571
- stopAppCastScan,
572
- connectAppCast,
573
- disconnectAppCast,
574
- getAppCastInfo,
575
- onAppCastChange,
576
- } from "rn-system-bar";
577
-
578
- // Subscribe first
579
- const unsub = onAppCastChange((info) => {
580
- console.log("Cast state:", info.state);
581
- console.log("Devices:", info.devices);
582
- if (info.state === "connected") {
583
- console.log("Casting to:", info.connectedDevice?.name);
584
- }
585
- if (info.error) console.warn("Error:", info.error);
586
- });
587
-
588
- // Start scan
589
- startAppCastScan();
590
-
591
- // Connect when a device appears
592
- connectAppCast("device_route_id_123");
593
- // With pairing PIN
594
- connectAppCast("device_route_id_123", "9876");
239
+ import { getFontScaleInfo, onFontScaleChange } from "rn-system-bar";
595
240
 
596
- // Stop casting
597
- disconnectAppCast();
598
-
599
- // Stop scanning
600
- stopAppCastScan();
601
-
602
- // Get current state without subscribing
603
- const info = await getAppCastInfo();
241
+ const info = await getFontScaleInfo();
242
+ // {
243
+ // fontScale: number, // 1.0 = default; larger = user has increased text size
244
+ // density: number, // screen pixel density
245
+ // }
604
246
 
605
- // Clean up
247
+ const unsub = onFontScaleChange((info) => {
248
+ console.log("Font scale changed:", info.fontScale);
249
+ });
606
250
  unsub();
607
251
  ```
608
252
 
609
253
  ---
610
254
 
611
- ## 🪝 `useSystemBar()` hook
255
+ ## React Hooks
612
256
 
613
- Apply multiple system bar settings declaratively in one call. Re-applies only when the config object actually changes (deep comparison via JSON).
257
+ ### `useSystemBar` declarative config
614
258
 
615
- ```tsx
259
+ ```ts
616
260
  import { useSystemBar } from "rn-system-bar";
617
261
 
618
262
  useSystemBar({
619
- // Navigation bar (Android-only — safe to include on iOS)
620
263
  navigationBarColor: "transparent",
621
- navigationBarVisibility: "visible",
622
264
  navigationBarButtonStyle: "light",
623
- navigationBarStyle: "dark",
624
- navigationBarBehavior: "overlay-swipe",
625
-
626
- // Status bar
627
- statusBarColor: "transparent", // Android only
628
265
  statusBarStyle: "light",
629
- statusBarVisible: true,
630
-
631
- // Screen
632
266
  keepScreenOn: true,
633
- immersiveMode: false, // Android only
634
- secureScreen: false, // Android only
635
267
  brightness: 0.8,
636
268
  orientation: "portrait",
637
269
  });
638
270
  ```
639
271
 
640
- All fields are optional. Only the ones you pass will be applied.
272
+ ### `useThemeSystemBar` theme-aware config
641
273
 
642
- ---
274
+ ```ts
275
+ import { useThemeSystemBar } from "rn-system-bar";
276
+
277
+ const { isDark, mode, setMode } = useThemeSystemBar({
278
+ dark: { navigationBarColor: "#000000", statusBarStyle: "light" },
279
+ light: { navigationBarColor: "#ffffff", statusBarStyle: "dark" },
280
+ base: { keepScreenOn: true },
281
+ });
282
+ ```
643
283
 
644
- ## 📱 Platform Support Matrix
645
-
646
- | Feature | Android | iOS |
647
- | ----------------------------- | :-----: | :---: |
648
- | `setNavigationBarColor` | ✅ | ❌ |
649
- | `setNavigationBarVisibility` | ✅ | ❌ |
650
- | `setNavigationBarButtonStyle` | ✅ | ❌ |
651
- | `setNavigationBarStyle` | ✅ | ❌ |
652
- | `setNavigationBarBehavior` | ✅ | ❌ |
653
- | `setStatusBarColor` | ✅ | ❌ |
654
- | `setStatusBarStyle` | ✅ | ✅ |
655
- | `setStatusBarVisibility` | ✅ | ✅ |
656
- | `setBrightness` | ✅ | ✅ |
657
- | `getBrightness` | ✅ | ✅ |
658
- | `onBrightnessChange` | ✅ | ✅ |
659
- | `setVolume` | ✅ | ❌ \* |
660
- | `getVolume` | ✅ | ✅ |
661
- | `setVolumeHUDVisible` | ✅ | ❌ |
662
- | `onVolumeChange` | ✅ | ❌ |
663
- | `keepScreenOn` | ✅ | ✅ |
664
- | `immersiveMode` | ✅ | ❌ |
665
- | `setSecureScreen` | ✅ | ❌ |
666
- | `setOrientation` | ✅ | ✅ |
667
- | `getSystemScreencastInfo` | ✅ | ✅ |
668
- | `onSystemScreencastChange` | ✅ | ✅ |
669
- | `useSystemScreencast` | ✅ | ✅ |
670
- | `startAppCastScan` | ✅ | ❌ † |
671
- | `connectAppCast` | ✅ | ❌ † |
672
- | `disconnectAppCast` | ✅ | ❌ † |
673
- | `useAppCast` | ✅ | ❌ † |
674
- | `useTheme` | ✅ | ✅ |
675
- | `useThemeSystemBar` | ✅ | ✅ |
676
- | `useSystemBar` | ✅ | ✅ |
677
-
678
- > \* `setVolume` is a no-op on iOS — Apple does not allow apps to set system volume.
679
- > † App-only cast APIs are no-ops on iOS — AirPlay is system-managed with no public API.
284
+ ### `useSystemScreencast`
680
285
 
681
- ---
286
+ ```ts
287
+ import { useSystemScreencast } from "rn-system-bar";
682
288
 
683
- ## 📐 Full Type Reference
289
+ const { isCasting, displayName, displays } = useSystemScreencast();
290
+ ```
291
+
292
+ ### `useTheme`
684
293
 
685
294
  ```ts
686
- // Theme
687
- type ThemeMode = "system" | "dark" | "light";
688
-
689
- interface ThemeState {
690
- isDark: boolean;
691
- mode: ThemeMode;
692
- setMode: (mode: ThemeMode) => void;
693
- }
694
-
695
- // Navigation bar
696
- type NavigationBarColorValue = string | "transparent" | "translucent";
697
- type NavigationBarBehavior = "overlay-swipe" | "inset-swipe" | "inset-touch";
698
- type NavigationBarButtonStyle = "light" | "dark";
699
- type NavigationBarStyle = "auto" | "inverted" | "light" | "dark";
700
- type NavigationBarVisibility = "visible" | "hidden";
701
-
702
- // Status bar
703
- type StatusBarColorValue = string | "transparent" | "translucent";
704
- type StatusBarStyle = "light" | "dark";
705
-
706
- // Orientation
707
- type Orientation =
708
- | "portrait"
709
- | "landscape"
710
- | "landscape-left"
711
- | "landscape-right"
712
- | "auto";
713
-
714
- // Volume
715
- type VolumeStream = "music" | "ring" | "notification" | "alarm" | "system";
716
-
717
- // System screencast
718
- interface ScreencastDisplay {
719
- id: number;
720
- name: string;
721
- isValid: boolean;
722
- }
723
- interface SystemScreencastInfo {
724
- isCasting: boolean;
725
- displayName: string | null;
726
- displays: ScreencastDisplay[];
727
- }
728
-
729
- // App-only cast
730
- type AppCastState =
731
- | "idle"
732
- | "scanning"
733
- | "connecting"
734
- | "connected"
735
- | "disconnecting";
736
-
737
- interface AppCastDevice {
738
- id: string;
739
- name: string;
740
- description: string | null;
741
- signalStrength: number | null; // 0–100 or null
742
- requiresPairing: boolean;
743
- }
744
-
745
- interface AppCastInfo {
746
- state: AppCastState;
747
- devices: AppCastDevice[];
748
- connectedDevice: AppCastDevice | null;
749
- error: string | null;
750
- }
295
+ import { useTheme } from "rn-system-bar";
296
+
297
+ const { isDark, mode, setMode } = useTheme();
298
+ setMode("dark"); // "dark" | "light" | "system"
751
299
  ```
752
300
 
753
301
  ---
754
302
 
755
- ## 🆕 New Architecture (TurboModules)
756
-
757
- Add to your `react-native.config.js`:
758
-
759
- ```js
760
- module.exports = {
761
- dependencies: {
762
- "rn-system-bar": {
763
- platforms: {
764
- android: {
765
- packageImportPath: "import com.systembar.SystemBarPackage;",
766
- },
767
- },
768
- },
769
- },
770
- };
771
- ```
303
+ ## Permissions
772
304
 
773
- The `specs/NativeSystemBar.ts` file is pre-configured for codegen.
305
+ ### Android
774
306
 
775
- ---
307
+ Add to `AndroidManifest.xml` if you need to write system brightness:
308
+
309
+ ```xml
310
+ <uses-permission
311
+ android:name="android.permission.WRITE_SETTINGS"
312
+ tools:ignore="ProtectedPermissions" />
313
+ ```
314
+
315
+ ### iOS
776
316
 
777
- ## 📋 Changelog
317
+ No additional permissions required. Note:
778
318
 
779
- ### v3.2 (current)
319
+ - `setVolume` is a no-op on iOS (AVAudioSession restriction).
320
+ - `setSecureScreen` / `immersiveMode` are no-ops on iOS.
321
+ - `isAirplaneMode`, `ssid`, and `cellularGeneration` in `NetworkInfo` are always `false` / `null` on iOS.
780
322
 
781
- - **New:** `setNavigationBarColor("transparent" | "translucent")` — edge-to-edge and frosted-glass nav bar
782
- - **New:** `setStatusBarColor("transparent" | "translucent")` — same for status bar
783
- - **New:** `useTheme()` — reactive OS dark/light mode with manual override (`"system"` | `"dark"` | `"light"`)
784
- - **New:** `setGlobalThemeMode()` — imperative theme setter, usable outside components
785
- - **New:** `useThemeSystemBar()` — auto-applies themed bar config on every theme change
786
- - **New:** `getSystemScreencastInfo()` / `onSystemScreencastChange()` / `useSystemScreencast()` — OS-level external display detection
787
- - **New:** `startAppCastScan()` / `connectAppCast()` / `disconnectAppCast()` / `useAppCast()` — in-app MediaRouter casting to TV/Chromecast (Android)
788
- - **Deprecated:** `getScreencastInfo()` → use `getSystemScreencastInfo()`
789
- - **Deprecated:** `onScreencastChange()` → use `onSystemScreencastChange()`
790
- - **Deprecated:** `useScreencast()` → use `useSystemScreencast()`
323
+ ---
791
324
 
792
- ### v3.1
325
+ ## License
793
326
 
794
- - Removed `expo-navigation-bar` dependency — all navigation bar APIs now go directly to native Kotlin/Swift
795
- - Added `onBrightnessChange()` realtime listener
796
- - Added `onVolumeChange()` realtime listener
797
- - Added `setSecureScreen()` for screenshot blocking
327
+ MIT