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 +180 -650
- package/android/src/main/java/com/systembar/SystemBarModule.kt +0 -222
- package/index.ts +3 -2
- package/ios/SystemBarModule.m +1 -8
- package/ios/SystemBarModule.swift +55 -68
- package/lib/index.d.ts +2 -2
- package/lib/index.js +4 -2
- package/lib/src/SystemBar.d.ts +33 -64
- package/lib/src/SystemBar.js +95 -131
- package/lib/src/types.d.ts +43 -43
- package/lib/src/useSystemBar.d.ts +25 -24
- package/lib/src/useSystemBar.js +101 -69
- package/package.json +1 -1
- package/src/SystemBar.ts +111 -128
- package/src/types.ts +68 -51
- package/src/useSystemBar.ts +106 -135
package/README.md
CHANGED
|
@@ -1,797 +1,327 @@
|
|
|
1
|
-
# rn-system-bar
|
|
1
|
+
# rn-system-bar
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
##
|
|
7
|
+
## Installation
|
|
10
8
|
|
|
11
|
-
```
|
|
9
|
+
```sh
|
|
12
10
|
npm install rn-system-bar
|
|
13
11
|
# or
|
|
14
12
|
yarn add rn-system-bar
|
|
15
13
|
```
|
|
16
14
|
|
|
17
|
-
|
|
15
|
+
iOS — run pod install after installing:
|
|
18
16
|
|
|
19
|
-
```
|
|
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
|
-
##
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
##
|
|
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 {
|
|
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
|
|
57
|
+
setNavigationBarColor("transparent"); // edge-to-edge
|
|
199
58
|
setNavigationBarColor("translucent"); // frosted glass
|
|
200
|
-
```
|
|
201
|
-
|
|
202
|
-
### `setNavigationBarVisibility(mode)`
|
|
203
59
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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
|
-
|
|
66
|
+
---
|
|
236
67
|
|
|
237
|
-
|
|
68
|
+
### Status Bar
|
|
238
69
|
|
|
239
70
|
```ts
|
|
240
|
-
import {
|
|
71
|
+
import {
|
|
72
|
+
setStatusBarColor,
|
|
73
|
+
setStatusBarStyle,
|
|
74
|
+
setStatusBarVisibility,
|
|
75
|
+
} from "rn-system-bar";
|
|
241
76
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
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
|
-
|
|
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 {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
### `setStatusBarVisibility(visible)` — Android + iOS
|
|
87
|
+
import {
|
|
88
|
+
setBrightness,
|
|
89
|
+
getBrightness,
|
|
90
|
+
onBrightnessChange,
|
|
91
|
+
} from "rn-system-bar";
|
|
277
92
|
|
|
278
|
-
|
|
279
|
-
|
|
93
|
+
setBrightness(0.5); // 0.0–1.0
|
|
94
|
+
const level = await getBrightness(); // returns 0.0–1.0
|
|
280
95
|
|
|
281
|
-
|
|
282
|
-
|
|
96
|
+
const unsub = onBrightnessChange((brightness) => {
|
|
97
|
+
console.log("Brightness:", brightness);
|
|
98
|
+
});
|
|
99
|
+
unsub(); // stop listening
|
|
283
100
|
```
|
|
284
101
|
|
|
285
102
|
---
|
|
286
103
|
|
|
287
|
-
|
|
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 {
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
### `onBrightnessChange(callback)` → unsubscribe
|
|
107
|
+
import {
|
|
108
|
+
setVolume,
|
|
109
|
+
getVolume,
|
|
110
|
+
setVolumeHUDVisible,
|
|
111
|
+
onVolumeChange,
|
|
112
|
+
} from "rn-system-bar";
|
|
309
113
|
|
|
310
|
-
|
|
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
|
-
|
|
313
|
-
import { onBrightnessChange } from "rn-system-bar";
|
|
118
|
+
setVolumeHUDVisible(false); // Android only — suppress system volume UI
|
|
314
119
|
|
|
315
|
-
const unsub =
|
|
316
|
-
console.log(
|
|
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
|
-
|
|
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 {
|
|
131
|
+
import { keepScreenOn, immersiveMode, setSecureScreen } from "rn-system-bar";
|
|
333
132
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
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
|
-
|
|
342
|
-
| ---------------- | ----------------------- |
|
|
343
|
-
| `"music"` | Media / music (default) |
|
|
344
|
-
| `"ring"` | Ringtone |
|
|
345
|
-
| `"notification"` | Notifications |
|
|
346
|
-
| `"alarm"` | Alarms |
|
|
347
|
-
| `"system"` | UI sounds |
|
|
138
|
+
---
|
|
348
139
|
|
|
349
|
-
###
|
|
140
|
+
### Orientation
|
|
350
141
|
|
|
351
142
|
```ts
|
|
352
|
-
import {
|
|
143
|
+
import { setOrientation } from "rn-system-bar";
|
|
353
144
|
|
|
354
|
-
|
|
145
|
+
// "portrait" | "landscape" | "landscape-left" | "landscape-right" | "auto"
|
|
146
|
+
setOrientation("landscape");
|
|
147
|
+
setOrientation("auto"); // follow system auto-rotate
|
|
355
148
|
```
|
|
356
149
|
|
|
357
|
-
|
|
150
|
+
---
|
|
358
151
|
|
|
359
|
-
|
|
152
|
+
### Network
|
|
360
153
|
|
|
361
154
|
```ts
|
|
362
|
-
import {
|
|
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
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
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 =
|
|
376
|
-
console.log(
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
414
|
-
|
|
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
|
-
|
|
417
|
-
|
|
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
|
-
|
|
423
|
-
|
|
424
|
-
### `setOrientation(mode)` — Android + iOS
|
|
195
|
+
### Haptics
|
|
425
196
|
|
|
426
197
|
```ts
|
|
427
|
-
import {
|
|
198
|
+
import { haptic } from "rn-system-bar";
|
|
428
199
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
200
|
+
haptic("light"); // subtle tap
|
|
201
|
+
haptic("medium"); // default
|
|
202
|
+
haptic("heavy"); // strong tap
|
|
203
|
+
haptic("success"); // notification — success
|
|
204
|
+
haptic("warning"); // notification — warning
|
|
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
|
-
|
|
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
|
-
|
|
213
|
+
Detects OS-level external displays — HDMI, Miracast on Android; AirPlay mirror on iOS.
|
|
445
214
|
|
|
446
215
|
```ts
|
|
447
|
-
import {
|
|
216
|
+
import {
|
|
217
|
+
getSystemScreencastInfo,
|
|
218
|
+
onSystemScreencastChange,
|
|
219
|
+
} from "rn-system-bar";
|
|
448
220
|
|
|
449
221
|
const info = await getSystemScreencastInfo();
|
|
450
222
|
// {
|
|
451
|
-
// isCasting:
|
|
452
|
-
// displayName:
|
|
453
|
-
// displays:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
//
|
|
600
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
255
|
+
## React Hooks
|
|
612
256
|
|
|
613
|
-
|
|
257
|
+
### `useSystemBar` — declarative config
|
|
614
258
|
|
|
615
|
-
```
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
289
|
+
const { isCasting, displayName, displays } = useSystemScreencast();
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### `useTheme`
|
|
684
293
|
|
|
685
294
|
```ts
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
|
|
317
|
+
No additional permissions required. Note:
|
|
778
318
|
|
|
779
|
-
|
|
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
|
-
|
|
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
|
-
|
|
325
|
+
## License
|
|
793
326
|
|
|
794
|
-
|
|
795
|
-
- Added `onBrightnessChange()` realtime listener
|
|
796
|
-
- Added `onVolumeChange()` realtime listener
|
|
797
|
-
- Added `setSecureScreen()` for screenshot blocking
|
|
327
|
+
MIT
|