rn-system-bar 3.1.7 → 3.1.8

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.
@@ -36,9 +36,22 @@ var __importStar = (this && this.__importStar) || (function () {
36
36
  };
37
37
  })();
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.useScreencast = exports.useSystemBar = void 0;
39
+ exports.useAppCast = exports.useScreencast = exports.useSystemScreencast = exports.useTheme = exports.useThemeSystemBar = exports.useSystemBar = void 0;
40
40
  const react_1 = require("react");
41
41
  const SystemBar = __importStar(require("./SystemBar"));
42
+ const useTheme_1 = require("./useTheme");
43
+ // ─────────────────────────────────────────────
44
+ // useSystemBar
45
+ // Apply system bar settings declaratively.
46
+ //
47
+ // @example
48
+ // useSystemBar({
49
+ // navigationBarColor: "transparent",
50
+ // navigationBarButtonStyle: "light",
51
+ // statusBarStyle: "light",
52
+ // keepScreenOn: true,
53
+ // });
54
+ // ─────────────────────────────────────────────
42
55
  const useSystemBar = (config) => {
43
56
  const configRef = (0, react_1.useRef)("");
44
57
  const configStr = JSON.stringify(config);
@@ -47,7 +60,6 @@ const useSystemBar = (config) => {
47
60
  return;
48
61
  configRef.current = configStr;
49
62
  const apply = async () => {
50
- // All nav bar calls are now sync (no expo-navigation-bar)
51
63
  if (config.navigationBarColor !== undefined)
52
64
  SystemBar.setNavigationBarColor(config.navigationBarColor);
53
65
  if (config.navigationBarVisibility !== undefined)
@@ -58,6 +70,8 @@ const useSystemBar = (config) => {
58
70
  SystemBar.setNavigationBarStyle(config.navigationBarStyle);
59
71
  if (config.navigationBarBehavior !== undefined)
60
72
  SystemBar.setNavigationBarBehavior(config.navigationBarBehavior);
73
+ if (config.statusBarColor !== undefined)
74
+ SystemBar.setStatusBarColor(config.statusBarColor);
61
75
  if (config.statusBarStyle !== undefined)
62
76
  SystemBar.setStatusBarStyle(config.statusBarStyle);
63
77
  if (config.statusBarVisible !== undefined)
@@ -81,20 +95,70 @@ const useSystemBar = (config) => {
81
95
  }, [configStr]);
82
96
  };
83
97
  exports.useSystemBar = useSystemBar;
98
+ const useThemeSystemBar = (config) => {
99
+ var _a, _b, _c;
100
+ const theme = (0, useTheme_1.useTheme)();
101
+ const resolved = Object.assign(Object.assign({}, ((_a = config.base) !== null && _a !== void 0 ? _a : {})), (theme.isDark ? ((_b = config.dark) !== null && _b !== void 0 ? _b : {}) : ((_c = config.light) !== null && _c !== void 0 ? _c : {})));
102
+ (0, exports.useSystemBar)(resolved);
103
+ return theme;
104
+ };
105
+ exports.useThemeSystemBar = useThemeSystemBar;
84
106
  // ─────────────────────────────────────────────
85
- // useScreencast
107
+ // Re-export useTheme + theme types
86
108
  // ─────────────────────────────────────────────
87
- const useScreencast = () => {
109
+ var useTheme_2 = require("./useTheme");
110
+ Object.defineProperty(exports, "useTheme", { enumerable: true, get: function () { return useTheme_2.useTheme; } });
111
+ // ─────────────────────────────────────────────
112
+ // useSystemScreencast
113
+ // Tracks OS-level external display state.
114
+ // Android: DisplayManager (HDMI / Miracast / Presentation display).
115
+ // iOS: UIScreen.screens (AirPlay mirror).
116
+ //
117
+ // @example
118
+ // const { isCasting, displayName, displays } = useSystemScreencast();
119
+ // ─────────────────────────────────────────────
120
+ const useSystemScreencast = () => {
88
121
  const [info, setInfo] = (0, react_1.useState)({
89
122
  isCasting: false,
90
123
  displayName: null,
91
124
  displays: [],
92
125
  });
93
126
  (0, react_1.useEffect)(() => {
94
- SystemBar.getScreencastInfo().then(setInfo).catch(() => { });
95
- const unsub = SystemBar.onScreencastChange(setInfo);
127
+ SystemBar.getSystemScreencastInfo()
128
+ .then(setInfo)
129
+ .catch(() => { });
130
+ const unsub = SystemBar.onSystemScreencastChange(setInfo);
96
131
  return () => unsub();
97
132
  }, []);
98
133
  return info;
99
134
  };
100
- exports.useScreencast = useScreencast;
135
+ exports.useSystemScreencast = useSystemScreencast;
136
+ /** @deprecated Renamed to useSystemScreencast() */
137
+ exports.useScreencast = exports.useSystemScreencast;
138
+ const useAppCast = () => {
139
+ const [info, setInfo] = (0, react_1.useState)({
140
+ state: "idle",
141
+ devices: [],
142
+ connectedDevice: null,
143
+ error: null,
144
+ });
145
+ (0, react_1.useEffect)(() => {
146
+ // Sync initial state
147
+ SystemBar.getAppCastInfo()
148
+ .then(setInfo)
149
+ .catch(() => { });
150
+ // Subscribe to all cast events (device found/lost, state changes, errors)
151
+ const unsub = SystemBar.onAppCastChange(setInfo);
152
+ return () => {
153
+ unsub();
154
+ // Auto-stop scan on unmount — avoids background battery drain
155
+ SystemBar.stopAppCastScan();
156
+ };
157
+ }, []);
158
+ const scan = (0, react_1.useCallback)(() => SystemBar.startAppCastScan(), []);
159
+ const stopScan = (0, react_1.useCallback)(() => SystemBar.stopAppCastScan(), []);
160
+ const connect = (0, react_1.useCallback)((id, pin) => SystemBar.connectAppCast(id, pin), []);
161
+ const disconnect = (0, react_1.useCallback)(() => SystemBar.disconnectAppCast(), []);
162
+ return Object.assign(Object.assign({}, info), { scan, stopScan, connect, disconnect });
163
+ };
164
+ exports.useAppCast = useAppCast;
@@ -0,0 +1,24 @@
1
+ import type { ThemeMode, ThemeState } from "./types";
2
+ /**
3
+ * Set the global theme mode from anywhere (outside a component).
4
+ * Triggers all `useTheme()` consumers.
5
+ */
6
+ export declare function setGlobalThemeMode(mode: ThemeMode): void;
7
+ /**
8
+ * React hook — subscribe to theme state.
9
+ *
10
+ * @example
11
+ * const { isDark, mode, setMode } = useTheme();
12
+ *
13
+ * // Auto nav bar colour based on theme
14
+ * useEffect(() => {
15
+ * setNavigationBarColor(isDark ? "#000000" : "#ffffff");
16
+ * setNavigationBarButtonStyle(isDark ? "light" : "dark");
17
+ * }, [isDark]);
18
+ *
19
+ * // Manual override
20
+ * <Button onPress={() => setMode("dark")} title="Force Dark" />
21
+ * <Button onPress={() => setMode("light")} title="Force Light" />
22
+ * <Button onPress={() => setMode("system")}title="Follow OS" />
23
+ */
24
+ export declare function useTheme(): ThemeState;
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ // ─────────────────────────────────────────────
3
+ // rn-system-bar · useTheme.ts
4
+ //
5
+ // Reads the OS colour scheme via React Native's
6
+ // `Appearance` API and lets the user override it
7
+ // with "dark" | "light" | "system".
8
+ //
9
+ // Usage:
10
+ // const { isDark, mode, setMode } = useTheme();
11
+ // const { isDark } = useTheme(); // read-only
12
+ // ─────────────────────────────────────────────
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.setGlobalThemeMode = setGlobalThemeMode;
15
+ exports.useTheme = useTheme;
16
+ const react_1 = require("react");
17
+ const react_native_1 = require("react-native");
18
+ // ── Module-level singleton so all consumers share state ──────────────────────
19
+ let _mode = "system";
20
+ let _systemIsDark = react_native_1.Appearance.getColorScheme() === "dark";
21
+ const _listeners = new Set();
22
+ function _notify() {
23
+ const isDark = resolveIsDark(_mode, _systemIsDark);
24
+ _listeners.forEach((fn) => fn({ isDark, mode: _mode }));
25
+ }
26
+ function resolveIsDark(mode, systemIsDark) {
27
+ if (mode === "dark")
28
+ return true;
29
+ if (mode === "light")
30
+ return false;
31
+ return systemIsDark; // "system"
32
+ }
33
+ // Keep in sync with OS changes
34
+ react_native_1.Appearance.addChangeListener(({ colorScheme }) => {
35
+ _systemIsDark = colorScheme === "dark";
36
+ _notify();
37
+ });
38
+ // ── Public API ───────────────────────────────────────────────────────────────
39
+ /**
40
+ * Set the global theme mode from anywhere (outside a component).
41
+ * Triggers all `useTheme()` consumers.
42
+ */
43
+ function setGlobalThemeMode(mode) {
44
+ _mode = mode;
45
+ _notify();
46
+ }
47
+ /**
48
+ * React hook — subscribe to theme state.
49
+ *
50
+ * @example
51
+ * const { isDark, mode, setMode } = useTheme();
52
+ *
53
+ * // Auto nav bar colour based on theme
54
+ * useEffect(() => {
55
+ * setNavigationBarColor(isDark ? "#000000" : "#ffffff");
56
+ * setNavigationBarButtonStyle(isDark ? "light" : "dark");
57
+ * }, [isDark]);
58
+ *
59
+ * // Manual override
60
+ * <Button onPress={() => setMode("dark")} title="Force Dark" />
61
+ * <Button onPress={() => setMode("light")} title="Force Light" />
62
+ * <Button onPress={() => setMode("system")}title="Follow OS" />
63
+ */
64
+ function useTheme() {
65
+ const [state, setState] = (0, react_1.useState)(() => ({
66
+ isDark: resolveIsDark(_mode, _systemIsDark),
67
+ mode: _mode,
68
+ }));
69
+ (0, react_1.useEffect)(() => {
70
+ // Sync on mount in case singleton changed between renders
71
+ setState({ isDark: resolveIsDark(_mode, _systemIsDark), mode: _mode });
72
+ _listeners.add(setState);
73
+ return () => {
74
+ _listeners.delete(setState);
75
+ };
76
+ }, []);
77
+ const setMode = (0, react_1.useCallback)((newMode) => {
78
+ setGlobalThemeMode(newMode);
79
+ }, []);
80
+ return Object.assign(Object.assign({}, state), { setMode });
81
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rn-system-bar",
3
- "version": "3.1.7",
3
+ "version": "3.1.8",
4
4
  "description": "Control Android & iOS system bars, brightness, volume, orientation and screen flags from React Native.",
5
5
  "main": "lib/index.js",
6
6
  "react-native": "lib/index.js",
@@ -37,7 +37,8 @@
37
37
  "scripts": {
38
38
  "build": "rimraf lib && npx tsc",
39
39
  "npm:prepublish": "npm publish --access public",
40
- "installing": "cd .. && npm install rn-system-bar@latest && cd rn-system-bar"
40
+ "installing": "cd .. && npm install rn-system-bar@latest && cd rn-system-bar",
41
+ "dev": "npm run build && npm run npm:prepublish && npm run installing"
41
42
  },
42
43
  "expo": {
43
44
  "plugins": [
@@ -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,18 @@
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
18
  ScreencastInfo,
19
+ StatusBarColorValue,
16
20
  StatusBarStyle,
21
+ SystemScreencastInfo,
17
22
  VolumeStream,
18
23
  } from "./types";
19
24
 
@@ -23,33 +28,59 @@ const isAndroid = Platform.OS === "android";
23
28
 
24
29
  const androidOnly = (name: string): boolean => {
25
30
  if (!isAndroid) {
26
- if (__DEV__) console.warn(`[rn-system-bar] ${name}() is Android-only, no-op on iOS.`);
31
+ if (__DEV__)
32
+ console.warn(`[rn-system-bar] ${name}() is Android-only, no-op on iOS.`);
27
33
  return false;
28
34
  }
29
35
  return true;
30
36
  };
31
37
 
32
38
  const checkNative = () => {
33
- if (!Native) throw new Error("[rn-system-bar] Native module not found. Rebuild your project.");
39
+ if (!Native)
40
+ throw new Error(
41
+ "[rn-system-bar] Native module not found. Rebuild your project.",
42
+ );
34
43
  };
35
44
 
36
45
  // ═══════════════════════════════════════════════
37
46
  // NAVIGATION BAR (Android — 100% native)
38
47
  // ═══════════════════════════════════════════════
39
48
 
40
- export const setNavigationBarColor = (color: string): void => {
49
+ /**
50
+ * Set the navigation bar background colour.
51
+ *
52
+ * @param color
53
+ * - Any hex string → solid colour e.g. `"#1a1a2e"`
54
+ * - `"transparent"` → fully transparent (content draws behind bar)
55
+ * - `"translucent"` → semi-transparent (system scrim over content)
56
+ *
57
+ * @example
58
+ * setNavigationBarColor("#000000"); // solid black
59
+ * setNavigationBarColor("transparent"); // glass / edge-to-edge
60
+ * setNavigationBarColor("translucent"); // frosted glass
61
+ */
62
+ export const setNavigationBarColor = (color: NavigationBarColorValue): void => {
41
63
  if (!androidOnly("setNavigationBarColor")) return;
42
64
  checkNative();
43
- Native.setNavigationBarColor(color);
65
+ Native.setNavigationBarColor(color); // Kotlin handles the special values
44
66
  };
45
67
 
46
- export const setNavigationBarVisibility = (mode: NavigationBarVisibility): void => {
68
+ /**
69
+ * Hide or show the navigation bar.
70
+ *
71
+ * @param mode `"visible"` | `"hidden"`
72
+ */
73
+ export const setNavigationBarVisibility = (
74
+ mode: NavigationBarVisibility,
75
+ ): void => {
47
76
  if (!androidOnly("setNavigationBarVisibility")) return;
48
77
  checkNative();
49
78
  Native.setNavigationBarVisibility(mode);
50
79
  };
51
80
 
52
- export const setNavigationBarButtonStyle = (style: NavigationBarButtonStyle): void => {
81
+ export const setNavigationBarButtonStyle = (
82
+ style: NavigationBarButtonStyle,
83
+ ): void => {
53
84
  if (!androidOnly("setNavigationBarButtonStyle")) return;
54
85
  checkNative();
55
86
  Native.setNavigationBarButtonStyle(style);
@@ -61,7 +92,9 @@ export const setNavigationBarStyle = (style: NavigationBarStyle): void => {
61
92
  Native.setNavigationBarStyle(style);
62
93
  };
63
94
 
64
- export const setNavigationBarBehavior = (behavior: NavigationBarBehavior): void => {
95
+ export const setNavigationBarBehavior = (
96
+ behavior: NavigationBarBehavior,
97
+ ): void => {
65
98
  if (!androidOnly("setNavigationBarBehavior")) return;
66
99
  checkNative();
67
100
  Native.setNavigationBarBehavior(behavior);
@@ -71,6 +104,20 @@ export const setNavigationBarBehavior = (behavior: NavigationBarBehavior): void
71
104
  // STATUS BAR (native — no RN StatusBar)
72
105
  // ═══════════════════════════════════════════════
73
106
 
107
+ /**
108
+ * Set the status bar background colour (Android only).
109
+ *
110
+ * @param color
111
+ * - Any hex string → solid colour
112
+ * - `"transparent"` → fully transparent
113
+ * - `"translucent"` → semi-transparent
114
+ */
115
+ export const setStatusBarColor = (color: StatusBarColorValue): void => {
116
+ if (!androidOnly("setStatusBarColor")) return;
117
+ checkNative();
118
+ Native.setStatusBarColor(color);
119
+ };
120
+
74
121
  export const setStatusBarStyle = (style: StatusBarStyle): void => {
75
122
  checkNative();
76
123
  Native.setStatusBarStyle(style);
@@ -96,18 +143,18 @@ export const getBrightness = (): Promise<number> => {
96
143
  };
97
144
 
98
145
  /**
99
- * Subscribe to system brightness changes (polls every 500ms on Android).
146
+ * Subscribe to system brightness changes (polls every 500 ms on Android).
100
147
  * @returns unsubscribe function
101
148
  */
102
149
  export const onBrightnessChange = (
103
- callback: (brightness: number) => void
150
+ callback: (brightness: number) => void,
104
151
  ): (() => void) => {
105
152
  checkNative();
106
153
  const { DeviceEventEmitter } = require("react-native");
107
154
  Native.startBrightnessListener();
108
155
  const sub = DeviceEventEmitter.addListener(
109
156
  "SystemBar_BrightnessChange",
110
- (e: { brightness: number }) => callback(e.brightness)
157
+ (e: { brightness: number }) => callback(e.brightness),
111
158
  );
112
159
  return () => {
113
160
  sub.remove();
@@ -119,7 +166,10 @@ export const onBrightnessChange = (
119
166
  // VOLUME
120
167
  // ═══════════════════════════════════════════════
121
168
 
122
- export const setVolume = (level: number, stream: VolumeStream = "music"): void => {
169
+ export const setVolume = (
170
+ level: number,
171
+ stream: VolumeStream = "music",
172
+ ): void => {
123
173
  checkNative();
124
174
  Native.setVolume(Math.max(0, Math.min(1, level)), stream);
125
175
  };
@@ -140,14 +190,15 @@ export const setVolumeHUDVisible = (visible: boolean): void => {
140
190
  * @returns unsubscribe function
141
191
  */
142
192
  export const onVolumeChange = (
143
- callback: (volume: number, stream: VolumeStream) => void
193
+ callback: (volume: number, stream: VolumeStream) => void,
144
194
  ): (() => void) => {
145
195
  checkNative();
146
196
  const { DeviceEventEmitter } = require("react-native");
147
197
  Native.startVolumeListener();
148
198
  const sub = DeviceEventEmitter.addListener(
149
199
  "SystemBar_VolumeChange",
150
- (e: { volume: number; stream: VolumeStream }) => callback(e.volume, e.stream)
200
+ (e: { volume: number; stream: VolumeStream }) =>
201
+ callback(e.volume, e.stream),
151
202
  );
152
203
  return () => {
153
204
  sub.remove();
@@ -186,23 +237,145 @@ export const setOrientation = (mode: Orientation): void => {
186
237
  };
187
238
 
188
239
  // ═══════════════════════════════════════════════
189
- // SCREENCAST
240
+ // SYSTEM SCREENCAST (external display / HDMI / Miracast)
241
+ // Reads DisplayManager — detects any externally mirrored display.
242
+ // ═══════════════════════════════════════════════
243
+
244
+ /**
245
+ * One-shot snapshot of system-level external display state.
246
+ * Works on both Android (DisplayManager) and iOS (UIScreen.screens).
247
+ */
248
+ export const getSystemScreencastInfo = (): Promise<SystemScreencastInfo> => {
249
+ checkNative();
250
+ return Native.getSystemScreencastInfo();
251
+ };
252
+
253
+ /**
254
+ * Subscribe to system external-display changes.
255
+ * Fires when an HDMI / Miracast / AirPlay display connects or disconnects.
256
+ * @returns unsubscribe function
257
+ */
258
+ export const onSystemScreencastChange = (
259
+ callback: (info: SystemScreencastInfo) => void,
260
+ ): (() => void) => {
261
+ checkNative();
262
+ const { DeviceEventEmitter } = require("react-native");
263
+ Native.startSystemScreencastListener();
264
+ const sub = DeviceEventEmitter.addListener(
265
+ "SystemBar_SystemScreencastChange",
266
+ callback,
267
+ );
268
+ return () => {
269
+ sub.remove();
270
+ Native.stopSystemScreencastListener();
271
+ };
272
+ };
273
+
274
+ // ─────────────────────────────────────────────
275
+ // Legacy aliases (backward compat)
276
+ // ─────────────────────────────────────────────
277
+ /** @deprecated Use getSystemScreencastInfo() */
278
+ export const getScreencastInfo = getSystemScreencastInfo;
279
+ /** @deprecated Use onSystemScreencastChange() */
280
+ export const onScreencastChange = onSystemScreencastChange;
281
+
282
+ // ═══════════════════════════════════════════════
283
+ // APP-ONLY CAST (MediaRouter — Chromecast / TV)
284
+ // Mirrors only this app's screen — NOT the whole system.
285
+ // Flow: startAppCastScan() → onAppCastChange (devices arrive)
286
+ // → connectAppCast(deviceId) → onAppCastChange (state = "connected")
287
+ // → disconnectAppCast()
190
288
  // ═══════════════════════════════════════════════
191
289
 
192
- export const getScreencastInfo = (): Promise<ScreencastInfo> => {
290
+ /**
291
+ * Start scanning for nearby castable devices (Chromecast, TV, etc.).
292
+ * Listen for results via `onAppCastChange`.
293
+ * Android: uses MediaRouter. iOS: no-op (AirPlay is system-only).
294
+ */
295
+ export const startAppCastScan = (): void => {
296
+ if (!androidOnly("startAppCastScan")) return;
297
+ checkNative();
298
+ Native.startAppCastScan();
299
+ };
300
+
301
+ /**
302
+ * Stop the device discovery scan.
303
+ */
304
+ export const stopAppCastScan = (): void => {
305
+ if (!androidOnly("stopAppCastScan")) return;
306
+ checkNative();
307
+ Native.stopAppCastScan();
308
+ };
309
+
310
+ /**
311
+ * Connect to a discovered device and begin casting this app's screen.
312
+ * @param deviceId The `id` field from `AppCastDevice` (MediaRouter route ID).
313
+ * @param pairingPin Optional PIN string if `requiresPairing` is true.
314
+ */
315
+ export const connectAppCast = (deviceId: string, pairingPin?: string): void => {
316
+ if (!androidOnly("connectAppCast")) return;
317
+ checkNative();
318
+ Native.connectAppCast(deviceId, pairingPin ?? null);
319
+ };
320
+
321
+ /**
322
+ * Disconnect the active in-app cast session.
323
+ */
324
+ export const disconnectAppCast = (): void => {
325
+ if (!androidOnly("disconnectAppCast")) return;
326
+ checkNative();
327
+ Native.disconnectAppCast();
328
+ };
329
+
330
+ /**
331
+ * Get the current in-app cast snapshot (state + device list).
332
+ */
333
+ export const getAppCastInfo = (): Promise<AppCastInfo> => {
334
+ if (!isAndroid) {
335
+ return Promise.resolve({
336
+ state: "idle" as AppCastState,
337
+ devices: [],
338
+ connectedDevice: null,
339
+ error: null,
340
+ });
341
+ }
193
342
  checkNative();
194
- return Native.getScreencastInfo();
343
+ return Native.getAppCastInfo();
195
344
  };
196
345
 
197
- export const onScreencastChange = (
198
- callback: (info: ScreencastInfo) => void
346
+ /**
347
+ * Subscribe to in-app cast state changes.
348
+ * Fires on: device discovered/lost, state changes (scanning → connecting → connected),
349
+ * pairing requests, errors.
350
+ *
351
+ * @example
352
+ * const unsub = onAppCastChange((info) => {
353
+ * if (info.state === "connected") console.log("Casting to", info.connectedDevice?.name);
354
+ * if (info.error) console.warn("Cast error:", info.error);
355
+ * });
356
+ *
357
+ * @returns unsubscribe function
358
+ */
359
+ export const onAppCastChange = (
360
+ callback: (info: AppCastInfo) => void,
199
361
  ): (() => void) => {
362
+ if (!isAndroid) {
363
+ // iOS: immediately call with idle state, return no-op
364
+ callback({
365
+ state: "idle",
366
+ devices: [],
367
+ connectedDevice: null,
368
+ error: null,
369
+ });
370
+ return () => {};
371
+ }
200
372
  checkNative();
201
373
  const { DeviceEventEmitter } = require("react-native");
202
- Native.startScreencastListener();
203
- const sub = DeviceEventEmitter.addListener("SystemBar_ScreencastChange", callback);
374
+ const sub = DeviceEventEmitter.addListener(
375
+ "SystemBar_AppCastChange",
376
+ callback,
377
+ );
204
378
  return () => {
205
379
  sub.remove();
206
- Native.stopScreencastListener();
207
380
  };
208
381
  };