rn-system-bar 3.1.1 → 3.1.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.
@@ -1,9 +1,9 @@
1
1
  <manifest xmlns:android="http://schemas.android.com/apk/res/android"
2
- package="com.systembar">
2
+ xmlns:tools="http://schemas.android.com/tools">
3
3
 
4
4
  <!-- Required for writing system brightness -->
5
- <uses-permission android:name="android.permission.WRITE_SETTINGS"
6
- tools:ignore="ProtectedPermissions"
7
- xmlns:tools="http://schemas.android.com/tools"/>
5
+ <uses-permission
6
+ android:name="android.permission.WRITE_SETTINGS"
7
+ tools:ignore="ProtectedPermissions" />
8
8
 
9
9
  </manifest>
@@ -5,12 +5,17 @@
5
5
  package com.systembar
6
6
 
7
7
  import android.app.Activity
8
+ import android.content.BroadcastReceiver
8
9
  import android.content.Context
9
10
  import android.content.Intent
11
+ import android.content.IntentFilter
10
12
  import android.content.pm.ActivityInfo
13
+ import android.graphics.Color
11
14
  import android.hardware.display.DisplayManager
12
15
  import android.media.AudioManager
13
16
  import android.os.Build
17
+ import android.os.Handler
18
+ import android.os.Looper
14
19
  import android.provider.Settings
15
20
  import android.view.View
16
21
  import android.view.WindowInsets
@@ -20,6 +25,7 @@ import android.view.WindowManager
20
25
  import com.facebook.react.bridge.*
21
26
  import com.facebook.react.modules.core.DeviceEventManagerModule
22
27
 
28
+ @Suppress("DEPRECATION", "OVERRIDE_DEPRECATION")
23
29
  class SystemBarModule(
24
30
  private val reactContext: ReactApplicationContext
25
31
  ) : ReactContextBaseJavaModule(reactContext) {
@@ -27,12 +33,10 @@ class SystemBarModule(
27
33
  override fun getName(): String = "SystemBar"
28
34
 
29
35
  private fun activity(): Activity? = reactContext.currentActivity
36
+ private val mainHandler = Handler(Looper.getMainLooper())
30
37
 
31
- private fun audioManager() =
32
- reactContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager
33
-
34
- private fun displayManager() =
35
- reactContext.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
38
+ private fun audioManager() = reactContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager
39
+ private fun displayManager() = reactContext.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
36
40
 
37
41
  private fun streamType(stream: String): Int = when (stream) {
38
42
  "ring" -> AudioManager.STREAM_RING
@@ -42,84 +46,189 @@ class SystemBarModule(
42
46
  else -> AudioManager.STREAM_MUSIC
43
47
  }
44
48
 
49
+ private fun emit(event: String, data: WritableMap) {
50
+ reactContext
51
+ .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
52
+ .emit(event, data)
53
+ }
54
+
45
55
  // ═══════════════════════════════════════════════
46
- // NAVIGATION BAR COLOR
47
- // Direct Window API — edge-to-edge safe.
56
+ // NAVIGATION BAR — 100% native, no expo dep
48
57
  // ═══════════════════════════════════════════════
49
58
 
50
59
  @ReactMethod
51
60
  fun setNavigationBarColor(color: String) {
52
61
  activity()?.runOnUiThread {
53
- try {
54
- activity()!!.window.navigationBarColor =
55
- android.graphics.Color.parseColor(color)
56
- } catch (_: Exception) {}
62
+ try { activity()!!.window.navigationBarColor = Color.parseColor(color) }
63
+ catch (_: Exception) {}
64
+ }
65
+ }
66
+
67
+ @ReactMethod
68
+ fun setNavigationBarVisibility(mode: String) {
69
+ activity()?.runOnUiThread {
70
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
71
+ val c = activity()!!.window.insetsController ?: return@runOnUiThread
72
+ if (mode == "hidden") c.hide(WindowInsets.Type.navigationBars())
73
+ else c.show(WindowInsets.Type.navigationBars())
74
+ } else {
75
+ val dv = activity()!!.window.decorView
76
+ dv.systemUiVisibility = if (mode == "hidden") {
77
+ dv.systemUiVisibility or
78
+ View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or
79
+ View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
80
+ } else {
81
+ dv.systemUiVisibility and View.SYSTEM_UI_FLAG_HIDE_NAVIGATION.inv()
82
+ }
83
+ }
84
+ }
85
+ }
86
+
87
+ @ReactMethod
88
+ fun setNavigationBarButtonStyle(style: String) {
89
+ activity()?.runOnUiThread {
90
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
91
+ val c = activity()!!.window.insetsController ?: return@runOnUiThread
92
+ c.setSystemBarsAppearance(
93
+ if (style == "dark") WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS else 0,
94
+ WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS
95
+ )
96
+ } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
97
+ val dv = activity()!!.window.decorView
98
+ dv.systemUiVisibility = if (style == "dark")
99
+ dv.systemUiVisibility or View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
100
+ else
101
+ dv.systemUiVisibility and View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR.inv()
102
+ }
103
+ }
104
+ }
105
+
106
+ @ReactMethod
107
+ fun setNavigationBarStyle(style: String) {
108
+ // Maps to button style — "dark"/"auto" → dark icons, "light"/"inverted" → light icons
109
+ setNavigationBarButtonStyle(if (style == "dark" || style == "auto") "dark" else "light")
110
+ }
111
+
112
+ @ReactMethod
113
+ fun setNavigationBarBehavior(behavior: String) {
114
+ activity()?.runOnUiThread {
115
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
116
+ val c = activity()!!.window.insetsController ?: return@runOnUiThread
117
+ c.systemBarsBehavior = when (behavior) {
118
+ "overlay-swipe" -> WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
119
+ // inset modes — default persistent behavior
120
+ else -> WindowInsetsController.BEHAVIOR_DEFAULT
121
+ }
122
+ }
123
+ // API < 30: IMMERSIVE_STICKY is closest — no-op otherwise
124
+ }
125
+ }
126
+
127
+ // ═══════════════════════════════════════════════
128
+ // STATUS BAR — 100% native
129
+ // ═══════════════════════════════════════════════
130
+
131
+ @ReactMethod
132
+ fun setStatusBarStyle(style: String) {
133
+ activity()?.runOnUiThread {
134
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
135
+ val c = activity()!!.window.insetsController ?: return@runOnUiThread
136
+ c.setSystemBarsAppearance(
137
+ if (style == "dark") WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS else 0,
138
+ WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
139
+ )
140
+ } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
141
+ val dv = activity()!!.window.decorView
142
+ dv.systemUiVisibility = if (style == "dark")
143
+ dv.systemUiVisibility or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
144
+ else
145
+ dv.systemUiVisibility and View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.inv()
146
+ }
147
+ }
148
+ }
149
+
150
+ @ReactMethod
151
+ fun setStatusBarVisibility(visible: Boolean) {
152
+ activity()?.runOnUiThread {
153
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
154
+ val c = activity()!!.window.insetsController ?: return@runOnUiThread
155
+ if (visible) c.show(WindowInsets.Type.statusBars())
156
+ else c.hide(WindowInsets.Type.statusBars())
157
+ } else {
158
+ val dv = activity()!!.window.decorView
159
+ dv.systemUiVisibility = if (!visible)
160
+ dv.systemUiVisibility or View.SYSTEM_UI_FLAG_FULLSCREEN
161
+ else
162
+ dv.systemUiVisibility and View.SYSTEM_UI_FLAG_FULLSCREEN.inv()
163
+ }
57
164
  }
