rn-system-bar 3.1.6 → 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.
package/README.md CHANGED
@@ -15,6 +15,7 @@ yarn add rn-system-bar
15
15
  ```
16
16
 
17
17
  **iOS** (after install):
18
+
18
19
  ```bash
19
20
  cd ios && pod install
20
21
  ```
@@ -85,10 +86,10 @@ import {
85
86
  setNavigationBarBehavior,
86
87
  } from "rn-system-bar";
87
88
 
88
- setNavigationBarColor("#1a1a2e"); // hex color
89
- setNavigationBarVisibility("hidden"); // "visible" | "hidden"
90
- setNavigationBarButtonStyle("light"); // "light" | "dark"
91
- setNavigationBarStyle("dark"); // "auto" | "inverted" | "light" | "dark"
89
+ setNavigationBarColor("#1a1a2e"); // hex color
90
+ setNavigationBarVisibility("hidden"); // "visible" | "hidden"
91
+ setNavigationBarButtonStyle("light"); // "light" | "dark"
92
+ setNavigationBarStyle("dark"); // "auto" | "inverted" | "light" | "dark"
92
93
  setNavigationBarBehavior("overlay-swipe"); // "overlay-swipe" | "inset-swipe" | "inset-touch"
93
94
  ```
94
95
 
@@ -103,9 +104,9 @@ import {
103
104
  setStatusBarVisibility,
104
105
  } from "rn-system-bar";
105
106
 
106
- setStatusBarColor("#000000"); // Android only (iOS ignores bg color)
107
- setStatusBarStyle("light"); // "light" | "dark" — works on both platforms
108
- setStatusBarVisibility(false); // hide status bar
107
+ setStatusBarColor("#000000"); // Android only (iOS ignores bg color)
108
+ setStatusBarStyle("light"); // "light" | "dark" — works on both platforms
109
+ setStatusBarVisibility(false); // hide status bar
109
110
  ```
110
111
 
111
112
  ---
@@ -115,9 +116,9 @@ setStatusBarVisibility(false); // hide status bar
115
116
  ```ts
116
117
  import { setBrightness, getBrightness } from "rn-system-bar";
117
118
 
118
- setBrightness(0.8); // 0.0 – 1.0
119
+ setBrightness(0.8); // 0.0 – 1.0
119
120
 
120
- const level = await getBrightness(); // returns 0.0 – 1.0
121
+ const level = await getBrightness(); // returns 0.0 – 1.0
121
122
  ```
122
123
 
123
124
  ---
@@ -127,10 +128,10 @@ const level = await getBrightness(); // returns 0.0 – 1.0
127
128
  ```ts
128
129
  import { setVolume, getVolume, setVolumeHUDVisible } from "rn-system-bar";
129
130
 
130
- setVolumeHUDVisible(false); // hide the system volume popup (Android)
131
- setVolume(0.5, "music"); // 0.0 – 1.0, stream: "music" | "ring" | "notification" | "alarm" | "system"
131
+ setVolumeHUDVisible(false); // hide the system volume popup (Android)
132
+ setVolume(0.5, "music"); // 0.0 – 1.0, stream: "music" | "ring" | "notification" | "alarm" | "system"
132
133
 
133
- const vol = await getVolume("music"); // returns 0.0 – 1.0
134
+ const vol = await getVolume("music"); // returns 0.0 – 1.0
134
135
  ```
135
136
 
136
137
  > **iOS note**: `setVolume()` is a no-op on iOS (Apple restriction). `getVolume()` works.
@@ -142,8 +143,8 @@ const vol = await getVolume("music"); // returns 0.0 – 1.0
142
143
  ```ts
143
144
  import { keepScreenOn, immersiveMode } from "rn-system-bar";
144
145
 
