rn-system-bar 3.1.8 → 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 +648 -159
- package/lib/src/SystemBar.js +12 -2
- package/lib/src/useSystemBar.d.ts +2 -2
- package/lib/src/useSystemBar.js +27 -1
- package/package.json +1 -1
- package/src/SystemBar.ts +16 -3
- package/src/useSystemBar.ts +31 -1
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
|
|
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,13 +14,13 @@ npm install rn-system-bar
|
|
|
14
14
|
yarn add rn-system-bar
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
**iOS**
|
|
17
|
+
**iOS** — run after install:
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
20
|
cd ios && pod install
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
**Android
|
|
23
|
+
**Android** — auto-linked, no manual steps required.
|
|
24
24
|
|
|
25
25
|
---
|
|
26
26
|
|
|
@@ -32,12 +32,17 @@ React Native App
|
|
|
32
32
|
▼
|
|
33
33
|
rn-system-bar (TypeScript API + Platform guard)
|
|
34
34
|
│
|
|
35
|
-
├─ Android ──► SystemBarModule.kt
|
|
36
|
-
│
|
|
37
|
-
│
|
|
38
|
-
│
|
|
35
|
+
├─ Android ──► SystemBarModule.kt
|
|
36
|
+
│ ├ WindowInsetsController (API 30+)
|
|
37
|
+
│ ├ AudioManager
|
|
38
|
+
│ ├ ActivityInfo
|
|
39
|
+
│ ├ DisplayManager ← system screencast
|
|
40
|
+
│ └ MediaRouter ← app-only cast
|
|
39
41
|
│
|
|
40
|
-
└─ iOS ──────► SystemBarModule.swift
|
|
42
|
+
└─ iOS ──────► SystemBarModule.swift
|
|
43
|
+
├ UIKit / AVFoundation
|
|
44
|
+
├ UIScreen.screens ← system screencast
|
|
45
|
+
└ (AirPlay = system-managed, no public API)
|
|
41
46
|
```
|
|
42
47
|
|
|
43
48
|
---
|
|
@@ -46,263 +51,747 @@ rn-system-bar (TypeScript API + Platform guard)
|
|
|
46
51
|
|
|
47
52
|
```
|
|
48
53
|
rn-system-bar/
|
|
49
|
-
├ package.json
|
|
50
54
|
├ index.ts
|
|
51
55
|
├ rn-system-bar.podspec
|
|
52
56
|
│
|
|
53
57
|
├ src/
|
|
54
|
-
│ ├ SystemBar.ts
|
|
55
|
-
│
|
|
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
|
|
56
62
|
│
|
|
57
63
|
├ specs/
|
|
58
|
-
│ └ NativeSystemBar.ts
|
|
64
|
+
│ └ NativeSystemBar.ts ← TurboModule spec (New Architecture)
|
|
59
65
|
│
|
|
60
66
|
├ android/
|
|
61
|
-
│ ├ build.gradle
|
|
62
67
|
│ └ src/main/java/com/systembar/
|
|
63
|
-
│ ├ SystemBarModule.kt
|
|
68
|
+
│ ├ SystemBarModule.kt
|
|
64
69
|
│ └ SystemBarPackage.kt
|
|
65
70
|
│
|
|
66
71
|
└ ios/
|
|
67
|
-
├ SystemBarModule.swift
|
|
68
|
-
└ SystemBarModule.m
|
|
72
|
+
├ SystemBarModule.swift
|
|
73
|
+
└ SystemBarModule.m
|
|
69
74
|
```
|
|
70
75
|
|
|
71
76
|
---
|
|
72
77
|
|
|
73
|
-
## 🚀
|
|
78
|
+
## 🚀 Quick Start
|
|
74
79
|
|
|
75
|
-
|
|
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
|
+
});
|
|
76
95
|
|
|
77
|
-
|
|
78
|
-
>
|
|
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
|
|
79
136
|
|
|
80
137
|
```ts
|
|
81
|
-
import {
|
|
82
|
-
setNavigationBarColor,
|
|
83
|
-
setNavigationBarVisibility,
|
|
84
|
-
setNavigationBarButtonStyle,
|
|
85
|
-
setNavigationBarStyle,
|
|
86
|
-
setNavigationBarBehavior,
|
|
87
|
-
} from "rn-system-bar";
|
|
138
|
+
import { setGlobalThemeMode } from "rn-system-bar";
|
|
88
139
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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")} />
|
|
94
177
|
```
|
|
95
178
|
|
|
96
179
|
---
|
|
97
180
|
|
|
98
|
-
|
|
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 |
|
|
99
193
|
|
|
100
194
|
```ts
|
|
101
|
-
import {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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";
|
|
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.
|
|
106
225
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
|
110
245
|
```
|
|
111
246
|
|
|
112
247
|
---
|
|
113
248
|
|
|
114
|
-
|
|
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 |
|
|
115
258
|
|
|
116
259
|
```ts
|
|
117
|
-
import {
|
|
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
|
+
```
|
|
118
266
|
|
|
119
|
-
|
|
267
|
+
### `setStatusBarStyle(style)` — Android + iOS
|
|
120
268
|
|
|
121
|
-
|
|
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
|
|
122
283
|
```
|
|
123
284
|
|
|
124
285
|
---
|
|
125
286
|
|
|
126
|
-
|
|
287
|
+
## ☀️ Brightness
|
|
288
|
+
|
|
289
|
+
### `setBrightness(level)`
|
|
127
290
|
|
|
128
291
|
```ts
|
|
129
|
-
import {
|
|
292
|
+
import { setBrightness } from "rn-system-bar";
|
|
130
293
|
|
|
131
|
-
|
|
132
|
-
|
|
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>`
|
|
300
|
+
|
|
301
|
+
```ts
|
|
302
|
+
import { getBrightness } from "rn-system-bar";
|
|
133
303
|
|
|
134
|
-
const
|
|
304
|
+
const level = await getBrightness(); // 0.0 – 1.0
|
|
305
|
+
console.log("Brightness:", level);
|
|
135
306
|
```
|
|
136
307
|
|
|
137
|
-
|
|
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();
|
|
321
|
+
```
|
|
138
322
|
|
|
139
323
|
---
|
|
140
324
|
|
|
141
|
-
|
|
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.
|
|
330
|
+
|
|
331
|
+
```ts
|
|
332
|
+
import { setVolume } from "rn-system-bar";
|
|
333
|
+
|
|
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
|
+
```
|
|
356
|
+
|
|
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)
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
### `onVolumeChange(callback)` → unsubscribe
|
|
369
|
+
|
|
370
|
+
Fires when the volume changes via hardware buttons or another app.
|
|
142
371
|
|
|
143
372
|
```ts
|
|
144
|
-
import {
|
|
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
|
+
```
|
|
382
|
+
|
|
383
|
+
---
|
|
384
|
+
|
|
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";
|
|
145
393
|
|
|
146
394
|
keepScreenOn(true); // prevent sleep
|
|
147
|
-
|
|
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.
|
|
412
|
+
|
|
413
|
+
```ts
|
|
414
|
+
import { setSecureScreen } from "rn-system-bar";
|
|
415
|
+
|
|
416
|
+
setSecureScreen(true); // block screenshots and screen recording
|
|
417
|
+
setSecureScreen(false); // allow captures (default)
|
|
148
418
|
```
|
|
149
419
|
|
|
150
420
|
---
|
|
151
421
|
|
|
152
|
-
|
|
422
|
+
## 🔄 Orientation
|
|
423
|
+
|
|
424
|
+
### `setOrientation(mode)` — Android + iOS
|
|
153
425
|
|
|
154
426
|
```ts
|
|
155
427
|
import { setOrientation } from "rn-system-bar";
|
|
156
428
|
|
|
157
429
|
setOrientation("portrait"); // lock portrait
|
|
158
|
-
setOrientation("landscape"); // lock landscape
|
|
159
|
-
setOrientation("landscape-left"); // specific side
|
|
160
|
-
setOrientation("landscape-right");
|
|
161
|
-
setOrientation("auto"); // unlock — sensor
|
|
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
|
|
162
434
|
```
|
|
163
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
|
+
|
|
164
438
|
---
|
|
165
439
|
|
|
166
|
-
##
|
|
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
|
|
167
475
|
|
|
168
476
|
```tsx
|
|
169
|
-
import
|
|
170
|
-
import { View, Button, Platform } from "react-native";
|
|
171
|
-
import * as SystemBar from "rn-system-bar";
|
|
477
|
+
import { useSystemScreencast } from "rn-system-bar";
|
|
172
478
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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.
|
|
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();
|
|
185
517
|
|
|
186
518
|
return (
|
|
187
519
|
<View>
|
|
188
|
-
<Button
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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
|
+
|
|
208
609
|
---
|
|
209
610
|
|
|
210
|
-
##
|
|
611
|
+
## 🪝 `useSystemBar()` hook
|
|
211
612
|
|
|
212
|
-
|
|
613
|
+
Apply multiple system bar settings declaratively in one call. Re-applies only when the config object actually changes (deep comparison via JSON).
|
|
213
614
|
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
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
|
+
});
|
|
226
638
|
```
|
|
227
639
|
|
|
228
|
-
|
|
640
|
+
All fields are optional. Only the ones you pass will be applied.
|
|
229
641
|
|
|
230
642
|
---
|
|
231
643
|
|
|
232
|
-
##
|
|
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
|
|
233
684
|
|
|
234
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";
|
|
235
697
|
type NavigationBarBehavior = "overlay-swipe" | "inset-swipe" | "inset-touch";
|
|
236
698
|
type NavigationBarButtonStyle = "light" | "dark";
|
|
237
699
|
type NavigationBarStyle = "auto" | "inverted" | "light" | "dark";
|
|
238
700
|
type NavigationBarVisibility = "visible" | "hidden";
|
|
701
|
+
|
|
702
|
+
// Status bar
|
|
703
|
+
type StatusBarColorValue = string | "transparent" | "translucent";
|
|
239
704
|
type StatusBarStyle = "light" | "dark";
|
|
705
|
+
|
|
706
|
+
// Orientation
|
|
240
707
|
type Orientation =
|
|
241
708
|
| "portrait"
|
|
242
709
|
| "landscape"
|
|
243
710
|
| "landscape-left"
|
|
244
711
|
| "landscape-right"
|
|
245
712
|
| "auto";
|
|
713
|
+
|
|
714
|
+
// Volume
|
|
246
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
|
+
}
|
|
247
751
|
```
|
|
248
752
|
|
|
249
753
|
---
|
|
250
754
|
|
|
251
|
-
##
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
> \*iOS: `setVolume` is a no-op due to Apple restrictions.
|
|
273
|
-
|
|
274
|
-
// useEffect(() => {
|
|
275
|
-
// checkAuth();
|
|
276
|
-
// }, []);
|
|
277
|
-
|
|
278
|
-
// const checkAuth = async () => {
|
|
279
|
-
// const token = await getAccessToken();
|
|
280
|
-
|
|
281
|
-
// if (token) {
|
|
282
|
-
// router.replace("/home");
|
|
283
|
-
// } else {
|
|
284
|
-
// router.replace("/login");
|
|
285
|
-
// }
|
|
286
|
-
// };
|
|
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
|
+
```
|
|
772
|
+
|
|
773
|
+
The `specs/NativeSystemBar.ts` file is pre-configured for codegen.
|
|
774
|
+
|
|
775
|
+
---
|
|
287
776
|
|
|
288
|
-
|
|
777
|
+
## 📋 Changelog
|
|
289
778
|
|
|
290
|
-
|
|
291
|
-
title="Open Sheet"
|
|
292
|
-
variant="neutral"
|
|
293
|
-
rounded={bw(80)}
|
|
294
|
-
style={{ paddingVertical: hp(1) }}
|
|
295
|
-
onPress={() => sheetRef.current?.open()}
|
|
296
|
-
/>
|
|
779
|
+
### v3.2 (current)
|
|
297
780
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
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()`
|
|
304
791
|
|
|
305
|
-
|
|
792
|
+
### v3.1
|
|
306
793
|
|
|
307
|
-
|
|
308
|
-
|
|
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
|
package/lib/src/SystemBar.js
CHANGED
|
@@ -17,9 +17,11 @@ const androidOnly = (name) => {
|
|
|
17
17
|
}
|
|
18
18
|
return true;
|
|
19
19
|
};
|
|
20
|
-
const checkNative = () => {
|
|
20
|
+
const checkNative = (method) => {
|
|
21
21
|
if (!Native)
|
|
22
22
|
throw new Error("[rn-system-bar] Native module not found. Rebuild your project.");
|
|
23
|
+
if (method && typeof Native[method] !== "function")
|
|
24
|
+
throw new Error(`[rn-system-bar] Native.${method} not found. Rebuild your project.`);
|
|
23
25
|
};
|
|
24
26
|
// ═══════════════════════════════════════════════
|
|
25
27
|
// NAVIGATION BAR (Android — 100% native)
|
|
@@ -207,7 +209,15 @@ exports.setOrientation = setOrientation;
|
|
|
207
209
|
* Works on both Android (DisplayManager) and iOS (UIScreen.screens).
|
|
208
210
|
*/
|
|
209
211
|
const getSystemScreencastInfo = () => {
|
|
210
|
-
|
|
212
|
+
if (!Native || typeof Native.getSystemScreencastInfo !== "function") {
|
|
213
|
+
if (__DEV__)
|
|
214
|
+
console.warn("[rn-system-bar] getSystemScreencastInfo not available on this build.");
|
|
215
|
+
return Promise.resolve({
|
|
216
|
+
isCasting: false,
|
|
217
|
+
displayName: null,
|
|
218
|
+
displays: [],
|
|
219
|
+
});
|
|
220
|
+
}
|
|
211
221
|
return Native.getSystemScreencastInfo();
|
|
212
222
|
};
|
|
213
223
|
exports.getSystemScreencastInfo = getSystemScreencastInfo;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AppCastInfo, NavigationBarBehavior, NavigationBarButtonStyle, NavigationBarColorValue, NavigationBarStyle, NavigationBarVisibility, Orientation, StatusBarColorValue, StatusBarStyle, SystemScreencastInfo, ThemeMode, ThemeState } from "./types";
|
|
1
|
+
import type { AppCastInfo, NavigationBarBehavior, NavigationBarButtonStyle, NavigationBarColorValue, NavigationBarStyle, NavigationBarVisibility, Orientation, ScreencastInfo, StatusBarColorValue, StatusBarStyle, SystemScreencastInfo, ThemeMode, ThemeState } from "./types";
|
|
2
2
|
export interface SystemBarConfig {
|
|
3
3
|
navigationBarColor?: NavigationBarColorValue;
|
|
4
4
|
navigationBarVisibility?: NavigationBarVisibility;
|
|
@@ -32,7 +32,7 @@ export { useTheme } from "./useTheme";
|
|
|
32
32
|
export type { ThemeMode, ThemeState };
|
|
33
33
|
export declare const useSystemScreencast: () => SystemScreencastInfo;
|
|
34
34
|
/** @deprecated Renamed to useSystemScreencast() */
|
|
35
|
-
export declare const useScreencast: () =>
|
|
35
|
+
export declare const useScreencast: () => ScreencastInfo;
|
|
36
36
|
export interface UseAppCastReturn extends AppCastInfo {
|
|
37
37
|
/** Start scanning for nearby castable devices (TV, Chromecast, etc.). */
|
|
38
38
|
scan: () => void;
|
package/lib/src/useSystemBar.js
CHANGED
|
@@ -134,7 +134,33 @@ const useSystemScreencast = () => {
|
|
|
134
134
|
};
|
|
135
135
|
exports.useSystemScreencast = useSystemScreencast;
|
|
136
136
|
/** @deprecated Renamed to useSystemScreencast() */
|
|
137
|
-
|
|
137
|
+
// useSystemBar.ts — useScreencast
|
|
138
|
+
const useScreencast = () => {
|
|
139
|
+
const [info, setInfo] = (0, react_1.useState)({
|
|
140
|
+
isCasting: false,
|
|
141
|
+
displayName: null,
|
|
142
|
+
displays: [],
|
|
143
|
+
});
|
|
144
|
+
(0, react_1.useEffect)(() => {
|
|
145
|
+
// Guard: if method doesn't exist on native module, bail silently
|
|
146
|
+
SystemBar.getScreencastInfo()
|
|
147
|
+
.then(setInfo)
|
|
148
|
+
.catch(() => {
|
|
149
|
+
if (__DEV__)
|
|
150
|
+
console.warn("[rn-system-bar] getSystemScreencastInfo not available. Rebuild native.");
|
|
151
|
+
});
|
|
152
|
+
let unsub;
|
|
153
|
+
try {
|
|
154
|
+
unsub = SystemBar.onScreencastChange(setInfo);
|
|
155
|
+
}
|
|
156
|
+
catch (_a) {
|
|
157
|
+
// native listener not available
|
|
158
|
+
}
|
|
159
|
+
return () => unsub === null || unsub === void 0 ? void 0 : unsub();
|
|
160
|
+
}, []);
|
|
161
|
+
return info;
|
|
162
|
+
};
|
|
163
|
+
exports.useScreencast = useScreencast;
|
|
138
164
|
const useAppCast = () => {
|
|
139
165
|
const [info, setInfo] = (0, react_1.useState)({
|
|
140
166
|
state: "idle",
|
package/package.json
CHANGED
package/src/SystemBar.ts
CHANGED
|
@@ -15,7 +15,6 @@ import type {
|
|
|
15
15
|
NavigationBarStyle,
|
|
16
16
|
NavigationBarVisibility,
|
|
17
17
|
Orientation,
|
|
18
|
-
ScreencastInfo,
|
|
19
18
|
StatusBarColorValue,
|
|
20
19
|
StatusBarStyle,
|
|
21
20
|
SystemScreencastInfo,
|
|
@@ -35,11 +34,15 @@ const androidOnly = (name: string): boolean => {
|
|
|
35
34
|
return true;
|
|
36
35
|
};
|
|
37
36
|
|
|
38
|
-
const checkNative = () => {
|
|
37
|
+
const checkNative = (method?: string) => {
|
|
39
38
|
if (!Native)
|
|
40
39
|
throw new Error(
|
|
41
40
|
"[rn-system-bar] Native module not found. Rebuild your project.",
|
|
42
41
|
);
|
|
42
|
+
if (method && typeof Native[method] !== "function")
|
|
43
|
+
throw new Error(
|
|
44
|
+
`[rn-system-bar] Native.${method} not found. Rebuild your project.`,
|
|
45
|
+
);
|
|
43
46
|
};
|
|
44
47
|
|
|
45
48
|
// ═══════════════════════════════════════════════
|
|
@@ -246,7 +249,17 @@ export const setOrientation = (mode: Orientation): void => {
|
|
|
246
249
|
* Works on both Android (DisplayManager) and iOS (UIScreen.screens).
|
|
247
250
|
*/
|
|
248
251
|
export const getSystemScreencastInfo = (): Promise<SystemScreencastInfo> => {
|
|
249
|
-
|
|
252
|
+
if (!Native || typeof Native.getSystemScreencastInfo !== "function") {
|
|
253
|
+
if (__DEV__)
|
|
254
|
+
console.warn(
|
|
255
|
+
"[rn-system-bar] getSystemScreencastInfo not available on this build.",
|
|
256
|
+
);
|
|
257
|
+
return Promise.resolve({
|
|
258
|
+
isCasting: false,
|
|
259
|
+
displayName: null,
|
|
260
|
+
displays: [],
|
|
261
|
+
});
|
|
262
|
+
}
|
|
250
263
|
return Native.getSystemScreencastInfo();
|
|
251
264
|
};
|
|
252
265
|
|
package/src/useSystemBar.ts
CHANGED
|
@@ -12,6 +12,7 @@ import type {
|
|
|
12
12
|
NavigationBarStyle,
|
|
13
13
|
NavigationBarVisibility,
|
|
14
14
|
Orientation,
|
|
15
|
+
ScreencastInfo,
|
|
15
16
|
StatusBarColorValue,
|
|
16
17
|
StatusBarStyle,
|
|
17
18
|
SystemScreencastInfo,
|
|
@@ -183,7 +184,36 @@ export const useSystemScreencast = (): SystemScreencastInfo => {
|
|
|
183
184
|
};
|
|
184
185
|
|
|
185
186
|
/** @deprecated Renamed to useSystemScreencast() */
|
|
186
|
-
|
|
187
|
+
// useSystemBar.ts — useScreencast
|
|
188
|
+
export const useScreencast = () => {
|
|
189
|
+
const [info, setInfo] = useState<ScreencastInfo>({
|
|
190
|
+
isCasting: false,
|
|
191
|
+
displayName: null,
|
|
192
|
+
displays: [],
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
useEffect(() => {
|
|
196
|
+
// Guard: if method doesn't exist on native module, bail silently
|
|
197
|
+
SystemBar.getScreencastInfo()
|
|
198
|
+
.then(setInfo)
|
|
199
|
+
.catch(() => {
|
|
200
|
+
if (__DEV__)
|
|
201
|
+
console.warn(
|
|
202
|
+
"[rn-system-bar] getSystemScreencastInfo not available. Rebuild native.",
|
|
203
|
+
);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
let unsub: (() => void) | undefined;
|
|
207
|
+
try {
|
|
208
|
+
unsub = SystemBar.onScreencastChange(setInfo);
|
|
209
|
+
} catch {
|
|
210
|
+
// native listener not available
|
|
211
|
+
}
|
|
212
|
+
return () => unsub?.();
|
|
213
|
+
}, []);
|
|
214
|
+
|
|
215
|
+
return info;
|
|
216
|
+
};
|
|
187
217
|
|
|
188
218
|
// ─────────────────────────────────────────────
|
|
189
219
|
// UseAppCastReturn + useAppCast
|