rn-system-bar 3.1.7 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # rn-system-bar v3
2
2
 
3
- > Control Android & iOS system bars, brightness, volume, orientation and screen flags from React Native.
3
+ > Control Android & iOS system bars, brightness, volume, orientation, theme, and screen casting from React Native.
4
4
 
5
5
  Supports **React Native CLI · Expo Dev Client · TypeScript · New Architecture (TurboModules)**
6
6
 
@@ -14,12 +14,13 @@ npm install rn-system-bar
14
14
  yarn add rn-system-bar
15
15
  ```
16
16
 
17
- **iOS** (after install):
17
+ **iOS** — run after install:
18
+
18
19
  ```bash
19
20
  cd ios && pod install
20
21
  ```
21
22
 
22
- **Android**: Auto-linked. No manual steps required.
23
+ **Android** — auto-linked, no manual steps required.
23
24
 
24
25
  ---
25
26
 
@@ -31,12 +32,17 @@ React Native App
31
32
 
32
33
  rn-system-bar (TypeScript API + Platform guard)
33
34
 
34
- ├─ Android ──► SystemBarModule.kt ──► Android SDK APIs
35
- ├ WindowInsetsController (API 30+)
36
- ├ AudioManager
37
- ActivityInfo
35
+ ├─ Android ──► SystemBarModule.kt
36
+ ├ WindowInsetsController (API 30+)
37
+ ├ AudioManager
38
+ ActivityInfo
39
+ │ ├ DisplayManager ← system screencast
40
+ │ └ MediaRouter ← app-only cast
38
41
 
39
- └─ iOS ──────► SystemBarModule.swift ──► UIKit / AVFoundation
42
+ └─ iOS ──────► SystemBarModule.swift
43
+ ├ UIKit / AVFoundation
44
+ ├ UIScreen.screens ← system screencast
45
+ └ (AirPlay = system-managed, no public API)
40
46
  ```
41
47
 
42
48
  ---
@@ -45,166 +51,705 @@ rn-system-bar (TypeScript API + Platform guard)
45
51
 
46
52
  ```
47
53
  rn-system-bar/
48
- ├ package.json
49
54
  ├ index.ts
50
55
  ├ rn-system-bar.podspec
51
56
 
52
57
  ├ src/
53
- │ ├ SystemBar.ts ← JS/TS API (Platform-guarded)
54
- types.ts All TypeScript types
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
55
62
 
56
63
  ├ specs/
57
- │ └ NativeSystemBar.ts ← TurboModule spec (New Architecture)
64
+ │ └ NativeSystemBar.ts ← TurboModule spec (New Architecture)
58
65
 
59
66
  ├ android/
60
- │ ├ build.gradle
61
67
  │ └ src/main/java/com/systembar/
62
- │ ├ SystemBarModule.kt ← Full Android native module
68
+ │ ├ SystemBarModule.kt
63
69
  │ └ SystemBarPackage.kt
64
70
 
65
71
  └ ios/
66
- ├ SystemBarModule.swift ← iOS native module
67
- └ SystemBarModule.m ← ObjC bridge header
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
+ }
68
106
  ```
69
107
 
70
108
  ---
71
109
 
72
- ## 🚀 API Reference
110
+ ## 🎨 Theme
111
+
112
+ ### `useTheme()`
73
113
 
74
- ### Navigation Bar `Android only`
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.
75
115
 
76
- > All `NavigationBar` APIs are automatically guarded with `Platform.OS === "android"`.
77
- > Calling them on iOS prints a dev warning and is a no-op — no crash.
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
78
136
 
79
137
  ```ts
80
- import {
81
- setNavigationBarColor,
82
- setNavigationBarVisibility,
83
- setNavigationBarButtonStyle,
84
- setNavigationBarStyle,
85
- setNavigationBarBehavior,
86
- } from "rn-system-bar";
138
+ import { setGlobalThemeMode } from "rn-system-bar";
87
139
 
88
- setNavigationBarColor("#1a1a2e"); // hex color
89
- setNavigationBarVisibility("hidden"); // "visible" | "hidden"
90
- setNavigationBarButtonStyle("light"); // "light" | "dark"
91
- setNavigationBarStyle("dark"); // "auto" | "inverted" | "light" | "dark"
92
- setNavigationBarBehavior("overlay-swipe"); // "overlay-swipe" | "inset-swipe" | "inset-touch"
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")} />
93
177
  ```