145
- keepScreenOn(true); // prevent sleep
146
- immersiveMode(true); // hide status + nav bar (Android only)
146
+ keepScreenOn(true); // prevent sleep
147
+ immersiveMode(true); // hide status + nav bar (Android only)
147
148
  ```
148
149
 
149
150
  ---
@@ -153,11 +154,11 @@ immersiveMode(true); // hide status + nav bar (Android o
153
154
  ```ts
154
155
  import { setOrientation } from "rn-system-bar";
155
156
 
156
- setOrientation("portrait"); // lock portrait
157
- setOrientation("landscape"); // lock landscape
158
- setOrientation("landscape-left"); // specific side
157
+ setOrientation("portrait"); // lock portrait
158
+ setOrientation("landscape"); // lock landscape
159
+ setOrientation("landscape-left"); // specific side
159
160
  setOrientation("landscape-right");
160
- setOrientation("auto"); // unlock — sensor-driven
161
+ setOrientation("auto"); // unlock — sensor-driven
161
162
  ```
162
163
 
163
164
  ---
@@ -170,7 +171,6 @@ import { View, Button, Platform } from "react-native";
170
171
  import * as SystemBar from "rn-system-bar";
171
172
 
172
173
  export default function App() {
173
-
174
174
  useEffect(() => {
175
175
  // Works on both platforms
176
176
  SystemBar.setStatusBarStyle("light");
@@ -237,7 +237,12 @@ type NavigationBarButtonStyle = "light" | "dark";
237
237
  type NavigationBarStyle = "auto" | "inverted" | "light" | "dark";
238
238
  type NavigationBarVisibility = "visible" | "hidden";
239
239
  type StatusBarStyle = "light" | "dark";
240
- type Orientation = "portrait" | "landscape" | "landscape-left" | "landscape-right" | "auto";
240
+ type Orientation =
241
+ | "portrait"
242
+ | "landscape"
243
+ | "landscape-left"
244
+ | "landscape-right"
245
+ | "auto";
241
246
  type VolumeStream = "music" | "ring" | "notification" | "alarm" | "system";
242
247
  ```
243
248
 
@@ -245,23 +250,59 @@ type VolumeStream = "music" | "ring" | "notification" | "alarm" | "system";
245
250
 
246
251
  ## 📋 Platform Support Matrix
247
252
 