58
165
  }
59
166
 
60
167
  // ═══════════════════════════════════════════════
61
168
  // BRIGHTNESS
62
- //
63
- // setBrightness — TWO things at once:
64
- // 1. window.attributes.screenBrightness → current screen (instant)
65
- // 2. Settings.System.SCREEN_BRIGHTNESS → system brightness persisted
66
- //
67
- // Requires WRITE_SETTINGS permission.
68
- // If permission not granted → request it, still apply window brightness.
69
- //
70
- // getBrightness — reads SYSTEM brightness (0–255 → 0.0–1.0)
71
- // so the slider always shows the true system value.
72
169
  // ═══════════════════════════════════════════════
73
170
 
74
171
  @ReactMethod
75
172
  fun setBrightness(level: Float) {
76
173
  val clamped = level.coerceIn(0.01f, 1f)
77
-
78
- // 1️⃣ Window brightness — instant, no permission needed
79
174
  activity()?.runOnUiThread {
80
175
  val lp = activity()!!.window.attributes
81
176
  lp.screenBrightness = clamped
82
177
  activity()!!.window.attributes = lp
83
178
  }
84
-
85
- // 2️⃣ System brightness — persisted, needs WRITE_SETTINGS
86
179
  val sysValue = (clamped * 255).toInt().coerceIn(1, 255)
87
-
88
180
  if (Settings.System.canWrite(reactContext)) {
89
- // Permission granted — write system brightness
90
- Settings.System.putInt(
91
- reactContext.contentResolver,
92
- Settings.System.SCREEN_BRIGHTNESS,
93
- sysValue
94
- )
95
- // Also disable auto-brightness so manual value sticks
96
- Settings.System.putInt(
97
- reactContext.contentResolver,
98
- Settings.System.SCREEN_BRIGHTNESS_MODE,
99
- Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL
100
- )
181
+ Settings.System.putInt(reactContext.contentResolver, Settings.System.SCREEN_BRIGHTNESS, sysValue)
182
+ Settings.System.putInt(reactContext.contentResolver, Settings.System.SCREEN_BRIGHTNESS_MODE, Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL)
101
183
  } else {
102
- // No permission — open system settings so user can grant it once
103
- val intent = Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS).apply {
104
- flags = Intent.FLAG_ACTIVITY_NEW_TASK
105
- }
106
- try { reactContext.startActivity(intent) } catch (_: Exception) {}
184
+ try {
185
+ reactContext.startActivity(Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS).apply {
186
+ flags = Intent.FLAG_ACTIVITY_NEW_TASK
187
+ })
188
+ } catch (_: Exception) {}
107
189
  }
108
190
  }
109
191
 
110
192
  @ReactMethod
111
193
  fun getBrightness(promise: Promise) {
112
194
  try {
113
- // Always read from system setting so value matches device brightness bar
114
- val sys = Settings.System.getInt(
115
- reactContext.contentResolver,
116
- Settings.System.SCREEN_BRIGHTNESS,
117
- 128
118
- )
195
+ val sys = Settings.System.getInt(reactContext.contentResolver, Settings.System.SCREEN_BRIGHTNESS, 128)
119
196
  promise.resolve(sys / 255.0)
120
- } catch (e: Exception) {
121
- promise.reject("BRIGHTNESS_ERROR", e.message, e)
197
+ } catch (e: Exception) { promise.reject("BRIGHTNESS_ERROR", e.message, e) }
198
+ }
199
+
200
+ // ── Realtime brightness listener ─────────────
201
+ private var brightnessReceiver: BroadcastReceiver? = null
202
+ private var brightnessPollingRunnable: Runnable? = null
203
+ private var lastBrightness = -1
204
+
205
+ @ReactMethod
206
+ fun startBrightnessListener() {
207
+ if (brightnessPollingRunnable != null) return
208
+ // Settings.System doesn't broadcast changes — poll every 500ms
209
+ val runnable = object : Runnable {
210
+ override fun run() {
211
+ try {
212
+ val cur = Settings.System.getInt(reactContext.contentResolver, Settings.System.SCREEN_BRIGHTNESS, 128)
213
+ if (cur != lastBrightness) {
214
+ lastBrightness = cur
215
+ val map = Arguments.createMap()
216
+ map.putDouble("brightness", cur / 255.0)
217
+ emit("SystemBar_BrightnessChange", map)
218
+ }
219
+ } catch (_: Exception) {}
220
+ brightnessPollingRunnable?.let { mainHandler.postDelayed(it, 500) }
221
+ }
122
222
  }
223
+ brightnessPollingRunnable = runnable
224
+ mainHandler.post(runnable)
225
+ }
226
+
227
+ @ReactMethod
228
+ fun stopBrightnessListener() {
229
+ brightnessPollingRunnable?.let { mainHandler.removeCallbacks(it) }
230
+ brightnessPollingRunnable = null
231
+ lastBrightness = -1
123
232
  }
