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.
@@ -7,33 +7,48 @@ import type { TurboModule } from "react-native";
7
7
  import { TurboModuleRegistry } from "react-native";
8
8
 
9
9
  export interface Spec extends TurboModule {
10
- // Navigation Bar (Android-only)
10
+ // ── Navigation Bar (Android-only) ──────────
11
+ // color: hex string | "transparent" | "translucent"
11
12
  setNavigationBarColor(color: string): void;
12
13
  setNavigationBarVisibility(mode: string): void;
13
14
  setNavigationBarButtonStyle(style: string): void;
14
15
  setNavigationBarStyle(style: string): void;
15
16
  setNavigationBarBehavior(behavior: string): void;
16
17
 
17
- // Status Bar
18
+ // ── Status Bar ─────────────────────────────
19
+ // color: hex string | "transparent" | "translucent"
18
20
  setStatusBarColor(color: string): void;
19
21
  setStatusBarStyle(style: string): void;
20
22
  setStatusBarVisibility(visible: boolean): void;
21
23
 
22
- // Brightness
24
+ // ── Brightness ─────────────────────────────
23
25
  setBrightness(level: number): void;
24
26
  getBrightness(): Promise<number>;
25
27
 
26
- // Volume
28
+ // ── Volume ─────────────────────────────────
27
29
  setVolume(level: number, stream: string): void;
28
30
  getVolume(stream: string): Promise<number>;
29
31
  setVolumeHUDVisible(visible: boolean): void;
30
32
 
31
- // Screen
33
+ // ── Screen ─────────────────────────────────
32
34
  keepScreenOn(enable: boolean): void;
33
35
  immersiveMode(enable: boolean): void;
36
+ setSecureScreen(enable: boolean): void;
34
37
 
35
- // Orientation
38
+ // ── Orientation ────────────────────────────
36
39
  setOrientation(mode: string): void;
40
+
41
+ // ── System Screencast ──────────────────────
42
+ getSystemScreencastInfo(): Promise<Object>;
43
+ startSystemScreencastListener(): void;
44
+ stopSystemScreencastListener(): void;
45
+
46
+ // ── App-only Cast (Android only) ───────────
47
+ startAppCastScan(): void;
48
+ stopAppCastScan(): void;
49
+ connectAppCast(deviceId: string, pairingPin: string | null): void;
50
+ disconnectAppCast(): void;
51
+ getAppCastInfo(): Promise<Object>;
37
52
  }
38
53
 
39
54
  export default TurboModuleRegistry.getEnforcing<Spec>("SystemBar");
package/src/SystemBar.ts CHANGED
@@ -7,13 +7,17 @@
7
7
  import { NativeModules, Platform } from "react-native";
8
8
 
9
9
  import type {
10
+ AppCastInfo,
11
+ AppCastState,
10
12
  NavigationBarBehavior,
11
13
  NavigationBarButtonStyle,
14
+ NavigationBarColorValue,
12
15
  NavigationBarStyle,
13
16
  NavigationBarVisibility,
14
17
  Orientation,
15
- ScreencastInfo,
18
+ StatusBarColorValue,
16
19
  StatusBarStyle,
20
+ SystemScreencastInfo,
17
21
  VolumeStream,
18
22
  } from "./types";
19
23
 
@@ -23,33 +27,63 @@ const isAndroid = Platform.OS === "android";
23
27
 
24
28
  const androidOnly = (name: string): boolean => {
25
29
  if (!isAndroid) {
26
- if (__DEV__) console.warn(`[rn-system-bar] ${name}() is Android-only, no-op on iOS.`);
30
+ if (__DEV__)
31
+ console.warn(`[rn-system-bar] ${name}() is Android-only, no-op on iOS.`);
27
32
  return false;
28
33
  }
29
34
  return true;
30
35
  };
31
36
 