248
- | Feature | Android | iOS |
249
- |----------------------------|:-------:|:---:|
250
- | setNavigationBarColor | | ❌ |
251
- | setNavigationBarVisibility | | ❌ |
252
- | setNavigationBarButtonStyle| | ❌ |
253
- | setNavigationBarStyle | | ❌ |
254
- | setNavigationBarBehavior | | ❌ |
255
- | setStatusBarColor | | ❌ |
256
- | setStatusBarStyle | | ✅ |
257
- | setStatusBarVisibility | | ✅ |
258
- | setBrightness | | ✅ |
259
- | getBrightness | | ✅ |
260
- | setVolume | | ❌* |
261
- | getVolume | | ✅ |
262
- | setVolumeHUDVisible | | ❌ |
263
- | keepScreenOn | | ✅ |
264
- | immersiveMode | | ❌ |
265
- | setOrientation | | ✅ |
266
-
267
- > *iOS: `setVolume` is a no-op due to Apple restrictions.
253
+ | Feature | Android | iOS |
254
+ | --------------------------- | :-----: | :--: |
255
+ | setNavigationBarColor | | ❌ |
256
+ | setNavigationBarVisibility | | ❌ |
257
+ | setNavigationBarButtonStyle | | ❌ |
258
+ | setNavigationBarStyle | | ❌ |
259
+ | setNavigationBarBehavior | | ❌ |
260
+ | setStatusBarColor | | ❌ |
261
+ | setStatusBarStyle | | ✅ |
262
+ | setStatusBarVisibility | | ✅ |
263
+ | setBrightness | | ✅ |
264
+ | getBrightness | | ✅ |
265
+ | setVolume | | ❌\* |
266
+ | getVolume | | ✅ |
267
+ | setVolumeHUDVisible | | ❌ |
268
+ | keepScreenOn | | ✅ |
269
+ | immersiveMode | | ❌ |
270
+ | setOrientation | | ✅ |
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
+ // };
287
+
288
+ # Use Sheet
289
+
290
+ <Button
291
+ title="Open Sheet"
292
+ variant="neutral"
293
+ rounded={bw(80)}
294
+ style={{ paddingVertical: hp(1) }}
295
+ onPress={() => sheetRef.current?.open()}
296
+ />
297
+
298
+ <BottomSheet
299
+ ref={sheetRef}
300
+ style={{
301
+ snapPoints: [0.4, 0.7, 0.9],
302
+ borderRadius: ["15"],
303
+ }}
304
+
305
+ >
306
+
307
+ <Text>BottomSheet Open</Text>
308
+ </BottomSheet>
@@ -56,11 +56,60 @@ class SystemBarModule(
56
56
  // NAVIGATION BAR — 100% native, no expo dep
57
57
  // ═══════════════════════════════════════════════
58
58
 
59
+ // ── Helpers for transparent / translucent bars ──────────────────────────
60
+
61
+ private fun applyEdgeToEdgeFlags() {
62
+ // Required so our colour / transparency actually shows through
63
+ activity()!!.window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
64
+ }
65
+
66
+ private fun setNavBarTransparent() {
67
+ val win = activity()!!.window
68
+ applyEdgeToEdgeFlags()
69
+ win.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION)
70
+ win.navigationBarColor = Color.TRANSPARENT
71
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
72
+ win.setDecorFitsSystemWindows(false)
73
+ } else {
74
+ @Suppress("DEPRECATION")
75
+ win.decorView.systemUiVisibility =
76
+ win.decorView.systemUiVisibility or
77
+ View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
78
+ View.SYSTEM_UI_FLAG_LAYOUT_STABLE
79
+ }
80
+ }
81
+
82
+ private fun setNavBarTranslucent() {
83
+ val win = activity()!!.window
84
+ win.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION)
85
+ win.clearFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
86
+ }
87
+
88
+ private fun setNavBarSolid(color: String) {
89
+ val win = activity()!!.window
90
+ applyEdgeToEdgeFlags()
91
+ win.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION)
92
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
93
+ win.setDecorFitsSystemWindows(true)
94
+ } else {
95
+ @Suppress("DEPRECATION")
96
+ win.decorView.systemUiVisibility =
97
+ win.decorView.systemUiVisibility and
98
+ View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION.inv() and
99
+ View.SYSTEM_UI_FLAG_LAYOUT_STABLE.inv()
100
+ }
101
+ try { win.navigationBarColor = Color.parseColor(color) }
102
+ catch (_: Exception) {}
103
+ }
104
+
59
105
  @ReactMethod
60
106
  fun setNavigationBarColor(color: String) {
61
107
  activity()?.runOnUiThread {
62
- try { activity()!!.window.navigationBarColor = Color.parseColor(color) }
63
- catch (_: Exception) {}
108
+ when (color.trim().lowercase()) {
109
+ "transparent" -> setNavBarTransparent()
110
+ "translucent" -> setNavBarTranslucent()
111
+ else -> setNavBarSolid(color)
112
+ }
64
113
  }
65
114
  }
66
115
 
@@ -147,6 +196,47 @@ class SystemBarModule(
147
196
  }
148
197
  }
149
198
 