124
233
 
125
234
  // ═══════════════════════════════════════════════
@@ -128,16 +237,13 @@ class SystemBarModule(
128
237
 
129
238
  private var suppressVolumeHUD = false
130
239
 
131
- @ReactMethod
132
- fun setVolumeHUDVisible(visible: Boolean) { suppressVolumeHUD = !visible }
240
+ @ReactMethod fun setVolumeHUDVisible(visible: Boolean) { suppressVolumeHUD = !visible }
133
241
 
134
242
  @ReactMethod
135
243
  fun setVolume(level: Float, stream: String) {
136
244
  try {
137
- val am = audioManager()
138
- val type = streamType(stream)
139
- val max = am.getStreamMaxVolume(type)
140
- val vol = (level.coerceIn(0f, 1f) * max).toInt()
245
+ val am = audioManager(); val type = streamType(stream)
246
+ val vol = (level.coerceIn(0f, 1f) * am.getStreamMaxVolume(type)).toInt()
141
247
  am.setStreamVolume(type, vol, if (suppressVolumeHUD) 0 else AudioManager.FLAG_SHOW_UI)
142
248
  } catch (_: Exception) {}
143
249
  }
@@ -145,13 +251,46 @@ class SystemBarModule(
145
251
  @ReactMethod
146
252
  fun getVolume(stream: String, promise: Promise) {
147
253
  try {
148
- val am = audioManager()
149
- val type = streamType(stream)
150
- val max = am.getStreamMaxVolume(type)
254
+ val am = audioManager(); val type = streamType(stream)
255
+ val max = am.getStreamMaxVolume(type)
151
256
  promise.resolve(if (max > 0) am.getStreamVolume(type).toDouble() / max else 0.0)
152
- } catch (e: Exception) {
153
- promise.reject("VOLUME_ERROR", e.message, e)
257
+ } catch (e: Exception) { promise.reject("VOLUME_ERROR", e.message, e) }
258
+ }
259
+
260
+ // ── Realtime volume listener ─────────────────
261
+ private var volumeReceiver: BroadcastReceiver? = null
262
+
263
+ @ReactMethod
264
+ fun startVolumeListener() {
265
+ if (volumeReceiver != null) return
266
+ val receiver = object : BroadcastReceiver() {
267
+ override fun onReceive(ctx: Context?, intent: Intent?) {
268
+ if (intent?.action != "android.media.VOLUME_CHANGED_ACTION") return
269
+ val streamType = intent.getIntExtra("android.media.EXTRA_VOLUME_STREAM_TYPE", AudioManager.STREAM_MUSIC)
270
+ val am = audioManager()
271
+ val max = am.getStreamMaxVolume(streamType)
272
+ val cur = am.getStreamVolume(streamType)
273
+ val streamName = when (streamType) {
274
+ AudioManager.STREAM_RING -> "ring"
275
+ AudioManager.STREAM_NOTIFICATION -> "notification"
276
+ AudioManager.STREAM_ALARM -> "alarm"
277
+ AudioManager.STREAM_SYSTEM -> "system"
278
+ else -> "music"
279
+ }
280
+ val map = Arguments.createMap()
281
+ map.putDouble("volume", if (max > 0) cur.toDouble() / max else 0.0)
282
+ map.putString("stream", streamName)
283
+ emit("SystemBar_VolumeChange", map)
284
+ }
154
285
  }
286
+ volumeReceiver = receiver
287
+ reactContext.registerReceiver(receiver, IntentFilter("android.media.VOLUME_CHANGED_ACTION"))
288
+ }
289
+
290
+ @ReactMethod
291
+ fun stopVolumeListener() {
292
+ volumeReceiver?.let { try { reactContext.unregisterReceiver(it) } catch (_: Exception) {} }
293
+ volumeReceiver = null
155
294
  }
156
295
 
157
296
  // ═══════════════════════════════════════════════
@@ -178,16 +317,11 @@ class SystemBarModule(
178
317
  c.show(WindowInsets.Type.statusBars() or WindowInsets.Type.navigationBars())
179
318
  }
180
319
  } else {
181
- @Suppress("DEPRECATION")
182
320
  activity()!!.window.decorView.systemUiVisibility = if (enable) {
183
- View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
184
- View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
185
- View.SYSTEM_UI_FLAG_FULLSCREEN or
186
- View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or
321
+ View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
322
+ View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or
187
323
  View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
188
- } else {
189
- View.SYSTEM_UI_FLAG_VISIBLE
190
- }
324
+ } else { View.SYSTEM_UI_FLAG_VISIBLE }
191
325
  }
192
326
  }
193
327
  }
@@ -211,7 +345,8 @@ class SystemBarModule(
211
345
  "landscape" -> ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
212
346
  "landscape-left" -> ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE
213
347
  "landscape-right" -> ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
214
- else -> ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR
348
+ // "auto" = respect system auto-rotate toggle (unspecified = sensor)
349
+ else -> ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
215
350
  }
216
351
  }
217
352
 