32
- const checkNative = () => {
33
- if (!Native) throw new Error("[rn-system-bar] Native module not found. Rebuild your project.");
37
+ const checkNative = (method?: string) => {
38
+ if (!Native)
39
+ throw new Error(
40
+ "[rn-system-bar] Native module not found. Rebuild your project.",
41
+ );
42
+ if (method && typeof Native[method] !== "function")
43
+ throw new Error(
44
+ `[rn-system-bar] Native.${method} not found. Rebuild your project.`,
45
+ );
34
46
  };
35
47
 
36
48
  // ═══════════════════════════════════════════════
37
49
  // NAVIGATION BAR (Android — 100% native)
38
50
  // ═══════════════════════════════════════════════
39
51
 
40
- export const setNavigationBarColor = (color: string): void => {
52
+ /**
53
+ * Set the navigation bar background colour.
54
+ *
55
+ * @param color
56
+ * - Any hex string → solid colour e.g. `"#1a1a2e"`
57
+ * - `"transparent"` → fully transparent (content draws behind bar)
58
+ * - `"translucent"` → semi-transparent (system scrim over content)
59
+ *
60
+ * @example
61
+ * setNavigationBarColor("#000000"); // solid black
62
+ * setNavigationBarColor("transparent"); // glass / edge-to-edge
63
+ * setNavigationBarColor("translucent"); // frosted glass
64
+ */
65
+ export const setNavigationBarColor = (color: NavigationBarColorValue): void => {
41
66
  if (!androidOnly("setNavigationBarColor")) return;
42
67
  checkNative();
43
- Native.setNavigationBarColor(color);
68
+ Native.setNavigationBarColor(color); // Kotlin handles the special values
44
69
  };
45
70
 
46
- export const setNavigationBarVisibility = (mode: NavigationBarVisibility): void => {
71
+ /**
72
+ * Hide or show the navigation bar.
73
+ *
74
+ * @param mode `"visible"` | `"hidden"`
75
+ */
76
+ export const setNavigationBarVisibility = (
77
+ mode: NavigationBarVisibility,
78
+ ): void => {
47
79
  if (!androidOnly("setNavigationBarVisibility")) return;
48
80
  checkNative();
49
81
  Native.setNavigationBarVisibility(mode);
50
82
  };
51
83
 
52
- export const setNavigationBarButtonStyle = (style: NavigationBarButtonStyle): void => {
84
+ export const setNavigationBarButtonStyle = (
85
+ style: NavigationBarButtonStyle,
86
+ ): void => {
53
87
  if (!androidOnly("setNavigationBarButtonStyle")) return;
54
88
  checkNative();
55
89
  Native.setNavigationBarButtonStyle(style);
@@ -61,7 +95,9 @@ export const setNavigationBarStyle = (style: NavigationBarStyle): void => {
61
95
  Native.setNavigationBarStyle(style);
62
96
  };
63
97
 
64
- export const setNavigationBarBehavior = (behavior: NavigationBarBehavior): void => {
98
+ export const setNavigationBarBehavior = (
99
+ behavior: NavigationBarBehavior,
100
+ ): void => {
65
101
  if (!androidOnly("setNavigationBarBehavior")) return;
66
102
  checkNative();
67
103
  Native.setNavigationBarBehavior(behavior);
@@ -71,6 +107,20 @@ export const setNavigationBarBehavior = (behavior: NavigationBarBehavior): void
71
107
  // STATUS BAR (native — no RN StatusBar)
72
108
  // ═══════════════════════════════════════════════
73
109
 
110
+ /**
111
+ * Set the status bar background colour (Android only).
112
+ *
113
+ * @param color
114
+ * - Any hex string → solid colour
115
+ * - `"transparent"` → fully transparent
116
+ * - `"translucent"` → semi-transparent
117
+ */
118
+ export const setStatusBarColor = (color: StatusBarColorValue): void => {
119
+ if (!androidOnly("setStatusBarColor")) return;
120
+ checkNative();
121
+ Native.setStatusBarColor(color);
122
+ };
123
+
74
124
  export const setStatusBarStyle = (style: StatusBarStyle): void => {
75
125
  checkNative();
76
126
  Native.setStatusBarStyle(style);
@@ -96,18 +146,18 @@ export const getBrightness = (): Promise<number> => {
96
146
  };
97
147
 
98
148
  /**
99
- * Subscribe to system brightness changes (polls every 500ms on Android).
149
+ * Subscribe to system brightness changes (polls every 500 ms on Android).
100
150
  * @returns unsubscribe function
101
151
  */
102
152
  export const onBrightnessChange = (
103
- callback: (brightness: number) => void
153
+ callback: (brightness: number) => void,
104
154
  ): (() => void) => {
105
155
  checkNative();
106
156
  const { DeviceEventEmitter } = require("react-native");
107
157
  Native.startBrightnessListener();
108
158
  const sub = DeviceEventEmitter.addListener(
109
159
  "SystemBar_BrightnessChange",
110
- (e: { brightness: number }) => callback(e.brightness)
160
+ (e: { brightness: number }) => callback(e.brightness),
111
161
  );
112
162
  return () => {
113
163
  sub.remove();
@@ -119,7 +169,10 @@ export const onBrightnessChange = (
119
169
  // VOLUME
120
170
  // ═══════════════════════════════════════════════
121
171
 
122
- export const setVolume = (level: number, stream: VolumeStream = "music"): void => {
172
+ export const setVolume = (
173
+ level: number,
174
+ stream: VolumeStream = "music",
175
+ ): void => {
123
176
  checkNative();
124
177
  Native.setVolume(Math.max(0, Math.min(1, level)), stream);
125
178
  };
@@ -140,14 +193,15 @@ export const setVolumeHUDVisible = (visible: boolean): void => {
140
193
  * @returns unsubscribe function
141
194
  */
142
195
  export const onVolumeChange = (
143
- callback: (volume: number, stream: VolumeStream) => void
196
+ callback: (volume: number, stream: VolumeStream) => void,
144
197
  ): (() => void) => {
145
198
  checkNative();
146
199
  const { DeviceEventEmitter } = require("react-native");
147
200
  Native.startVolumeListener();
148
201
  const sub = DeviceEventEmitter.addListener(
149
202
  "SystemBar_VolumeChange",
150
- (e: { volume: number; stream: VolumeStream }) => callback(e.volume, e.stream)
203
+ (e: { volume: number; stream: VolumeStream }) =>
204
+ callback(e.volume, e.stream),
151
205
  );
152
206
  return () => {
153
207
  sub.remove();
@@ -186,23 +240,155 @@ export const setOrientation = (mode: Orientation): void => {
186
240
  };
187
241
 
188
242
  // ═══════════════════════════════════════════════
189
- // SCREENCAST
243
+ // SYSTEM SCREENCAST (external display / HDMI / Miracast)
244
+ // Reads DisplayManager — detects any externally mirrored display.
245
+ // ═══════════════════════════════════════════════
246
+
247
+ /**
248
+ * One-shot snapshot of system-level external display state.
249
+ * Works on both Android (DisplayManager) and iOS (UIScreen.screens).
250
+ */
251
+ export const getSystemScreencastInfo = (): Promise<SystemScreencastInfo> => {
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
+ }
263
+ return Native.getSystemScreencastInfo();
264
+ };
265
+
266
+ /**
267
+ * Subscribe to system external-display changes.
268
+ * Fires when an HDMI / Miracast / AirPlay display connects or disconnects.
269
+ * @returns unsubscribe function
270
+ */
271
+ export const onSystemScreencastChange = (
272
+ callback: (info: SystemScreencastInfo) => void,
273
+ ): (() => void) => {
274
+ checkNative();
275
+ const { DeviceEventEmitter } = require("react-native");
276
+ Native.startSystemScreencastListener();
277
+ const sub = DeviceEventEmitter.addListener(
278
+ "SystemBar_SystemScreencastChange",
279
+ callback,
280
+ );
281
+ return () => {
282
+ sub.remove();
283
+ Native.stopSystemScreencastListener();
284
+ };
285
+ };
286
+
287
+ // ─────────────────────────────────────────────
288
+ // Legacy aliases (backward compat)
289
+ // ─────────────────────────────────────────────
290
+ /** @deprecated Use getSystemScreencastInfo() */
291
+ export const getScreencastInfo = getSystemScreencastInfo;
292
+ /** @deprecated Use onSystemScreencastChange() */
293
+ export const onScreencastChange = onSystemScreencastChange;
294
+
295
+ // ═══════════════════════════════════════════════
296
+ // APP-ONLY CAST (MediaRouter — Chromecast / TV)
297
+ // Mirrors only this app's screen — NOT the whole system.
298
+ // Flow: startAppCastScan() → onAppCastChange (devices arrive)
299
+ // → connectAppCast(deviceId) → onAppCastChange (state = "connected")
300
+ // → disconnectAppCast()
190
301
  // ═══════════════════════════════════════════════
191
302
 
192
- export const getScreencastInfo = (): Promise<ScreencastInfo> => {
303
+ /**
304
+ * Start scanning for nearby castable devices (Chromecast, TV, etc.).
305
+ * Listen for results via `onAppCastChange`.
306
+ * Android: uses MediaRouter. iOS: no-op (AirPlay is system-only).
307
+ */
308
+ export const startAppCastScan = (): void => {
309
+ if (!androidOnly("startAppCastScan")) return;
310
+ checkNative();
311
+ Native.startAppCastScan();
312
+ };
313
+
314
+ /**
315
+ * Stop the device discovery scan.
316
+ */
317
+ export const stopAppCastScan = (): void => {
318
+ if (!androidOnly("stopAppCastScan")) return;
319
+ checkNative();
320
+ Native.stopAppCastScan();
321
+ };
322
+
323
+ /**
324
+ * Connect to a discovered device and begin casting this app's screen.
325
+ * @param deviceId The `id` field from `AppCastDevice` (MediaRouter route ID).
326
+ * @param pairingPin Optional PIN string if `requiresPairing` is true.
327
+ */
328
+ export const connectAppCast = (deviceId: string, pairingPin?: string): void => {
329
+ if (!androidOnly("connectAppCast")) return;
330
+ checkNative();
331
+ Native.connectAppCast(deviceId, pairingPin ?? null);
332
+ };
333
+
334
+ /**
335
+ * Disconnect the active in-app cast session.
336
+ */
337
+ export const disconnectAppCast = (): void => {
338
+ if (!androidOnly("disconnectAppCast")) return;
339
+ checkNative();
340
+ Native.disconnectAppCast();
341
+ };
342
+
343
+ /**
344
+ * Get the current in-app cast snapshot (state + device list).
345
+ */
346
+ export const getAppCastInfo = (): Promise<AppCastInfo> => {
347
+ if (!isAndroid) {
348
+ return Promise.resolve({
349
+ state: "idle" as AppCastState,
350
+ devices: [],
351
+ connectedDevice: null,
352
+ error: null,
353
+ });
354
+ }
193
355
  checkNative();
194
- return Native.getScreencastInfo();
356
+ return Native.getAppCastInfo();
195
357
  };
196
358
 
197
- export const onScreencastChange = (
198
- callback: (info: ScreencastInfo) => void
359
+ /**
360
+ * Subscribe to in-app cast state changes.
361
+ * Fires on: device discovered/lost, state changes (scanning → connecting → connected),
362
+ * pairing requests, errors.
363
+ *
364
+ * @example
365
+ * const unsub = onAppCastChange((info) => {
366
+ * if (info.state === "connected") console.log("Casting to", info.connectedDevice?.name);
367
+ * if (info.error) console.warn("Cast error:", info.error);
368
+ * });
369
+ *
370
+ * @returns unsubscribe function
371
+ */
372
+ export const onAppCastChange = (
373
+ callback: (info: AppCastInfo) => void,
199
374
  ): (() => void) => {
375
+ if (!isAndroid) {
376
+ // iOS: immediately call with idle state, return no-op
377
+ callback({
378
+ state: "idle",
379
+ devices: [],
380
+ connectedDevice: null,
381
+ error: null,
382
+ });
383
+ return () => {};
384
+ }
200
385
  checkNative();
201
386
  const { DeviceEventEmitter } = require("react-native");
202
- Native.startScreencastListener();
203
- const sub = DeviceEventEmitter.addListener("SystemBar_ScreencastChange", callback);
387
+ const sub = DeviceEventEmitter.addListener(
388
+ "SystemBar_AppCastChange",
389
+ callback,
390
+ );
204
391
  return () => {
205
392
  sub.remove();
206
- Native.stopScreencastListener();
207
393
  };
208
394
  };
package/src/types.ts CHANGED
@@ -12,6 +12,45 @@ export type NavigationBarStyle = "auto" | "inverted" | "light" | "dark";
12
12
  export type NavigationBarVisibility = "visible" | "hidden";
13
13
  export type StatusBarStyle = "light" | "dark";
14
14
 
15
+ /**
16
+ * Navigation bar color value.
17
+ *
18
+ * - Any CSS hex string → solid colour e.g. "#1a1a2e", "#000"
19
+ * - `"transparent"` → fully transparent (FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS +
20
+ * transparent color; content draws behind bar)
21
+ * - `"translucent"` → semi-transparent (FLAG_TRANSLUCENT_NAVIGATION)
22
+ */
23
+ export type NavigationBarColorValue = string | "transparent" | "translucent";
24
+
25
+ /**
26
+ * Status bar background color value.
27
+ *
28
+ * - Any CSS hex string → solid colour
29
+ * - `"transparent"` → fully transparent (draws behind content)
30
+ * - `"translucent"` → semi-transparent
31
+ */
32
+ export type StatusBarColorValue = string | "transparent" | "translucent";
33
+
34
+ // ─────────────────────────────────────────────
35
+ // Theme
36
+ // ─────────────────────────────────────────────
37
+
38
+ /**
39
+ * "system" → follow the OS Appearance (dark/light).
40
+ * "dark" → force dark.
41
+ * "light" → force light.
42
+ */
43
+ export type ThemeMode = "system" | "dark" | "light";
44
+
45
+ export interface ThemeState {
46
+ /** Resolved dark-mode flag. Always a concrete boolean. */
47
+ isDark: boolean;
48
+ /** Currently active override ("system" = following OS). */
49
+ mode: ThemeMode;
50
+ /** Set the override mode. Pass "system" to revert to OS. */
51
+ setMode: (mode: ThemeMode) => void;
52
+ }
53
+
15
54
  export type Orientation =
16
55
  | "portrait"
17
56
  | "landscape"
@@ -26,14 +65,59 @@ export type VolumeStream =
26
65
  | "alarm"
27
66
  | "system";
28
67
 
68
+ // ─────────────────────────────────────────────
69
+ // System screencast (external display / HDMI)
70
+ // ─────────────────────────────────────────────
71
+
29
72
  export interface ScreencastDisplay {
30
73
  id: number;
31
74
  name: string;
32
75
  isValid: boolean;
33
76
  }
34
77
 
35
- export interface ScreencastInfo {
78
+ /** Result of getSystemScreencastInfo() — physical/HDMI/Miracast external display. */
79
+ export interface SystemScreencastInfo {
36
80
  isCasting: boolean;
37
81
  displayName: string | null;
38
82
  displays: ScreencastDisplay[];
39
83
  }
84
+
85
+ // ─────────────────────────────────────────────
86
+ // App-only cast (MediaRouter — Chromecast / TV)
87
+ // ─────────────────────────────────────────────
88
+
89
+ /** Connection state of the in-app cast session. */
90
+ export type AppCastState =
91
+ | "idle"
92
+ | "scanning"
93
+ | "connecting"
94
+ | "connected"
95
+ | "disconnecting";
96
+
97
+ /** A discovered castable device (TV, Chromecast, etc.). */
98
+ export interface AppCastDevice {
99
+ /** Unique route ID from MediaRouter. */
100
+ id: string;
101
+ /** Human-readable device name e.g. "Living Room TV". */
102
+ name: string;
103
+ /** Device description / model string (may be null). */
104
+ description: string | null;
105
+ /** Signal strength 0–100, or null if unavailable. */
106
+ signalStrength: number | null;
107
+ /** Whether a pairing/PIN step is required. */
108
+ requiresPairing: boolean;
109
+ }
110
+
111
+ /** Full snapshot of in-app cast state. */
112
+ export interface AppCastInfo {
113
+ state: AppCastState;
114
+ /** Devices found during the last scan. */
115
+ devices: AppCastDevice[];
116
+ /** The device currently connected (or connecting). */
117
+ connectedDevice: AppCastDevice | null;
118
+ /** Error message from the last failed operation. */
119
+ error: string | null;
120
+ }
121
+
122
+ /** @deprecated Use SystemScreencastInfo */
123
+ export interface ScreencastInfo extends SystemScreencastInfo {}