199
+ @ReactMethod
200
+ fun setStatusBarColor(color: String) {
201
+ activity()?.runOnUiThread {
202
+ val win = activity()!!.window
203
+ when (color.trim().lowercase()) {
204
+ "transparent" -> {
205
+ applyEdgeToEdgeFlags()
206
+ win.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
207
+ win.statusBarColor = Color.TRANSPARENT
208
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
209
+ win.setDecorFitsSystemWindows(false)
210
+ } else {
211
+ @Suppress("DEPRECATION")
212
+ win.decorView.systemUiVisibility =
213
+ win.decorView.systemUiVisibility or
214
+ View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
215
+ View.SYSTEM_UI_FLAG_LAYOUT_STABLE
216
+ }
217
+ }
218
+ "translucent" -> {
219
+ win.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
220
+ win.clearFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
221
+ }
222
+ else -> {
223
+ applyEdgeToEdgeFlags()
224
+ win.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
225
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
226
+ win.setDecorFitsSystemWindows(true)
227
+ } else {
228
+ @Suppress("DEPRECATION")
229
+ win.decorView.systemUiVisibility =
230
+ win.decorView.systemUiVisibility and
231
+ View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN.inv()
232
+ }
233
+ try { win.statusBarColor = Color.parseColor(color) }
234
+ catch (_: Exception) {}
235
+ }
236
+ }
237
+ }
238
+ }
239
+
150
240
  @ReactMethod