94
178
 
95
179
  ---
96
180
 
97
- ### Status Bar
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 |
98
193
 
99
194
  ```ts
100
- import {
101
- setStatusBarColor,
102
- setStatusBarStyle,
103
- setStatusBarVisibility,
104
- } from "rn-system-bar";
195
+ import { setNavigationBarColor } from "rn-system-bar";
196
+
197
+ setNavigationBarColor("#1a1a2e"); // solid hex
198
+ setNavigationBarColor("transparent"); // edge-to-edge / glass
199
+ setNavigationBarColor("translucent"); // frosted glass
200
+ ```
201
+
202
+ ### `setNavigationBarVisibility(mode)`
203
+
204
+ ```ts
205
+ import { setNavigationBarVisibility } from "rn-system-bar";
105
206
 
106
- setStatusBarColor("#000000"); // Android only (iOS ignores bg color)
107
- setStatusBarStyle("light"); // "light" | "dark" — works on both platforms
108
- setStatusBarVisibility(false); // hide status bar
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
233
+ ```
234
+
235
+ ### `setNavigationBarBehavior(behavior)`
236
+
237
+ Controls how hidden bars re-appear after a swipe gesture.
238
+
239
+ ```ts
240
+ import { setNavigationBarBehavior } from "rn-system-bar";
241
+
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
245
+ ```
246
+
247
+ ---
248
+
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
268
+
269
+ ```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
277
+
278
+ ```ts
279
+ import { setStatusBarVisibility } from "rn-system-bar";
280
+
281
+ setStatusBarVisibility(false); // hide status bar (fullscreen)
282
+ setStatusBarVisibility(true); // show status bar
109
283
  ```
110
284
 
111
285
  ---
112
286
 
113
- ### Brightness
287
+ ## ☀️ Brightness
288
+
289
+ ### `setBrightness(level)`
114
290
 
115
291
  ```ts
116
- import { setBrightness, getBrightness } from "rn-system-bar";
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
+ ```
117
298
 
118
- setBrightness(0.8); // 0.0 – 1.0
299
+ ### `getBrightness()` `Promise<number>`
119
300
 
120
- const level = await getBrightness(); // returns 0.0 – 1.0
301
+ ```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
309
+
310
+ Fires on every brightness change. On Android, polls every 500 ms since `Settings.System` doesn't broadcast changes.
311
+
312
+ ```ts
313
+ import { onBrightnessChange } from "rn-system-bar";
314
+
315
+ const unsub = onBrightnessChange((brightness) => {
316
+ console.log("New brightness:", brightness); // 0.0 – 1.0
317
+ });
318
+
319
+ // Stop listening
320
+ unsub();
121
321
  ```
122
322
 
123
323
  ---
124
324
 
125
- ### Volume
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.
126
330
 
127
331
  ```ts
128
- import { setVolume, getVolume, setVolumeHUDVisible } from "rn-system-bar";
332
+ import { setVolume } from "rn-system-bar";
129
333
 
130
- setVolumeHUDVisible(false); // hide the system volume popup (Android)
131
- setVolume(0.5, "music"); // 0.0 – 1.0, stream: "music" | "ring" | "notification" | "alarm" | "system"
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");
339
+ ```
340
+
341
+ | Stream | Description |
342
+ | ---------------- | ----------------------- |
343
+ | `"music"` | Media / music (default) |
344
+ | `"ring"` | Ringtone |
345
+ | `"notification"` | Notifications |
346
+ | `"alarm"` | Alarms |
347
+ | `"system"` | UI sounds |
348
+
349
+ ### `getVolume(stream?)` → `Promise<number>`
350
+
351
+ ```ts
352
+ import { getVolume } from "rn-system-bar";
353
+
354
+ const vol = await getVolume("music"); // 0.0 – 1.0
355
+ ```
132
356
 
133
- const vol = await getVolume("music"); // returns 0.0 – 1.0
357
+ ### `setVolumeHUDVisible(visible)` Android only
358
+
359
+ Show or hide the system volume popup when `setVolume` is called.
360
+
361
+ ```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)
134
366
  ```
135
367
 