@@ -221,26 +356,28 @@ class SystemBarModule(
221
356
 
222
357
  private var displayListener: DisplayManager.DisplayListener? = null
223
358
 
224
- private fun emit(event: String, data: WritableMap) {
225
- reactContext
226
- .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
227
- .emit(event, data)
228
- }
229
-
230
- private fun buildScreencastMap(): WritableMap {
231
- val dm = displayManager()
232
- val map = Arguments.createMap()
233
- val presentations = dm.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION)
234
- val isCasting = presentations.isNotEmpty()
235
- map.putBoolean("isCasting", isCasting)
236
- if (isCasting) map.putString("displayName", presentations[0].name)
359
+ private fun buildDisplayListMap(): WritableMap {
360
+ val dm = displayManager()
361
+ val map = Arguments.createMap()
362
+ val displays = dm.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION)
363
+ val arr = Arguments.createArray()
364
+ for (d in displays) {
365
+ val item = Arguments.createMap()
366
+ item.putInt("id", d.displayId)
367
+ item.putString("name", d.name)
368
+ item.putBoolean("isValid", d.isValid)
369
+ arr.pushMap(item)
370
+ }
371
+ map.putBoolean("isCasting", displays.isNotEmpty())
372
+ map.putArray("displays", arr)
373
+ if (displays.isNotEmpty()) map.putString("displayName", displays[0].name)
237
374
  else map.putNull("displayName")
238
375
  return map
239
376
  }
240
377
 
241
378
  @ReactMethod
242
379
  fun getScreencastInfo(promise: Promise) {
243
- try { promise.resolve(buildScreencastMap()) }
380
+ try { promise.resolve(buildDisplayListMap()) }
244
381
  catch (e: Exception) { promise.reject("SCREENCAST_ERROR", e.message, e) }
245
382
  }
246
383
 
