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.
- package/android/src/main/AndroidManifest.xml +4 -4
- package/android/src/main/java/com/systembar/SystemBarModule.kt +227 -88
- package/lib/src/SystemBar.d.ts +16 -7
- package/lib/src/SystemBar.js +53 -43
- package/lib/src/types.d.ts +6 -0
- package/lib/src/useSystemBar.js +8 -10
- package/package.json +1 -1
- package/src/SystemBar.ts +67 -39
- package/src/types.ts +7 -0
- package/src/useSystemBar.ts +13 -17
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
2
|
-
|
|
2
|
+
xmlns:tools="http://schemas.android.com/tools">
|
|
3
3
|
|
|
4
4
|
<!-- Required for writing system brightness -->
|
|
5
|
-
<uses-permission
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
138
|
-
val
|
|
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
|
|
149
|
-
val
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
225
|
-
|
|
226
|
-
.
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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(
|
|
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",
|
|
252
|
-
override fun onDisplayRemoved(id: Int) { emit("SystemBar_ScreencastChange",
|
|
253
|
-
override fun onDisplayChanged(id: Int) { emit("SystemBar_ScreencastChange",
|
|
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
|
}
|
package/lib/src/SystemBar.d.ts
CHANGED
|
@@ -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) =>
|
|
4
|
-
export declare const setNavigationBarButtonStyle: (style: NavigationBarButtonStyle) =>
|
|
5
|
-
export declare const setNavigationBarStyle: (style: NavigationBarStyle) =>
|
|
6
|
-
export declare const setNavigationBarBehavior: (behavior: NavigationBarBehavior) =>
|
|
7
|
-
export declare const
|
|
8
|
-
export declare const
|
|
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;
|
package/lib/src/SystemBar.js
CHANGED
|
@@ -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.
|
|
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
|
|
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
|
|
46
|
-
|
|
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
|
|
53
|
-
|
|
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
|
|
60
|
-
|
|
61
|
-
|
|
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
|
|
68
|
-
|
|
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
|
|
75
|
-
|
|
76
|
-
|
|
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
|
|
85
|
-
|
|
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
|
// ═══════════════════════════════════════════════
|
package/lib/src/types.d.ts
CHANGED
|
@@ -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
|
}
|
package/lib/src/useSystemBar.js
CHANGED
|
@@ -47,24 +47,21 @@ const useSystemBar = (config) => {
|
|
|
47
47
|
return;
|
|
48
48
|
configRef.current = configStr;
|
|
49
49
|
const apply = async () => {
|
|
50
|
-
|
|
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
|
-
|
|
54
|
+
SystemBar.setNavigationBarVisibility(config.navigationBarVisibility);
|
|
55
55
|
if (config.navigationBarButtonStyle !== undefined)
|
|
56
|
-
|
|
56
|
+
SystemBar.setNavigationBarButtonStyle(config.navigationBarButtonStyle);
|
|
57
57
|
if (config.navigationBarStyle !== undefined)
|
|
58
|
-
|
|
58
|
+
SystemBar.setNavigationBarStyle(config.navigationBarStyle);
|
|
59
59
|
if (config.navigationBarBehavior !== undefined)
|
|
60
|
-
|
|
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
|
|
62
|
+
SystemBar.setStatusBarStyle(config.statusBarStyle);
|
|
66
63
|
if (config.statusBarVisible !== undefined)
|
|
67
|
-
SystemBar.setStatusBarVisibility(config.statusBarVisible
|
|
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
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
|
|
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
|
|
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):
|
|
52
|
-
if (!androidOnly("setNavigationBarVisibility")) return
|
|
53
|
-
|
|
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):
|
|
57
|
-
if (!androidOnly("setNavigationBarButtonStyle")) return
|
|
58
|
-
|
|
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):
|
|
62
|
-
if (!androidOnly("setNavigationBarStyle")) return
|
|
63
|
-
|
|
64
|
-
|
|
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):
|
|
68
|
-
if (!androidOnly("setNavigationBarBehavior")) return
|
|
69
|
-
|
|
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
|
|
77
|
-
|
|
78
|
-
|
|
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
|
|
86
|
-
|
|
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
|
}
|
package/src/useSystemBar.ts
CHANGED
|
@@ -44,24 +44,19 @@ export const useSystemBar = (config: SystemBarConfig) => {
|
|
|
44
44
|
configRef.current = configStr;
|
|
45
45
|
|
|
46
46
|
const apply = async () => {
|
|
47
|
-
|
|
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)
|
|
51
|
-
if (config.navigationBarButtonStyle !== undefined)
|
|
52
|
-
if (config.navigationBarStyle !== undefined)
|
|
53
|
-
if (config.navigationBarBehavior !== undefined)
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
if (config.
|
|
58
|
-
if (config.
|
|
59
|
-
if (config.
|
|
60
|
-
if (config.
|
|
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(() => {
|