151
241
  fun setStatusBarVisibility(visible: Boolean) {
152
242
  activity()?.runOnUiThread {
@@ -379,12 +469,13 @@ class SystemBarModule(
379
469
  }
380
470
 
381
471
  // ═══════════════════════════════════════════════
382
- // SCREENCAST
472
+ // SYSTEM SCREENCAST (external display / HDMI / Miracast)
473
+ // Detects OS-level external displays via DisplayManager.
383
474
  // ═══════════════════════════════════════════════
384
475
 
385
476
  private var displayListener: DisplayManager.DisplayListener? = null
386
477
 
387
- private fun buildDisplayListMap(): WritableMap {
478
+ private fun buildSystemDisplayMap(): WritableMap {
388
479
  val dm = displayManager()
389
480
  val map = Arguments.createMap()
390
481
  val displays = dm.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION)
@@ -404,33 +495,217 @@ class SystemBarModule(
404
495
  }
405
496
 
406
497
  @ReactMethod
407
- fun getScreencastInfo(promise: Promise) {
408
- try { promise.resolve(buildDisplayListMap()) }
498
+ fun getSystemScreencastInfo(promise: Promise) {
499
+ try { promise.resolve(buildSystemDisplayMap()) }
409
500
  catch (e: Exception) { promise.reject("SCREENCAST_ERROR", e.message, e) }
410
501
  }
411
502
 
412
503
  @ReactMethod
413
- fun startScreencastListener() {
504
+ fun startSystemScreencastListener() {
414
505
  if (displayListener != null) return
415
506
  val listener = object : DisplayManager.DisplayListener {
416
- override fun onDisplayAdded(id: Int) { emit("SystemBar_ScreencastChange", buildDisplayListMap()) }
417
- override fun onDisplayRemoved(id: Int) { emit("SystemBar_ScreencastChange", buildDisplayListMap()) }
418
- override fun onDisplayChanged(id: Int) { emit("SystemBar_ScreencastChange", buildDisplayListMap()) }
507
+ override fun onDisplayAdded(id: Int) { emit("SystemBar_SystemScreencastChange", buildSystemDisplayMap()) }
508
+ override fun onDisplayRemoved(id: Int) { emit("SystemBar_SystemScreencastChange", buildSystemDisplayMap()) }
509
+ override fun onDisplayChanged(id: Int) { emit("SystemBar_SystemScreencastChange", buildSystemDisplayMap()) }
419
510
  }
420
511
  displayListener = listener
421
512
  displayManager().registerDisplayListener(listener, null)
422
513
  }
423
514
 
424
515
  @ReactMethod
425
- fun stopScreencastListener() {
516
+ fun stopSystemScreencastListener() {
426
517
  displayListener?.let { displayManager().unregisterDisplayListener(it) }
427
518
  displayListener = null
428
519
  }
429
520
 
521
+ // ═══════════════════════════════════════════════
522
+ // APP-ONLY CAST (MediaRouter — Chromecast / TV)
523
+ //
524
+ // Casts ONLY this app's screen — not the whole system.
525
+ // Uses android.media.MediaRouter (API 16+, no Play Services required).
526
+ //
527
+ // Flow:
528
+ // startAppCastScan() → devices arrive via SystemBar_AppCastChange
529
+ // connectAppCast(deviceId) → state = "connecting" then "connected"
530
+ // disconnectAppCast() → state = "disconnecting" then "idle"
531
+ // ═══════════════════════════════════════════════
532
+
533
+ private val mediaRouter: android.media.MediaRouter by lazy {
534
+ reactContext.getSystemService(Context.MEDIA_ROUTER_SERVICE) as android.media.MediaRouter
535
+ }
536
+
537
+ // Current state
538
+ private var appCastState = "idle"
539
+ private var connectedRoute: android.media.MediaRouter.RouteInfo? = null
540
+ private var appCastCallback: android.media.MediaRouter.Callback? = null
541
+
542
+ // All routes discovered during the current scan (id → route)
543
+ private val discoveredRoutes = mutableMapOf<String, android.media.MediaRouter.RouteInfo>()
544
+
545
+ // ── Serialisation helpers ──────────────────
546
+
547
+ private fun routeId(r: android.media.MediaRouter.RouteInfo): String =
548
+ r.name?.toString()?.replace(" ", "_")?.plus("_${r.hashCode()}") ?: r.hashCode().toString()
549
+
550
+ private fun routeToMap(r: android.media.MediaRouter.RouteInfo): WritableMap {
551
+ val m = Arguments.createMap()
552
+ m.putString("id", routeId(r))
553
+ m.putString("name", r.name?.toString() ?: "Unknown")
554
+ val desc = r.description?.toString()
555
+ if (desc != null) m.putString("description", desc) else m.putNull("description")
556
+ // MediaRouter doesn't expose signal strength — set null
557
+ m.putNull("signalStrength")
558
+ // Consider a device to require pairing when its status matches "connecting"
559
+ m.putBoolean("requiresPairing", false)
560
+ return m
561
+ }
562
+
563
+ private fun buildAppCastMap(): WritableMap {
564
+ val map = Arguments.createMap()
565
+ map.putString("state", appCastState)
566
+ map.putNull("error")
567
+
568
+ val devArr = Arguments.createArray()
569
+ for (r in discoveredRoutes.values) devArr.pushMap(routeToMap(r))
570
+ map.putArray("devices", devArr)
571
+
572
+ val connected = connectedRoute
573
+ if (connected != null) map.putMap("connectedDevice", routeToMap(connected))
574
+ else map.putNull("connectedDevice")
575
+
576
+ return map
577
+ }
578
+
579
+ private fun emitAppCastChange(error: String? = null) {
580
+ val map = buildAppCastMap()
581
+ if (error != null) map.putString("error", error)
582
+ emit("SystemBar_AppCastChange", map)
583
+ }
584
+
585
+ // ── Scan ──────────────────────────────────
586
+
587
+ @ReactMethod
588
+ fun startAppCastScan() {
589
+ if (appCastCallback != null) return // already scanning
590
+ appCastState = "scanning"
591
+ discoveredRoutes.clear()
592
+
593
+ val cb = object : android.media.MediaRouter.Callback() {
594
+ override fun onRouteAdded(router: android.media.MediaRouter, route: android.media.MediaRouter.RouteInfo) {
595
+ // Skip the default phone speaker / earpiece
596
+ if (route.isDefault) return
597
+ val id = routeId(route)
598
+ discoveredRoutes[id] = route
599
+ emitAppCastChange()
600
+ }
601
+ override fun onRouteRemoved(router: android.media.MediaRouter, route: android.media.MediaRouter.RouteInfo) {
602
+ discoveredRoutes.remove(routeId(route))
603
+ if (connectedRoute?.let { routeId(it) } == routeId(route)) {
604
+ connectedRoute = null
605
+ appCastState = "idle"
606
+ }
607
+ emitAppCastChange()
608
+ }
609
+ override fun onRouteChanged(router: android.media.MediaRouter, route: android.media.MediaRouter.RouteInfo) {
610
+ val id = routeId(route)
611
+ if (!route.isDefault) discoveredRoutes[id] = route
612
+ emitAppCastChange()
613
+ }
614
+ override fun onRouteSelected(router: android.media.MediaRouter, type: Int, route: android.media.MediaRouter.RouteInfo) {
615
+ if (route.isDefault) return
616
+ connectedRoute = route
617
+ appCastState = "connected"
618
+ emitAppCastChange()
619
+ }
620
+ override fun onRouteUnselected(router: android.media.MediaRouter, type: Int, route: android.media.MediaRouter.RouteInfo) {
621
+ if (appCastState != "idle") {
622
+ connectedRoute = null
623
+ appCastState = "idle"
624
+ emitAppCastChange()
625
+ }
626
+ }
627
+ }
628
+
629
+ appCastCallback = cb
630
+ mainHandler.post {
631
+ mediaRouter.addCallback(
632
+ android.media.MediaRouter.ROUTE_TYPE_LIVE_VIDEO,
633
+ cb,
634
+ android.media.MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY or
635
+ android.media.MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN
636
+ )
637
+ }
638
+ emitAppCastChange()
639
+ }
640
+
641
+ @ReactMethod
642
+ fun stopAppCastScan() {
643
+ appCastCallback?.let { mainHandler.post { mediaRouter.removeCallback(it) } }
644
+ appCastCallback = null
645
+ if (appCastState == "scanning") {
646
+ appCastState = "idle"
647
+ emitAppCastChange()
648
+ }
649
+ }
650
+
651
+ // ── Connect / Disconnect ──────────────────
652
+
653
+ @ReactMethod
654
+ fun connectAppCast(deviceId: String, pairingPin: String?) {
655
+ val route = discoveredRoutes[deviceId]
656
+ if (route == null) {
657
+ val err = Arguments.createMap()
658
+ err.putString("state", appCastState)
659
+ err.putString("error", "Device not found: $deviceId")
660
+ err.putNull("connectedDevice")
661
+ err.putArray("devices", Arguments.createArray())
662
+ emit("SystemBar_AppCastChange", err)
663
+ return
664
+ }
665
+ appCastState = "connecting"
666
+ emitAppCastChange()
667
+ mainHandler.post {
668
+ try {
669
+ mediaRouter.selectRoute(android.media.MediaRouter.ROUTE_TYPE_LIVE_VIDEO, route)
670
+ // onRouteSelected callback will set state = "connected" and re-emit
671
+ } catch (e: Exception) {
672
+ appCastState = "idle"
673
+ emitAppCastChange(error = e.message ?: "Connection failed")
674
+ }
675
+ }
676
+ }
677
+
678
+ @ReactMethod
679
+ fun disconnectAppCast() {
680
+ if (connectedRoute == null) return
681
+ appCastState = "disconnecting"
682
+ emitAppCastChange()
683
+ mainHandler.post {
684
+ try {
685
+ // Select the default (phone) route to stop remote casting
686
+ val defaultRoute = mediaRouter.getDefaultRoute()
687
+ mediaRouter.selectRoute(android.media.MediaRouter.ROUTE_TYPE_LIVE_VIDEO, defaultRoute)
688
+ connectedRoute = null
689
+ appCastState = "idle"
690
+ emitAppCastChange()
691
+ } catch (e: Exception) {
692
+ appCastState = "idle"
693
+ emitAppCastChange(error = e.message ?: "Disconnect failed")
694
+ }
695
+ }
696
+ }
697
+
698
+ @ReactMethod
699
+ fun getAppCastInfo(promise: Promise) {
700
+ try { promise.resolve(buildAppCastMap()) }
701
+ catch (e: Exception) { promise.reject("APP_CAST_ERROR", e.message, e) }
702
+ }
703
+
430
704
  // ── Cleanup ──────────────────────────────────
431
705
  override fun onCatalystInstanceDestroy() {
432
706
  stopBrightnessListener()
433
707
  stopVolumeListener()
434
- stopScreencastListener()
708
+ stopSystemScreencastListener()
709
+ stopAppCastScan()
435
710
  }
436
- }
711
+ }
package/index.ts CHANGED
@@ -2,7 +2,28 @@
2
2
  // rn-system-bar · index.ts
3
3
  // ─────────────────────────────────────────────
4
4
 
5
+ // All imperative JS/TS APIs
5
6
  export * from "./src/SystemBar";
7
+
8
+ // All TypeScript types
6
9
  export * from "./src/types";
7
- export { useSystemBar, useScreencast } from "./src/useSystemBar";
8
- export type { SystemBarConfig } from "./src/useSystemBar";
10
+
11
+ // Theme hook (standalone access)
12
+ export { setGlobalThemeMode, useTheme } from "./src/useTheme";
13
+
14
+ // React hooks
15
+ export {
16
+ useAppCast,
17
+ useScreencast,
18
+ useSystemBar,
19
+ useSystemScreencast,
20
+ useTheme as useThemeHook,
21
+ useThemeSystemBar,
22
+ } from "./src/useSystemBar";
23
+
24
+ // Types from hooks
25
+ export type {
26
+ SystemBarConfig,
27
+ ThemedSystemBarConfig,
28
+ UseAppCastReturn,
29
+ } from "./src/useSystemBar";
@@ -1,5 +1,5 @@
1
1
  // ─────────────────────────────────────────────
2
- // rn-system-bar · SystemBarModule.m v5
2
+ // rn-system-bar · SystemBarModule.m v6
3
3
  // Objective-C bridge — exposes Swift to React Native
4
4
  // ─────────────────────────────────────────────
5
5
 
@@ -36,10 +36,17 @@ RCT_EXTERN_METHOD(stopBatteryListener)
36
36
  // Haptics
37
37
  RCT_EXTERN_METHOD(haptic:(NSString *)pattern)
38
38
 
39
- // Screencast
40
- RCT_EXTERN_METHOD(getScreencastInfo:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
41
- RCT_EXTERN_METHOD(startScreencastListener)
42
- RCT_EXTERN_METHOD(stopScreencastListener)
39
+ // System Screencast (external display / AirPlay mirror)
40
+ RCT_EXTERN_METHOD(getSystemScreencastInfo:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
41
+ RCT_EXTERN_METHOD(startSystemScreencastListener)
42
+ RCT_EXTERN_METHOD(stopSystemScreencastListener)
43
+
44
+ // App-only Cast (Android MediaRouter — stubs on iOS)
45
+ RCT_EXTERN_METHOD(startAppCastScan)
46
+ RCT_EXTERN_METHOD(stopAppCastScan)
47
+ RCT_EXTERN_METHOD(connectAppCast:(NSString *)deviceId pairingPin:(NSString *)pairingPin)
48
+ RCT_EXTERN_METHOD(disconnectAppCast)
49
+ RCT_EXTERN_METHOD(getAppCastInfo:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
43
50
 
44
51
  // Font Scale
45
52
  RCT_EXTERN_METHOD(getFontScaleInfo:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
@@ -58,4 +65,4 @@ RCT_EXTERN_METHOD(setStatusBarVisibility:(BOOL)visible)
58
65
  RCT_EXTERN_METHOD(setVolumeHUDVisible:(BOOL)visible)
59
66
  RCT_EXTERN_METHOD(immersiveMode:(BOOL)enable)
60
67
 
61
- @end
68
+ @end