@@ -248,9 +385,9 @@ class SystemBarModule(
248
385
  fun startScreencastListener() {
249
386
  if (displayListener != null) return
250
387
  val listener = object : DisplayManager.DisplayListener {
251
- override fun onDisplayAdded(id: Int) { emit("SystemBar_ScreencastChange", buildScreencastMap()) }
252
- override fun onDisplayRemoved(id: Int) { emit("SystemBar_ScreencastChange", buildScreencastMap()) }
253
- override fun onDisplayChanged(id: Int) { emit("SystemBar_ScreencastChange", buildScreencastMap()) }
388
+ override fun onDisplayAdded(id: Int) { emit("SystemBar_ScreencastChange", buildDisplayListMap()) }
389
+ override fun onDisplayRemoved(id: Int) { emit("SystemBar_ScreencastChange", buildDisplayListMap()) }
390
+ override fun onDisplayChanged(id: Int) { emit("SystemBar_ScreencastChange", buildDisplayListMap()) }
254
391
  }
255
392
  displayListener = listener
256
393
  displayManager().registerDisplayListener(listener, null)
@@ -264,6 +401,8 @@ class SystemBarModule(
264
401
 
265
402
  // ── Cleanup ──────────────────────────────────
266
403
  override fun onCatalystInstanceDestroy() {
404
+ stopBrightnessListener()
405
+ stopVolumeListener()
267
406
  stopScreencastListener()
268
407
  }
269
408
  }
@@ -1,17 +1,26 @@
1
1
  import type { NavigationBarBehavior, NavigationBarButtonStyle, NavigationBarStyle, NavigationBarVisibility, Orientation, ScreencastInfo, StatusBarStyle, VolumeStream } from "./types";
2
2
  export declare const setNavigationBarColor: (color: string) => void;
3
- export declare const setNavigationBarVisibility: (mode: NavigationBarVisibility) => Promise<void>;
4
- export declare const setNavigationBarButtonStyle: (style: NavigationBarButtonStyle) => Promise<void>;
5
- export declare const setNavigationBarStyle: (style: NavigationBarStyle) => Promise<void>;
6
- export declare const setNavigationBarBehavior: (behavior: NavigationBarBehavior) => Promise<void>;
7
- export declare const setStatusBarColor: (color: string, animated?: boolean) => void;
8
- export declare const setStatusBarStyle: (style: StatusBarStyle, animated?: boolean) => void;
9
- export declare const setStatusBarVisibility: (visible: boolean, animated?: boolean) => void;
3
+ export declare const setNavigationBarVisibility: (mode: NavigationBarVisibility) => void;
4
+ export declare const setNavigationBarButtonStyle: (style: NavigationBarButtonStyle) => void;
5
+ export declare const setNavigationBarStyle: (style: NavigationBarStyle) => void;
6
+ export declare const setNavigationBarBehavior: (behavior: NavigationBarBehavior) => void;
7
+ export declare const setStatusBarStyle: (style: StatusBarStyle) => void;
8
+ export declare const setStatusBarVisibility: (visible: boolean) => void;
10
9
  export declare const setBrightness: (level: number) => void;
11
10
  export declare const getBrightness: () => Promise<number>;
11
+ /**
12
+ * Subscribe to system brightness changes (polls every 500ms on Android).
13
+ * @returns unsubscribe function
14
+ */
15
+ export declare const onBrightnessChange: (callback: (brightness: number) => void) => (() => void);
12
16
  export declare const setVolume: (level: number, stream?: VolumeStream) => void;
13
17
  export declare const getVolume: (stream?: VolumeStream) => Promise<number>;
14
18
  export declare const setVolumeHUDVisible: (visible: boolean) => void;
19
+ /**
20
+ * Subscribe to system volume changes (hardware buttons, other apps).
21
+ * @returns unsubscribe function
22
+ */
23
+ export declare const onVolumeChange: (callback: (volume: number, stream: VolumeStream) => void) => (() => void);
15
24
  export declare const keepScreenOn: (enable: boolean) => void;
16
25
  export declare const immersiveMode: (enable: boolean) => void;
17
26
  export declare const setSecureScreen: (enable: boolean) => void;
@@ -1,18 +1,12 @@
1
1
  "use strict";
2
2
  // ─────────────────────────────────────────────
3
3
  // rn-system-bar · SystemBar.ts
4
+ // Zero expo-navigation-bar dependency.
5
+ // All APIs → native Kotlin / iOS Swift.
4
6
  // ─────────────────────────────────────────────
5
7
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.onScreencastChange = exports.getScreencastInfo = exports.setOrientation = exports.setSecureScreen = exports.immersiveMode = exports.keepScreenOn = exports.setVolumeHUDVisible = exports.getVolume = exports.setVolume = exports.getBrightness = exports.setBrightness = exports.setStatusBarVisibility = exports.setStatusBarStyle = exports.setStatusBarColor = exports.setNavigationBarBehavior = exports.setNavigationBarStyle = exports.setNavigationBarButtonStyle = exports.setNavigationBarVisibility = exports.setNavigationBarColor = void 0;
8
+ exports.onScreencastChange = exports.getScreencastInfo = exports.setOrientation = exports.setSecureScreen = exports.immersiveMode = exports.keepScreenOn = exports.onVolumeChange = exports.setVolumeHUDVisible = exports.getVolume = exports.setVolume = exports.onBrightnessChange = exports.getBrightness = exports.setBrightness = exports.setStatusBarVisibility = exports.setStatusBarStyle = exports.setNavigationBarBehavior = exports.setNavigationBarStyle = exports.setNavigationBarButtonStyle = exports.setNavigationBarVisibility = exports.setNavigationBarColor = void 0;
7
9
  const react_native_1 = require("react-native");
8
- const NavBar = (() => {
9
- try {
10
- return require("expo-navigation-bar");
11
- }
12
- catch (_a) {
13
- return null;
14
- }
15
- })();
16
10
  const { SystemBar: Native } = react_native_1.NativeModules;
17
11
  const isAndroid = react_native_1.Platform.OS === "android";
18
12
  const androidOnly = (name) => {
@@ -28,9 +22,7 @@ const checkNative = () => {
28
22
  throw new Error("[rn-system-bar] Native module not found. Rebuild your project.");
29
23
  };
30
24
  // ═══════════════════════════════════════════════
31
- // NAVIGATION BAR (Android-only)
32
- // Color → native Window API (edge-to-edge safe)
33
- // Others → expo-navigation-bar (work in edge-to-edge)
25
+ // NAVIGATION BAR (Android — 100% native)
34
26
  // ═══════════════════════════════════════════════
35
27
  const setNavigationBarColor = (color) => {
36
28
  if (!androidOnly("setNavigationBarColor"))
@@ -40,60 +32,48 @@ const setNavigationBarColor = (color) => {
40
32
  };
41
33
  exports.setNavigationBarColor = setNavigationBarColor;
42
34
  const setNavigationBarVisibility = (mode) => {
43
- var _a;
44
35
  if (!androidOnly("setNavigationBarVisibility"))
45
- return Promise.resolve();
46
- return (_a = NavBar === null || NavBar === void 0 ? void 0 : NavBar.setVisibilityAsync(mode)) !== null && _a !== void 0 ? _a : Promise.resolve();
36
+ return;
37
+ checkNative();
38
+ Native.setNavigationBarVisibility(mode);
47
39
  };
48
40
  exports.setNavigationBarVisibility = setNavigationBarVisibility;
49
41
  const setNavigationBarButtonStyle = (style) => {
50
- var _a;
51
42
  if (!androidOnly("setNavigationBarButtonStyle"))
52
- return Promise.resolve();
53
- return (_a = NavBar === null || NavBar === void 0 ? void 0 : NavBar.setButtonStyleAsync(style)) !== null && _a !== void 0 ? _a : Promise.resolve();
43
+ return;
44
+ checkNative();
45
+ Native.setNavigationBarButtonStyle(style);
54
46
  };
55
47
  exports.setNavigationBarButtonStyle = setNavigationBarButtonStyle;
56
48
  const setNavigationBarStyle = (style) => {
57
- var _a;
58
49
  if (!androidOnly("setNavigationBarStyle"))
59
- return Promise.resolve();
60
- const btn = style === "dark" || style === "auto" ? "dark" : "light";
61
- return (_a = NavBar === null || NavBar === void 0 ? void 0 : NavBar.setButtonStyleAsync(btn)) !== null && _a !== void 0 ? _a : Promise.resolve();
50
+ return;
51
+ checkNative();
52
+ Native.setNavigationBarStyle(style);
62
53
  };
63
54
  exports.setNavigationBarStyle = setNavigationBarStyle;
64
55
  const setNavigationBarBehavior = (behavior) => {
65
- var _a;
66
56
  if (!androidOnly("setNavigationBarBehavior"))
67
- return Promise.resolve();
68
- return (_a = NavBar === null || NavBar === void 0 ? void 0 : NavBar.setBehaviorAsync(behavior)) !== null && _a !== void 0 ? _a : Promise.resolve();
57
+ return;
58
+ checkNative();
59
+ Native.setNavigationBarBehavior(behavior);
69
60
  };
70
61
  exports.setNavigationBarBehavior = setNavigationBarBehavior;
71
62
  // ═══════════════════════════════════════════════
72
- // STATUS BAR
63
+ // STATUS BAR (native — no RN StatusBar)
73
64
  // ═══════════════════════════════════════════════
74
- const setStatusBarColor = (color, animated = false) => {
75
- if (!androidOnly("setStatusBarColor"))
76
- return;
77
- react_native_1.StatusBar.setBackgroundColor(color, animated);
78
- };
79
- exports.setStatusBarColor = setStatusBarColor;
80
- const setStatusBarStyle = (style, animated = false) => {
81
- react_native_1.StatusBar.setBarStyle(style === "light" ? "light-content" : "dark-content", animated);
65
+ const setStatusBarStyle = (style) => {
66
+ checkNative();
67
+ Native.setStatusBarStyle(style);
82
68
  };
83
69
  exports.setStatusBarStyle = setStatusBarStyle;
84
- const setStatusBarVisibility = (visible, animated = false) => {
85
- react_native_1.StatusBar.setHidden(!visible, animated ? "slide" : "none");
70
+ const setStatusBarVisibility = (visible) => {
71
+ checkNative();
72
+ Native.setStatusBarVisibility(visible);
86
73
  };
87
74
  exports.setStatusBarVisibility = setStatusBarVisibility;
88
75
  // ═══════════════════════════════════════════════
89
76
  // BRIGHTNESS
90
- //
91
- // setBrightness → updates BOTH window brightness (instant)
92
- // AND system brightness (persisted).
93
- // On first call: opens WRITE_SETTINGS if not granted.
94
- //
95
- // getBrightness → reads SYSTEM brightness so slider always
96
- // matches the device brightness bar.
97
77
  // ═══════════════════════════════════════════════
98
78
  const setBrightness = (level) => {
99
79
  checkNative();
@@ -105,6 +85,21 @@ const getBrightness = () => {
105
85
  return Native.getBrightness();
106
86
  };
107
87
  exports.getBrightness = getBrightness;
88
+ /**
89
+ * Subscribe to system brightness changes (polls every 500ms on Android).
90
+ * @returns unsubscribe function
91
+ */
92
+ const onBrightnessChange = (callback) => {
93
+ checkNative();
94
+ const { DeviceEventEmitter } = require("react-native");
95
+ Native.startBrightnessListener();
96
+ const sub = DeviceEventEmitter.addListener("SystemBar_BrightnessChange", (e) => callback(e.brightness));
97
+ return () => {
98
+ sub.remove();
99
+ Native.stopBrightnessListener();
100
+ };
101
+ };
102
+ exports.onBrightnessChange = onBrightnessChange;
108
103
  // ═══════════════════════════════════════════════
109
104
  // VOLUME
110
105
  // ═══════════════════════════════════════════════
@@ -125,6 +120,21 @@ const setVolumeHUDVisible = (visible) => {
125
120
  Native.setVolumeHUDVisible(visible);
126
121
  };
127
122
  exports.setVolumeHUDVisible = setVolumeHUDVisible;
123
+ /**
124
+ * Subscribe to system volume changes (hardware buttons, other apps).
125
+ * @returns unsubscribe function
126
+ */
127
+ const onVolumeChange = (callback) => {
128
+ checkNative();
129
+ const { DeviceEventEmitter } = require("react-native");
130
+ Native.startVolumeListener();
131
+ const sub = DeviceEventEmitter.addListener("SystemBar_VolumeChange", (e) => callback(e.volume, e.stream));
132
+ return () => {
133
+ sub.remove();
134
+ Native.stopVolumeListener();
135
+ };
136
+ };
137
+ exports.onVolumeChange = onVolumeChange;
128
138
  // ═══════════════════════════════════════════════
129
139
  // SCREEN
130
140
  // ═══════════════════════════════════════════════
@@ -5,7 +5,13 @@ export type NavigationBarVisibility = "visible" | "hidden";
5
5
  export type StatusBarStyle = "light" | "dark";
6
6
  export type Orientation = "portrait" | "landscape" | "landscape-left" | "landscape-right" | "auto";
7
7
  export type VolumeStream = "music" | "ring" | "notification" | "alarm" | "system";
8
+ export interface ScreencastDisplay {
9
+ id: number;
10
+ name: string;
11
+ isValid: boolean;
12
+ }
8
13
  export interface ScreencastInfo {
9
14
  isCasting: boolean;
10
15
  displayName: string | null;
16
+ displays: ScreencastDisplay[];
11
17
  }
@@ -47,24 +47,21 @@ const useSystemBar = (config) => {
47
47
  return;
48
48
  configRef.current = configStr;
49
49
  const apply = async () => {
50
- const p = [];
50
+ // All nav bar calls are now sync (no expo-navigation-bar)
51
51
  if (config.navigationBarColor !== undefined)
52
52
  SystemBar.setNavigationBarColor(config.navigationBarColor);
53
53
  if (config.navigationBarVisibility !== undefined)
54
- p.push(SystemBar.setNavigationBarVisibility(config.navigationBarVisibility));
54
+ SystemBar.setNavigationBarVisibility(config.navigationBarVisibility);
55
55
  if (config.navigationBarButtonStyle !== undefined)
56
- p.push(SystemBar.setNavigationBarButtonStyle(config.navigationBarButtonStyle));
56
+ SystemBar.setNavigationBarButtonStyle(config.navigationBarButtonStyle);
57
57
  if (config.navigationBarStyle !== undefined)
58
- p.push(SystemBar.setNavigationBarStyle(config.navigationBarStyle));
58
+ SystemBar.setNavigationBarStyle(config.navigationBarStyle);
59
59
  if (config.navigationBarBehavior !== undefined)
60
- p.push(SystemBar.setNavigationBarBehavior(config.navigationBarBehavior));
61
- await Promise.allSettled(p);
62
- if (config.statusBarColor !== undefined)
63
- SystemBar.setStatusBarColor(config.statusBarColor, config.statusBarAnimated);
60
+ SystemBar.setNavigationBarBehavior(config.navigationBarBehavior);
64
61
  if (config.statusBarStyle !== undefined)
65
- SystemBar.setStatusBarStyle(config.statusBarStyle, config.statusBarAnimated);
62
+ SystemBar.setStatusBarStyle(config.statusBarStyle);
66
63
  if (config.statusBarVisible !== undefined)
67
- SystemBar.setStatusBarVisibility(config.statusBarVisible, config.statusBarAnimated);
64
+ SystemBar.setStatusBarVisibility(config.statusBarVisible);
68
65
  if (config.keepScreenOn !== undefined)
69
66
  SystemBar.keepScreenOn(config.keepScreenOn);
70
67
  if (config.immersiveMode !== undefined)
@@ -91,6 +88,7 @@ const useScreencast = () => {
91
88
  const [info, setInfo] = (0, react_1.useState)({
92
89
  isCasting: false,
93
90
  displayName: null,
91
+ displays: [],
94
92
  });
95
93
  (0, react_1.useEffect)(() => {
96
94
  SystemBar.getScreencastInfo().then(setInfo).catch(() => { });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rn-system-bar",
3
- "version": "3.1.1",
3
+ "version": "3.1.4",
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",
package/src/SystemBar.ts CHANGED
@@ -1,8 +1,10 @@
1
1
  // ─────────────────────────────────────────────
2
2
  // rn-system-bar · SystemBar.ts
3
+ // Zero expo-navigation-bar dependency.
4
+ // All APIs → native Kotlin / iOS Swift.
3
5
  // ─────────────────────────────────────────────
4
6
 
5
- import { NativeModules, Platform, StatusBar } from "react-native";
7
+ import { NativeModules, Platform } from "react-native";
6
8
 
7
9
  import type {
8
10
  NavigationBarBehavior,
@@ -15,11 +17,6 @@ import type {
15
17
  VolumeStream,
16
18
  } from "./types";
17
19
 
18
- const NavBar = (() => {
19
- try { return require("expo-navigation-bar"); }
20
- catch { return null; }
21
- })();
22
-
23
20
  const { SystemBar: Native } = NativeModules;
24
21
 
25
22
  const isAndroid = Platform.OS === "android";
@@ -37,9 +34,7 @@ const checkNative = () => {
37
34
  };
38
35
 
39
36
  // ═══════════════════════════════════════════════
40
- // NAVIGATION BAR (Android-only)
41
- // Color → native Window API (edge-to-edge safe)
42
- // Others → expo-navigation-bar (work in edge-to-edge)
37
+ // NAVIGATION BAR (Android — 100% native)
43
38
  // ═══════════════════════════════════════════════
44
39
 
45
40
  export const setNavigationBarColor = (color: string): void => {
@@ -48,53 +43,46 @@ export const setNavigationBarColor = (color: string): void => {
48
43
  Native.setNavigationBarColor(color);
49
44
  };
50
45
 
51
- export const setNavigationBarVisibility = (mode: NavigationBarVisibility): Promise<void> => {
52
- if (!androidOnly("setNavigationBarVisibility")) return Promise.resolve();
53
- return NavBar?.setVisibilityAsync(mode) ?? Promise.resolve();
46
+ export const setNavigationBarVisibility = (mode: NavigationBarVisibility): void => {
47
+ if (!androidOnly("setNavigationBarVisibility")) return;
48
+ checkNative();
49
+ Native.setNavigationBarVisibility(mode);
54
50
  };
55
51
 
56
- export const setNavigationBarButtonStyle = (style: NavigationBarButtonStyle): Promise<void> => {
57
- if (!androidOnly("setNavigationBarButtonStyle")) return Promise.resolve();
58
- return NavBar?.setButtonStyleAsync(style) ?? Promise.resolve();
52
+ export const setNavigationBarButtonStyle = (style: NavigationBarButtonStyle): void => {
53
+ if (!androidOnly("setNavigationBarButtonStyle")) return;
54
+ checkNative();
55
+ Native.setNavigationBarButtonStyle(style);
59
56
  };
60
57
 
61
- export const setNavigationBarStyle = (style: NavigationBarStyle): Promise<void> => {
62
- if (!androidOnly("setNavigationBarStyle")) return Promise.resolve();
63
- const btn: NavigationBarButtonStyle = style === "dark" || style === "auto" ? "dark" : "light";
64
- return NavBar?.setButtonStyleAsync(btn) ?? Promise.resolve();
58
+ export const setNavigationBarStyle = (style: NavigationBarStyle): void => {
59
+ if (!androidOnly("setNavigationBarStyle")) return;
60
+ checkNative();
61
+ Native.setNavigationBarStyle(style);
65
62
  };
66
63
 
67
- export const setNavigationBarBehavior = (behavior: NavigationBarBehavior): Promise<void> => {
68
- if (!androidOnly("setNavigationBarBehavior")) return Promise.resolve();
69
- return NavBar?.setBehaviorAsync(behavior) ?? Promise.resolve();
64
+ export const setNavigationBarBehavior = (behavior: NavigationBarBehavior): void => {
65
+ if (!androidOnly("setNavigationBarBehavior")) return;
66
+ checkNative();
67
+ Native.setNavigationBarBehavior(behavior);
70
68
  };
71
69
 
72
70
  // ═══════════════════════════════════════════════
73
- // STATUS BAR
71
+ // STATUS BAR (native — no RN StatusBar)
74
72
  // ═══════════════════════════════════════════════
75
73
 
76
- export const setStatusBarColor = (color: string, animated = false): void => {
77
- if (!androidOnly("setStatusBarColor")) return;
78
- StatusBar.setBackgroundColor(color, animated);
79
- };
80
-
81
- export const setStatusBarStyle = (style: StatusBarStyle, animated = false): void => {
82
- StatusBar.setBarStyle(style === "light" ? "light-content" : "dark-content", animated);
74
+ export const setStatusBarStyle = (style: StatusBarStyle): void => {
75
+ checkNative();
76
+ Native.setStatusBarStyle(style);
83
77
  };
84
78
 
85
- export const setStatusBarVisibility = (visible: boolean, animated = false): void => {
86
- StatusBar.setHidden(!visible, animated ? "slide" : "none");
79
+ export const setStatusBarVisibility = (visible: boolean): void => {
80
+ checkNative();
81
+ Native.setStatusBarVisibility(visible);
87
82
  };
88
83
 
89
84
  // ═══════════════════════════════════════════════
90
85
  // BRIGHTNESS
91
- //
92
- // setBrightness → updates BOTH window brightness (instant)
93
- // AND system brightness (persisted).
94
- // On first call: opens WRITE_SETTINGS if not granted.
95
- //
96
- // getBrightness → reads SYSTEM brightness so slider always
97
- // matches the device brightness bar.
98
86
  // ═══════════════════════════════════════════════
99
87
 
100
88
  export const setBrightness = (level: number): void => {
@@ -107,6 +95,26 @@ export const getBrightness = (): Promise<number> => {
107
95
  return Native.getBrightness();
108
96
  };
109
97
 
98
+ /**
99
+ * Subscribe to system brightness changes (polls every 500ms on Android).
100
+ * @returns unsubscribe function
101
+ */
102
+ export const onBrightnessChange = (
103
+ callback: (brightness: number) => void
104
+ ): (() => void) => {
105
+ checkNative();
106
+ const { DeviceEventEmitter } = require("react-native");
107
+ Native.startBrightnessListener();
108
+ const sub = DeviceEventEmitter.addListener(
109
+ "SystemBar_BrightnessChange",
110
+ (e: { brightness: number }) => callback(e.brightness)
111
+ );
112
+ return () => {
113
+ sub.remove();
114
+ Native.stopBrightnessListener();
115
+ };
116
+ };
117
+
110
118
  // ═══════════════════════════════════════════════
111
119
  // VOLUME
112
120
  // ═══════════════════════════════════════════════
@@ -127,6 +135,26 @@ export const setVolumeHUDVisible = (visible: boolean): void => {
127
135
  Native.setVolumeHUDVisible(visible);
128
136
  };
129
137
 
138
+ /**
139
+ * Subscribe to system volume changes (hardware buttons, other apps).
140
+ * @returns unsubscribe function
141
+ */
142
+ export const onVolumeChange = (
143
+ callback: (volume: number, stream: VolumeStream) => void
144
+ ): (() => void) => {
145
+ checkNative();
146
+ const { DeviceEventEmitter } = require("react-native");
147
+ Native.startVolumeListener();
148
+ const sub = DeviceEventEmitter.addListener(
149
+ "SystemBar_VolumeChange",
150
+ (e: { volume: number; stream: VolumeStream }) => callback(e.volume, e.stream)
151
+ );
152
+ return () => {
153
+ sub.remove();
154
+ Native.stopVolumeListener();
155
+ };
156
+ };
157
+
130
158
  // ═══════════════════════════════════════════════
131
159
  // SCREEN
132
160
  // ═══════════════════════════════════════════════
package/src/types.ts CHANGED
@@ -26,7 +26,14 @@ export type VolumeStream =
26
26
  | "alarm"
27
27
  | "system";
28
28
 
29
+ export interface ScreencastDisplay {
30
+ id: number;
31
+ name: string;
32
+ isValid: boolean;
33
+ }
34
+
29
35
  export interface ScreencastInfo {
30
36
  isCasting: boolean;
31
37
  displayName: string | null;
38
+ displays: ScreencastDisplay[];
32
39
  }
@@ -44,24 +44,19 @@ export const useSystemBar = (config: SystemBarConfig) => {
44
44
  configRef.current = configStr;
45
45
 
46
46
  const apply = async () => {
47
- const p: Promise<void>[] = [];
48
-
47
+ // All nav bar calls are now sync (no expo-navigation-bar)
49
48
  if (config.navigationBarColor !== undefined) SystemBar.setNavigationBarColor(config.navigationBarColor);
50
- if (config.navigationBarVisibility !== undefined) p.push(SystemBar.setNavigationBarVisibility(config.navigationBarVisibility));
51
- if (config.navigationBarButtonStyle !== undefined) p.push(SystemBar.setNavigationBarButtonStyle(config.navigationBarButtonStyle));
52
- if (config.navigationBarStyle !== undefined) p.push(SystemBar.setNavigationBarStyle(config.navigationBarStyle));
53
- if (config.navigationBarBehavior !== undefined) p.push(SystemBar.setNavigationBarBehavior(config.navigationBarBehavior));
54
-
55
- await Promise.allSettled(p);
56
-
57
- if (config.statusBarColor !== undefined) SystemBar.setStatusBarColor(config.statusBarColor, config.statusBarAnimated);
58
- if (config.statusBarStyle !== undefined) SystemBar.setStatusBarStyle(config.statusBarStyle, config.statusBarAnimated);
59
- if (config.statusBarVisible !== undefined) SystemBar.setStatusBarVisibility(config.statusBarVisible, config.statusBarAnimated);
60
- if (config.keepScreenOn !== undefined) SystemBar.keepScreenOn(config.keepScreenOn);
61
- if (config.immersiveMode !== undefined) SystemBar.immersiveMode(config.immersiveMode);
62
- if (config.brightness !== undefined) SystemBar.setBrightness(config.brightness);
63
- if (config.orientation !== undefined) SystemBar.setOrientation(config.orientation);
64
- if (config.secureScreen !== undefined) SystemBar.setSecureScreen(config.secureScreen);
49
+ if (config.navigationBarVisibility !== undefined) SystemBar.setNavigationBarVisibility(config.navigationBarVisibility);
50
+ if (config.navigationBarButtonStyle !== undefined) SystemBar.setNavigationBarButtonStyle(config.navigationBarButtonStyle);
51
+ if (config.navigationBarStyle !== undefined) SystemBar.setNavigationBarStyle(config.navigationBarStyle);
52
+ if (config.navigationBarBehavior !== undefined) SystemBar.setNavigationBarBehavior(config.navigationBarBehavior);
53
+ if (config.statusBarStyle !== undefined) SystemBar.setStatusBarStyle(config.statusBarStyle);
54
+ if (config.statusBarVisible !== undefined) SystemBar.setStatusBarVisibility(config.statusBarVisible);
55
+ if (config.keepScreenOn !== undefined) SystemBar.keepScreenOn(config.keepScreenOn);
56
+ if (config.immersiveMode !== undefined) SystemBar.immersiveMode(config.immersiveMode);
57
+ if (config.brightness !== undefined) SystemBar.setBrightness(config.brightness);
58
+ if (config.orientation !== undefined) SystemBar.setOrientation(config.orientation);
59
+ if (config.secureScreen !== undefined) SystemBar.setSecureScreen(config.secureScreen);
65
60
  };
66
61
 
67
62
  apply().catch(() => {
@@ -78,6 +73,7 @@ export const useScreencast = () => {
78
73
  const [info, setInfo] = useState<ScreencastInfo>({
79
74
  isCasting: false,
80
75
  displayName: null,
76
+ displays: [],
81
77
  });
82
78
 
83
79
  useEffect(() => {