136
- > **iOS note**: `setVolume()` is a no-op on iOS (Apple restriction). `getVolume()` works.
368
+ ### `onVolumeChange(callback)` unsubscribe
369
+
370
+ Fires when the volume changes via hardware buttons or another app.
371
+
372
+ ```ts
373
+ import { onVolumeChange } from "rn-system-bar";
374
+
375
+ const unsub = onVolumeChange((volume, stream) => {
376
+ console.log(`${stream} volume: ${volume}`); // e.g. "music volume: 0.7"
377
+ });
378
+
379
+ // Stop listening
380
+ unsub();
381
+ ```
137
382
 
138
383
  ---
139
384
 
140
- ### Screen
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.
401
+
402
+ ```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.
141
412
 
142
413
  ```ts
143
- import { keepScreenOn, immersiveMode } from "rn-system-bar";
414
+ import { setSecureScreen } from "rn-system-bar";
144
415
 
145
- keepScreenOn(true); // prevent sleep
146
- immersiveMode(true); // hide status + nav bar (Android only)
416
+ setSecureScreen(true); // block screenshots and screen recording
417
+ setSecureScreen(false); // allow captures (default)
147
418
  ```
148
419
 
149
420
  ---
150
421
 
151
- ### Orientation
422
+ ## 🔄 Orientation
423
+
424
+ ### `setOrientation(mode)` — Android + iOS
152
425
 
153
426
  ```ts
154
427
  import { setOrientation } from "rn-system-bar";
155
428
 
156
- setOrientation("portrait"); // lock portrait
157
- setOrientation("landscape"); // lock landscape
158
- setOrientation("landscape-left"); // specific side
159
- setOrientation("landscape-right");
160
- setOrientation("auto"); // unlock — sensor-driven
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"); // unlock — follows device sensor
161
434
  ```
162
435
 
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
+
163
438
  ---
164
439
 
165
- ## 📱 Full Example
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.
443
+
444
+ ### `getSystemScreencastInfo()` → `Promise<SystemScreencastInfo>`
445
+
446
+ ```ts
447
+ import { getSystemScreencastInfo } from "rn-system-bar";
448
+
449
+ const info = await getSystemScreencastInfo();
450
+ // {
451
+ // isCasting: true,
452
+ // displayName: "Living Room TV",
453
+ // displays: [{ id: 1, name: "Living Room TV", isValid: true }]
454
+ // }
455
+ ```
456
+
457
+ ### `onSystemScreencastChange(callback)` → unsubscribe
458
+
459
+ ```ts
460
+ import { onSystemScreencastChange } from "rn-system-bar";
461
+
462
+ const unsub = onSystemScreencastChange((info) => {
463
+ if (info.isCasting) {
464
+ console.log("Now casting to:", info.displayName);
465
+ } else {
466
+ console.log("Casting stopped");
467
+ }
468
+ });
469
+
470
+ // Stop listening
471
+ unsub();
472
+ ```
473
+
474
+ ### `useSystemScreencast()` hook
166
475
 
167
476
  ```tsx
168
- import React, { useEffect } from "react";
169
- import { View, Button, Platform } from "react-native";
170
- import * as SystemBar from "rn-system-bar";
477
+ import { useSystemScreencast } from "rn-system-bar";
171
478
 
172
- export default function App() {
479
+ const { isCasting, displayName, displays } = useSystemScreencast();
480
+
481
+ return <Text>{isCasting ? `Casting to ${displayName}` : "Not casting"}</Text>;
482
+ ```
483
+
484
+ ---
485
+
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.
173
489
 
174
- useEffect(() => {
175
- // Works on both platforms
176
- SystemBar.setStatusBarStyle("light");
177
- SystemBar.setBrightness(0.9);
178
- SystemBar.keepScreenOn(true);
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.
179
491
 
180
- // Android-only (auto-guarded — safe to call without Platform check)
181
- SystemBar.setNavigationBarColor("#000000");
182
- SystemBar.setNavigationBarButtonStyle("light");
183
- SystemBar.setNavigationBarBehavior("overlay-swipe");
184
- }, []);
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();
185
517
 
186
518
  return (
187
519
  <View>
188
- <Button
189
- title="Immersive Mode"
190
- onPress={() => SystemBar.immersiveMode(true)}
191
- />
192
- <Button
193
- title="Landscape"
194
- onPress={() => SystemBar.setOrientation("landscape")}
195
- />
196
- <Button
197
- title="Get Brightness"
198
- onPress={async () => {
199
- const b = await SystemBar.getBrightness();
200
- console.log("Brightness:", b);
201
- }}
202
- />
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>}
203
552
  </View>
204
553
  );
205
554
  }
206
555
  ```
207
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)
567
+
568
+ ```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");
595
+
596
+ // Stop casting
597
+ disconnectAppCast();
598
+
599
+ // Stop scanning
600
+ stopAppCastScan();
601
+
602
+ // Get current state without subscribing
603
+ const info = await getAppCastInfo();
604
+
605
+ // Clean up
606
+ unsub();
607
+ ```
608
+
609
+ ---
610
+
611
+ ## 🪝 `useSystemBar()` hook
612
+
613
+ Apply multiple system bar settings declaratively in one call. Re-applies only when the config object actually changes (deep comparison via JSON).
614
+
615
+ ```tsx
616
+ import { useSystemBar } from "rn-system-bar";
617
+
618
+ useSystemBar({
619
+ // Navigation bar (Android-only — safe to include on iOS)
620
+ navigationBarColor: "transparent",
621
+ navigationBarVisibility: "visible",
622
+ navigationBarButtonStyle: "light",
623
+ navigationBarStyle: "dark",
624
+ navigationBarBehavior: "overlay-swipe",
625
+
626
+ // Status bar
627
+ statusBarColor: "transparent", // Android only
628
+ statusBarStyle: "light",
629
+ statusBarVisible: true,
630
+
631
+ // Screen
632
+ keepScreenOn: true,
633
+ immersiveMode: false, // Android only
634
+ secureScreen: false, // Android only
635
+ brightness: 0.8,
636
+ orientation: "portrait",
637
+ });
638
+ ```
639
+
640
+ All fields are optional. Only the ones you pass will be applied.
641
+
642
+ ---
643
+
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.
680
+
681
+ ---
682
+
683
+ ## 📐 Full Type Reference
684
+
685
+ ```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
+ }
751
+ ```
752
+
208
753
  ---
209
754
 
210
755
  ## 🆕 New Architecture (TurboModules)
@@ -225,43 +770,28 @@ module.exports = {
225
770
  };
226
771
  ```
227
772
 
228
- The `specs/NativeSystemBar.ts` file is already configured for codegen via `codegenConfig` in `package.json`.
773
+ The `specs/NativeSystemBar.ts` file is pre-configured for codegen.
229
774
 
230
775
  ---
231
776
 
232
- ## 🔖 Type Reference
777
+ ## 📋 Changelog
233
778
 
234
- ```ts
235
- type NavigationBarBehavior = "overlay-swipe" | "inset-swipe" | "inset-touch";
236
- type NavigationBarButtonStyle = "light" | "dark";
237
- type NavigationBarStyle = "auto" | "inverted" | "light" | "dark";
238
- type NavigationBarVisibility = "visible" | "hidden";
239
- type StatusBarStyle = "light" | "dark";
240
- type Orientation = "portrait" | "landscape" | "landscape-left" | "landscape-right" | "auto";
241
- type VolumeStream = "music" | "ring" | "notification" | "alarm" | "system";
242
- ```
779
+ ### v3.2 (current)
243
780
 
244
- ---
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()`
791
+
792
+ ### v3.1
245
793
 
246
- ## 📋 Platform Support Matrix
247
-
248
- | Feature | Android | iOS |
249
- |----------------------------|:-------:|:---:|
250
- | setNavigationBarColor | ✅ | ❌ |
251
- | setNavigationBarVisibility | ✅ | ❌ |
252
- | setNavigationBarButtonStyle| ✅ | ❌ |
253
- | setNavigationBarStyle | ✅ | ❌ |
254
- | setNavigationBarBehavior | ✅ | ❌ |
255
- | setStatusBarColor | ✅ | ❌ |
256
- | setStatusBarStyle | ✅ | ✅ |
257
- | setStatusBarVisibility | ✅ | ✅ |
258
- | setBrightness | ✅ | ✅ |
259
- | getBrightness | ✅ | ✅ |
260
- | setVolume | ✅ | ❌* |
261
- | getVolume | ✅ | ✅ |
262
- | setVolumeHUDVisible | ✅ | ❌ |
263
- | keepScreenOn | ✅ | ✅ |
264
- | immersiveMode | ✅ | ❌ |
265
- | setOrientation | ✅ | ✅ |
266
-
267
- > *iOS: `setVolume` is a no-op due to Apple restrictions.
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