rn-system-bar 3.1.0 → 3.1.3
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 +9 -0
- package/android/src/main/java/com/systembar/SystemBarModule.kt +120 -294
- package/index.ts +1 -8
- package/lib/index.d.ts +1 -1
- package/lib/index.js +1 -5
- package/lib/src/SystemBar.d.ts +3 -56
- package/lib/src/SystemBar.js +24 -140
- package/lib/src/types.d.ts +5 -32
- package/lib/src/types.js +1 -1
- package/lib/src/useSystemBar.d.ts +1 -31
- package/lib/src/useSystemBar.js +7 -98
- package/package.json +3 -2
- package/rn-system-bar.podspec +1 -1
- package/src/SystemBar.ts +24 -154
- package/src/types.ts +6 -64
- package/src/useSystemBar.ts +11 -111
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
2
|
+
xmlns:tools="http://schemas.android.com/tools">
|
|
3
|
+
|
|
4
|
+
<!-- Required for writing system brightness -->
|
|
5
|
+
<uses-permission
|
|
6
|
+
android:name="android.permission.WRITE_SETTINGS"
|
|
7
|
+
tools:ignore="ProtectedPermissions" />
|
|
8
|
+
|
|
9
|
+
</manifest>
|
|
@@ -1,29 +1,18 @@
|
|
|
1
1
|
// ─────────────────────────────────────────────
|
|
2
|
-
// rn-system-bar · SystemBarModule.kt
|
|
3
|
-
// New: Network, Battery, Haptics, Screencast, FontScale
|
|
2
|
+
// rn-system-bar · SystemBarModule.kt
|
|
4
3
|
// ─────────────────────────────────────────────
|
|
5
4
|
|
|
6
5
|
package com.systembar
|
|
7
6
|
|
|
8
7
|
import android.app.Activity
|
|
9
|
-
import android.content.BroadcastReceiver
|
|
10
8
|
import android.content.Context
|
|
11
9
|
import android.content.Intent
|
|
12
|
-
import android.content.IntentFilter
|
|
13
10
|
import android.content.pm.ActivityInfo
|
|
11
|
+
import android.graphics.Color
|
|
14
12
|
import android.hardware.display.DisplayManager
|
|
15
13
|
import android.media.AudioManager
|
|
16
|
-
import android.net.ConnectivityManager
|
|
17
|
-
import android.net.Network
|
|
18
|
-
import android.net.NetworkCapabilities
|
|
19
|
-
import android.net.NetworkRequest
|
|
20
|
-
import android.os.BatteryManager
|
|
21
14
|
import android.os.Build
|
|
22
|
-
import android.os.VibrationEffect
|
|
23
|
-
import android.os.Vibrator
|
|
24
|
-
import android.os.VibratorManager
|
|
25
15
|
import android.provider.Settings
|
|
26
|
-
import android.view.Display
|
|
27
16
|
import android.view.View
|
|
28
17
|
import android.view.WindowInsets
|
|
29
18
|
import android.view.WindowInsetsController
|
|
@@ -32,6 +21,7 @@ import android.view.WindowManager
|
|
|
32
21
|
import com.facebook.react.bridge.*
|
|
33
22
|
import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
34
23
|
|
|
24
|
+
@Suppress("DEPRECATION", "OVERRIDE_DEPRECATION")
|
|
35
25
|
class SystemBarModule(
|
|
36
26
|
private val reactContext: ReactApplicationContext
|
|
37
27
|
) : ReactContextBaseJavaModule(reactContext) {
|
|
@@ -40,23 +30,11 @@ class SystemBarModule(
|
|
|
40
30
|
|
|
41
31
|
private fun activity(): Activity? = reactContext.currentActivity
|
|
42
32
|
|
|
43
|
-
private fun
|
|
44
|
-
reactContext
|
|
45
|
-
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
46
|
-
.emit(event, data)
|
|
47
|
-
}
|
|
33
|
+
private fun audioManager() =
|
|
34
|
+
reactContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
|
48
35
|
|
|
49
|
-
private fun
|
|
50
|
-
|
|
51
|
-
private fun batteryManager() = reactContext.getSystemService(Context.BATTERY_SERVICE) as BatteryManager
|
|
52
|
-
private fun displayManager() = reactContext.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
|
|
53
|
-
|
|
54
|
-
@Suppress("DEPRECATION")
|
|
55
|
-
private fun vibrator(): Vibrator = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
56
|
-
(reactContext.getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager).defaultVibrator
|
|
57
|
-
} else {
|
|
58
|
-
reactContext.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
|
|
59
|
-
}
|
|
36
|
+
private fun displayManager() =
|
|
37
|
+
reactContext.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
|
|
60
38
|
|
|
61
39
|
private fun streamType(stream: String): Int = when (stream) {
|
|
62
40
|
"ring" -> AudioManager.STREAM_RING
|
|
@@ -66,29 +44,84 @@ class SystemBarModule(
|
|
|
66
44
|
else -> AudioManager.STREAM_MUSIC
|
|
67
45
|
}
|
|
68
46
|
|
|
47
|
+
private fun emit(event: String, data: WritableMap) {
|
|
48
|
+
reactContext
|
|
49
|
+
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
50
|
+
.emit(event, data)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ═══════════════════════════════════════════════
|
|
54
|
+
// NAVIGATION BAR COLOR
|
|
55
|
+
// WindowInsetsController API 30+, fallback for older.
|
|
56
|
+
// navigationBarColor deprecated on API 35+ but still
|
|
57
|
+
// works — suppressed at class level.
|
|
58
|
+
// ═══════════════════════════════════════════════
|
|
59
|
+
|
|
60
|
+
@ReactMethod
|
|
61
|
+
fun setNavigationBarColor(color: String) {
|
|
62
|
+
activity()?.runOnUiThread {
|
|
63
|
+
try {
|
|
64
|
+
val parsed = Color.parseColor(color)
|
|
65
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
|
|
66
|
+
// API 35+ — use WindowInsetsController appearance for tinting
|
|
67
|
+
val controller = activity()!!.window.insetsController
|
|
68
|
+
// Nav bar color via window attribute still works as tint on 35+
|
|
69
|
+
activity()!!.window.navigationBarColor = parsed
|
|
70
|
+
} else {
|
|
71
|
+
activity()!!.window.navigationBarColor = parsed
|
|
72
|
+
}
|
|
73
|
+
} catch (_: Exception) {}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
69
77
|
// ═══════════════════════════════════════════════
|
|
70
78
|
// BRIGHTNESS
|
|
71
79
|
// ═══════════════════════════════════════════════
|
|
72
80
|
|
|
73
81
|
@ReactMethod
|
|
74
82
|
fun setBrightness(level: Float) {
|
|
83
|
+
val clamped = level.coerceIn(0.01f, 1f)
|
|
84
|
+
|
|
85
|
+
// Window brightness — instant, no permission needed
|
|
75
86
|
activity()?.runOnUiThread {
|
|
76
87
|
val lp = activity()!!.window.attributes
|
|
77
|
-
lp.screenBrightness =
|
|
88
|
+
lp.screenBrightness = clamped
|
|
78
89
|
activity()!!.window.attributes = lp
|
|
79
90
|
}
|
|
91
|
+
|
|
92
|
+
// System brightness — persisted, needs WRITE_SETTINGS
|
|
93
|
+
val sysValue = (clamped * 255).toInt().coerceIn(1, 255)
|
|
94
|
+
if (Settings.System.canWrite(reactContext)) {
|
|
95
|
+
Settings.System.putInt(
|
|
96
|
+
reactContext.contentResolver,
|
|
97
|
+
Settings.System.SCREEN_BRIGHTNESS,
|
|
98
|
+
sysValue
|
|
99
|
+
)
|
|
100
|
+
Settings.System.putInt(
|
|
101
|
+
reactContext.contentResolver,
|
|
102
|
+
Settings.System.SCREEN_BRIGHTNESS_MODE,
|
|
103
|
+
Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL
|
|
104
|
+
)
|
|
105
|
+
} else {
|
|
106
|
+
val intent = Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS).apply {
|
|
107
|
+
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
|
108
|
+
}
|
|
109
|
+
try { reactContext.startActivity(intent) } catch (_: Exception) {}
|
|
110
|
+
}
|
|
80
111
|
}
|
|
81
112
|
|
|
82
113
|
@ReactMethod
|
|
83
114
|
fun getBrightness(promise: Promise) {
|
|
84
115
|
try {
|
|
85
|
-
val
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
116
|
+
val sys = Settings.System.getInt(
|
|
117
|
+
reactContext.contentResolver,
|
|
118
|
+
Settings.System.SCREEN_BRIGHTNESS,
|
|
119
|
+
128
|
|
120
|
+
)
|
|
90
121
|
promise.resolve(sys / 255.0)
|
|
91
|
-
} catch (e: Exception) {
|
|
122
|
+
} catch (e: Exception) {
|
|
123
|
+
promise.reject("BRIGHTNESS_ERROR", e.message, e)
|
|
124
|
+
}
|
|
92
125
|
}
|
|
93
126
|
|
|
94
127
|
// ═══════════════════════════════════════════════
|
|
@@ -97,13 +130,16 @@ class SystemBarModule(
|
|
|
97
130
|
|
|
98
131
|
private var suppressVolumeHUD = false
|
|
99
132
|
|
|
100
|
-
@ReactMethod
|
|
133
|
+
@ReactMethod
|
|
134
|
+
fun setVolumeHUDVisible(visible: Boolean) { suppressVolumeHUD = !visible }
|
|
101
135
|
|
|
102
136
|
@ReactMethod
|
|
103
137
|
fun setVolume(level: Float, stream: String) {
|
|
104
138
|
try {
|
|
105
|
-
val am
|
|
106
|
-
val
|
|
139
|
+
val am = audioManager()
|
|
140
|
+
val type = streamType(stream)
|
|
141
|
+
val max = am.getStreamMaxVolume(type)
|
|
142
|
+
val vol = (level.coerceIn(0f, 1f) * max).toInt()
|
|
107
143
|
am.setStreamVolume(type, vol, if (suppressVolumeHUD) 0 else AudioManager.FLAG_SHOW_UI)
|
|
108
144
|
} catch (_: Exception) {}
|
|
109
145
|
}
|
|
@@ -111,10 +147,13 @@ class SystemBarModule(
|
|
|
111
147
|
@ReactMethod
|
|
112
148
|
fun getVolume(stream: String, promise: Promise) {
|
|
113
149
|
try {
|
|
114
|
-
val am
|
|
115
|
-
val
|
|
150
|
+
val am = audioManager()
|
|
151
|
+
val type = streamType(stream)
|
|
152
|
+
val max = am.getStreamMaxVolume(type)
|
|
116
153
|
promise.resolve(if (max > 0) am.getStreamVolume(type).toDouble() / max else 0.0)
|
|
117
|
-
} catch (e: Exception) {
|
|
154
|
+
} catch (e: Exception) {
|
|
155
|
+
promise.reject("VOLUME_ERROR", e.message, e)
|
|
156
|
+
}
|
|
118
157
|
}
|
|
119
158
|
|
|
120
159
|
// ═══════════════════════════════════════════════
|
|
@@ -141,18 +180,30 @@ class SystemBarModule(
|
|
|
141
180
|
c.show(WindowInsets.Type.statusBars() or WindowInsets.Type.navigationBars())
|
|
142
181
|
}
|
|
143
182
|
} else {
|
|
144
|
-
@Suppress("DEPRECATION")
|
|
145
183
|
activity()!!.window.decorView.systemUiVisibility = if (enable) {
|
|
146
|
-
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
|
|
147
|
-
View.
|
|
184
|
+
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
|
|
185
|
+
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
|
|
186
|
+
View.SYSTEM_UI_FLAG_FULLSCREEN or
|
|
187
|
+
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or
|
|
148
188
|
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
|
149
|
-
} else {
|
|
189
|
+
} else {
|
|
190
|
+
View.SYSTEM_UI_FLAG_VISIBLE
|
|
191
|
+
}
|
|
150
192
|
}
|
|
151
193
|
}
|
|
152
194
|
}
|
|
153
195
|
|
|
196
|
+
@ReactMethod
|
|
197
|
+
fun setSecureScreen(enable: Boolean) {
|
|
198
|
+
activity()?.runOnUiThread {
|
|
199
|
+
if (enable) activity()!!.window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
|
|
200
|
+
else activity()!!.window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
154
204
|
// ═══════════════════════════════════════════════
|
|
155
205
|
// ORIENTATION
|
|
206
|
+
// "auto" = FULL_SENSOR (follows system auto-rotate)
|
|
156
207
|
// ═══════════════════════════════════════════════
|
|
157
208
|
|
|
158
209
|
@ReactMethod
|
|
@@ -162,216 +213,41 @@ class SystemBarModule(
|
|
|
162
213
|
"landscape" -> ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
|
|
163
214
|
"landscape-left" -> ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE
|
|
164
215
|
"landscape-right" -> ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
|
|
216
|
+
// "auto" = respect system auto-rotate toggle
|
|
165
217
|
else -> ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR
|
|
166
218
|
}
|
|
167
219
|
}
|
|
168
220
|
|
|
169
221
|
// ═══════════════════════════════════════════════
|
|
170
|
-
//
|
|
171
|
-
// ═══════════════════════════════════════════════
|
|
172
|
-
|
|
173
|
-
private var networkCallback: ConnectivityManager.NetworkCallback? = null
|
|
174
|
-
|
|
175
|
-
private fun buildNetworkMap(): WritableMap {
|
|
176
|
-
val cm = connectivityManager()
|
|
177
|
-
val map = Arguments.createMap()
|
|
178
|
-
|
|
179
|
-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
180
|
-
val net = cm.activeNetwork
|
|
181
|
-
val caps = cm.getNetworkCapabilities(net)
|
|
182
|
-
val connected = caps != null
|
|
183
|
-
|
|
184
|
-
map.putBoolean("isConnected", connected)
|
|
185
|
-
|
|
186
|
-
val type = when {
|
|
187
|
-
caps == null -> "none"
|
|
188
|
-
caps.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> "wifi"
|
|
189
|
-
caps.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> "cellular"
|
|
190
|
-
caps.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> "ethernet"
|
|
191
|
-
else -> "unknown"
|
|
192
|
-
}
|
|
193
|
-
map.putString("type", type)
|
|
194
|
-
|
|
195
|
-
// Cellular generation
|
|
196
|
-
val cellGen: String? = if (type == "cellular") {
|
|
197
|
-
val am = audioManager() // not audio — reuse context trick; use TelephonyManager instead
|
|
198
|
-
null // placeholder; TelephonyManager requires READ_PHONE_STATE permission
|
|
199
|
-
} else null
|
|
200
|
-
if (cellGen != null) map.putString("cellularGeneration", cellGen)
|
|
201
|
-
else map.putNull("cellularGeneration")
|
|
202
|
-
|
|
203
|
-
// SSID (requires ACCESS_FINE_LOCATION on Android 10+)
|
|
204
|
-
map.putNull("ssid")
|
|
205
|
-
|
|
206
|
-
} else {
|
|
207
|
-
@Suppress("DEPRECATION")
|
|
208
|
-
val info = cm.activeNetworkInfo
|
|
209
|
-
map.putBoolean("isConnected", info?.isConnected == true)
|
|
210
|
-
map.putString("type", when (info?.type) {
|
|
211
|
-
ConnectivityManager.TYPE_WIFI -> "wifi"
|
|
212
|
-
ConnectivityManager.TYPE_MOBILE -> "cellular"
|
|
213
|
-
ConnectivityManager.TYPE_ETHERNET -> "ethernet"
|
|
214
|
-
else -> if (info?.isConnected == true) "unknown" else "none"
|
|
215
|
-
})
|
|
216
|
-
map.putNull("cellularGeneration")
|
|
217
|
-
map.putNull("ssid")
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// Airplane mode
|
|
221
|
-
val airplaneMode = Settings.Global.getInt(
|
|
222
|
-
reactContext.contentResolver, Settings.Global.AIRPLANE_MODE_ON, 0
|
|
223
|
-
) != 0
|
|
224
|
-
map.putBoolean("isAirplaneMode", airplaneMode)
|
|
225
|
-
|
|
226
|
-
return map
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
@ReactMethod
|
|
230
|
-
fun getNetworkInfo(promise: Promise) {
|
|
231
|
-
try { promise.resolve(buildNetworkMap()) }
|
|
232
|
-
catch (e: Exception) { promise.reject("NETWORK_ERROR", e.message, e) }
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
@ReactMethod
|
|
236
|
-
fun startNetworkListener() {
|
|
237
|
-
if (networkCallback != null) return
|
|
238
|
-
val cb = object : ConnectivityManager.NetworkCallback() {
|
|
239
|
-
override fun onAvailable(network: Network) { emit("SystemBar_NetworkChange", buildNetworkMap()) }
|
|
240
|
-
override fun onLost(network: Network) { emit("SystemBar_NetworkChange", buildNetworkMap()) }
|
|
241
|
-
override fun onCapabilitiesChanged(n: Network, c: NetworkCapabilities) {
|
|
242
|
-
emit("SystemBar_NetworkChange", buildNetworkMap())
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
networkCallback = cb
|
|
246
|
-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
|
247
|
-
connectivityManager().registerDefaultNetworkCallback(cb)
|
|
248
|
-
} else {
|
|
249
|
-
connectivityManager().registerNetworkCallback(NetworkRequest.Builder().build(), cb)
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
@ReactMethod
|
|
254
|
-
fun stopNetworkListener() {
|
|
255
|
-
networkCallback?.let { connectivityManager().unregisterNetworkCallback(it) }
|
|
256
|
-
networkCallback = null
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
// ═══════════════════════════════════════════════
|
|
260
|
-
// 🆕 BATTERY
|
|
222
|
+
// SCREENCAST — list all presentation displays
|
|
261
223
|
// ═══════════════════════════════════════════════
|
|
262
224
|
|
|
263
|
-
private var
|
|
264
|
-
|
|
265
|
-
private fun buildBatteryMap(): WritableMap {
|
|
266
|
-
val bm = batteryManager()
|
|
267
|
-
val map = Arguments.createMap()
|
|
268
|
-
val level = bm.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
|
|
269
|
-
val status = bm.getIntProperty(BatteryManager.BATTERY_PROPERTY_STATUS)
|
|
270
|
-
|
|
271
|
-
val state = when (status) {
|
|
272
|
-
BatteryManager.BATTERY_STATUS_CHARGING -> "charging"
|
|
273
|
-
BatteryManager.BATTERY_STATUS_FULL -> "full"
|
|
274
|
-
BatteryManager.BATTERY_STATUS_DISCHARGING -> "discharging"
|
|
275
|
-
BatteryManager.BATTERY_STATUS_NOT_CHARGING -> "discharging"
|
|
276
|
-
else -> "unknown"
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
val isCharging = state == "charging" || state == "full"
|
|
280
|
-
|
|
281
|
-
map.putInt("level", level)
|
|
282
|
-
map.putString("state", state)
|
|
283
|
-
map.putBoolean("isCharging", isCharging)
|
|
284
|
-
map.putBoolean("isLow", level <= 20 && !isCharging)
|
|
285
|
-
return map
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
@ReactMethod
|
|
289
|
-
fun getBatteryInfo(promise: Promise) {
|
|
290
|
-
try { promise.resolve(buildBatteryMap()) }
|
|
291
|
-
catch (e: Exception) { promise.reject("BATTERY_ERROR", e.message, e) }
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
@ReactMethod
|
|
295
|
-
fun startBatteryListener() {
|
|
296
|
-
if (batteryReceiver != null) return
|
|
297
|
-
val receiver = object : BroadcastReceiver() {
|
|
298
|
-
override fun onReceive(ctx: Context?, intent: Intent?) {
|
|
299
|
-
emit("SystemBar_BatteryChange", buildBatteryMap())
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
batteryReceiver = receiver
|
|
303
|
-
val filter = IntentFilter().apply {
|
|
304
|
-
addAction(Intent.ACTION_BATTERY_CHANGED)
|
|
305
|
-
addAction(Intent.ACTION_BATTERY_LOW)
|
|
306
|
-
addAction(Intent.ACTION_BATTERY_OKAY)
|
|
307
|
-
addAction(Intent.ACTION_POWER_CONNECTED)
|
|
308
|
-
addAction(Intent.ACTION_POWER_DISCONNECTED)
|
|
309
|
-
}
|
|
310
|
-
reactContext.registerReceiver(receiver, filter)
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
@ReactMethod
|
|
314
|
-
fun stopBatteryListener() {
|
|
315
|
-
batteryReceiver?.let { reactContext.unregisterReceiver(it) }
|
|
316
|
-
batteryReceiver = null
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
// ═══════════════════════════════════════════════
|
|
320
|
-
// 🆕 HAPTIC FEEDBACK
|
|
321
|
-
// ═══════════════════════════════════════════════
|
|
225
|
+
private var displayListener: DisplayManager.DisplayListener? = null
|
|
322
226
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
val
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
"error" -> VibrationEffect.createWaveform(longArrayOf(0, 40, 40, 40, 40, 40), intArrayOf(0, 255, 0, 255, 0, 255), -1)
|
|
336
|
-
"selection" -> VibrationEffect.createOneShot(10, 60)
|
|
337
|
-
else -> VibrationEffect.createOneShot(30, 128)
|
|
338
|
-
}
|
|
339
|
-
vib.vibrate(effect)
|
|
340
|
-
} else {
|
|
341
|
-
@Suppress("DEPRECATION")
|
|
342
|
-
when (pattern) {
|
|
343
|
-
"light" -> vib.vibrate(20)
|
|
344
|
-
"medium" -> vib.vibrate(40)
|
|
345
|
-
"heavy" -> vib.vibrate(60)
|
|
346
|
-
"success" -> vib.vibrate(longArrayOf(0, 30, 60, 30), -1)
|
|
347
|
-
"warning" -> vib.vibrate(longArrayOf(0, 50, 80, 50), -1)
|
|
348
|
-
"error" -> vib.vibrate(longArrayOf(0, 40, 40, 40, 40, 40), -1)
|
|
349
|
-
"selection" -> vib.vibrate(10)
|
|
350
|
-
else -> vib.vibrate(30)
|
|
351
|
-
}
|
|
227
|
+
private fun buildDisplayListMap(): WritableMap {
|
|
228
|
+
val dm = displayManager()
|
|
229
|
+
val map = Arguments.createMap()
|
|
230
|
+
val displays = dm.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION)
|
|
231
|
+
val arr = Arguments.createArray()
|
|
232
|
+
|
|
233
|
+
for (d in displays) {
|
|
234
|
+
val item = Arguments.createMap()
|
|
235
|
+
item.putInt("id", d.displayId)
|
|
236
|
+
item.putString("name", d.name)
|
|
237
|
+
item.putBoolean("isValid", d.isValid)
|
|
238
|
+
arr.pushMap(item)
|
|
352
239
|
}
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
// ═══════════════════════════════════════════════
|
|
356
|
-
// 🆕 SCREENCAST
|
|
357
|
-
// ═══════════════════════════════════════════════
|
|
358
240
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
val dm = displayManager()
|
|
363
|
-
val map = Arguments.createMap()
|
|
364
|
-
val presentations = dm.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION)
|
|
365
|
-
val isCasting = presentations.isNotEmpty()
|
|
366
|
-
map.putBoolean("isCasting", isCasting)
|
|
367
|
-
if (isCasting) map.putString("displayName", presentations[0].name)
|
|
241
|
+
map.putBoolean("isCasting", displays.isNotEmpty())
|
|
242
|
+
map.putArray("displays", arr)
|
|
243
|
+
if (displays.isNotEmpty()) map.putString("displayName", displays[0].name)
|
|
368
244
|
else map.putNull("displayName")
|
|
369
245
|
return map
|
|
370
246
|
}
|
|
371
247
|
|
|
372
248
|
@ReactMethod
|
|
373
249
|
fun getScreencastInfo(promise: Promise) {
|
|
374
|
-
try { promise.resolve(
|
|
250
|
+
try { promise.resolve(buildDisplayListMap()) }
|
|
375
251
|
catch (e: Exception) { promise.reject("SCREENCAST_ERROR", e.message, e) }
|
|
376
252
|
}
|
|
377
253
|
|
|
@@ -379,9 +255,9 @@ class SystemBarModule(
|
|
|
379
255
|
fun startScreencastListener() {
|
|
380
256
|
if (displayListener != null) return
|
|
381
257
|
val listener = object : DisplayManager.DisplayListener {
|
|
382
|
-
override fun onDisplayAdded(id: Int)
|
|
383
|
-
override fun onDisplayRemoved(id: Int)
|
|
384
|
-
override fun onDisplayChanged(id: Int)
|
|
258
|
+
override fun onDisplayAdded(id: Int) { emit("SystemBar_ScreencastChange", buildDisplayListMap()) }
|
|
259
|
+
override fun onDisplayRemoved(id: Int) { emit("SystemBar_ScreencastChange", buildDisplayListMap()) }
|
|
260
|
+
override fun onDisplayChanged(id: Int) { emit("SystemBar_ScreencastChange", buildDisplayListMap()) }
|
|
385
261
|
}
|
|
386
262
|
displayListener = listener
|
|
387
263
|
displayManager().registerDisplayListener(listener, null)
|
|
@@ -393,58 +269,8 @@ class SystemBarModule(
|
|
|
393
269
|
displayListener = null
|
|
394
270
|
}
|
|
395
271
|
|
|
396
|
-
|
|
397
|
-
fun setSecureScreen(enable: Boolean) {
|
|
398
|
-
activity()?.runOnUiThread {
|
|
399
|
-
if (enable) activity()!!.window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
|
|
400
|
-
else activity()!!.window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
// ═══════════════════════════════════════════════
|
|
405
|
-
// 🆕 FONT SCALE
|
|
406
|
-
// ═══════════════════════════════════════════════
|
|
407
|
-
|
|
408
|
-
private var fontScaleReceiver: BroadcastReceiver? = null
|
|
409
|
-
|
|
410
|
-
private fun buildFontScaleMap(): WritableMap {
|
|
411
|
-
val map = Arguments.createMap()
|
|
412
|
-
val cfg = reactContext.resources.configuration
|
|
413
|
-
val dm = reactContext.resources.displayMetrics
|
|
414
|
-
map.putDouble("fontScale", cfg.fontScale.toDouble())
|
|
415
|
-
map.putDouble("density", dm.density.toDouble())
|
|
416
|
-
return map
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
@ReactMethod
|
|
420
|
-
fun getFontScaleInfo(promise: Promise) {
|
|
421
|
-
try { promise.resolve(buildFontScaleMap()) }
|
|
422
|
-
catch (e: Exception) { promise.reject("FONT_SCALE_ERROR", e.message, e) }
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
@ReactMethod
|
|
426
|
-
fun startFontScaleListener() {
|
|
427
|
-
if (fontScaleReceiver != null) return
|
|
428
|
-
val receiver = object : BroadcastReceiver() {
|
|
429
|
-
override fun onReceive(ctx: Context?, intent: Intent?) {
|
|
430
|
-
emit("SystemBar_FontScaleChange", buildFontScaleMap())
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
fontScaleReceiver = receiver
|
|
434
|
-
reactContext.registerReceiver(receiver, IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED))
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
@ReactMethod
|
|
438
|
-
fun stopFontScaleListener() {
|
|
439
|
-
fontScaleReceiver?.let { reactContext.unregisterReceiver(it) }
|
|
440
|
-
fontScaleReceiver = null
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
// ── Cleanup on host destroy ──────────────────
|
|
272
|
+
// ── Cleanup ──────────────────────────────────
|
|
444
273
|
override fun onCatalystInstanceDestroy() {
|
|
445
|
-
stopNetworkListener()
|
|
446
|
-
stopBatteryListener()
|
|
447
274
|
stopScreencastListener()
|
|
448
|
-
stopFontScaleListener()
|
|
449
275
|
}
|
|
450
276
|
}
|
package/index.ts
CHANGED
|
@@ -4,12 +4,5 @@
|
|
|
4
4
|
|
|
5
5
|
export * from "./src/SystemBar";
|
|
6
6
|
export * from "./src/types";
|
|
7
|
-
export {
|
|
8
|
-
useSystemBar,
|
|
9
|
-
useBattery,
|
|
10
|
-
useNetwork,
|
|
11
|
-
useScreencast,
|
|
12
|
-
useFontScale,
|
|
13
|
-
useHaptic,
|
|
14
|
-
} from "./src/useSystemBar";
|
|
7
|
+
export { useSystemBar, useScreencast } from "./src/useSystemBar";
|
|
15
8
|
export type { SystemBarConfig } from "./src/useSystemBar";
|
package/lib/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export * from "./src/SystemBar";
|
|
2
2
|
export * from "./src/types";
|
|
3
|
-
export { useSystemBar,
|
|
3
|
+
export { useSystemBar, useScreencast } from "./src/useSystemBar";
|
|
4
4
|
export type { SystemBarConfig } from "./src/useSystemBar";
|
package/lib/index.js
CHANGED
|
@@ -17,13 +17,9 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
17
17
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
18
18
|
};
|
|
19
19
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
-
exports.
|
|
20
|
+
exports.useScreencast = exports.useSystemBar = void 0;
|
|
21
21
|
__exportStar(require("./src/SystemBar"), exports);
|
|
22
22
|
__exportStar(require("./src/types"), exports);
|
|
23
23
|
var useSystemBar_1 = require("./src/useSystemBar");
|
|
24
24
|
Object.defineProperty(exports, "useSystemBar", { enumerable: true, get: function () { return useSystemBar_1.useSystemBar; } });
|
|
25
|
-
Object.defineProperty(exports, "useBattery", { enumerable: true, get: function () { return useSystemBar_1.useBattery; } });
|
|
26
|
-
Object.defineProperty(exports, "useNetwork", { enumerable: true, get: function () { return useSystemBar_1.useNetwork; } });
|
|
27
25
|
Object.defineProperty(exports, "useScreencast", { enumerable: true, get: function () { return useSystemBar_1.useScreencast; } });
|
|
28
|
-
Object.defineProperty(exports, "useFontScale", { enumerable: true, get: function () { return useSystemBar_1.useFontScale; } });
|
|
29
|
-
Object.defineProperty(exports, "useHaptic", { enumerable: true, get: function () { return useSystemBar_1.useHaptic; } });
|
package/lib/src/SystemBar.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
export declare const setNavigationBarColor: (color: string) =>
|
|
1
|
+
import type { NavigationBarBehavior, NavigationBarButtonStyle, NavigationBarStyle, NavigationBarVisibility, Orientation, ScreencastInfo, StatusBarStyle, VolumeStream } from "./types";
|
|
2
|
+
export declare const setNavigationBarColor: (color: string) => void;
|
|
3
3
|
export declare const setNavigationBarVisibility: (mode: NavigationBarVisibility) => Promise<void>;
|
|
4
4
|
export declare const setNavigationBarButtonStyle: (style: NavigationBarButtonStyle) => Promise<void>;
|
|
5
5
|
export declare const setNavigationBarStyle: (style: NavigationBarStyle) => Promise<void>;
|
|
@@ -14,60 +14,7 @@ export declare const getVolume: (stream?: VolumeStream) => Promise<number>;
|
|
|
14
14
|
export declare const setVolumeHUDVisible: (visible: boolean) => void;
|
|
15
15
|
export declare const keepScreenOn: (enable: boolean) => void;
|
|
16
16
|
export declare const immersiveMode: (enable: boolean) => void;
|
|
17
|
+
export declare const setSecureScreen: (enable: boolean) => void;
|
|
17
18
|
export declare const setOrientation: (mode: Orientation) => void;
|
|
18
|
-
/**
|
|
19
|
-
* Get current network connection info.
|
|
20
|
-
* Includes: type, isConnected, isAirplaneMode, ssid, cellularGeneration
|
|
21
|
-
*/
|
|
22
|
-
export declare const getNetworkInfo: () => Promise<NetworkInfo>;
|
|
23
|
-
/**
|
|
24
|
-
* Subscribe to network changes.
|
|
25
|
-
* @returns unsubscribe function — call it in useEffect cleanup
|
|
26
|
-
* @example
|
|
27
|
-
* const unsub = onNetworkChange((info) => console.log(info));
|
|
28
|
-
* return () => unsub();
|
|
29
|
-
*/
|
|
30
|
-
export declare const onNetworkChange: (callback: (info: NetworkInfo) => void) => (() => void);
|
|
31
|
-
/**
|
|
32
|
-
* Get current battery info: level (0–100), state, isCharging, isLow.
|
|
33
|
-
*/
|
|
34
|
-
export declare const getBatteryInfo: () => Promise<BatteryInfo>;
|
|
35
|
-
/**
|
|
36
|
-
* Subscribe to battery level / state changes.
|
|
37
|
-
* @returns unsubscribe function
|
|
38
|
-
*/
|
|
39
|
-
export declare const onBatteryChange: (callback: (info: BatteryInfo) => void) => (() => void);
|
|
40
|
-
/**
|
|
41
|
-
* Trigger haptic feedback.
|
|
42
|
-
*
|
|
43
|
-
* iOS: Uses UIImpactFeedbackGenerator / UINotificationFeedbackGenerator.
|
|
44
|
-
* Android: Uses Vibrator / VibrationEffect (API 26+).
|
|
45
|
-
*
|
|
46
|
-
* @param pattern "light" | "medium" | "heavy" | "success" | "warning" | "error" | "selection"
|
|
47
|
-
*/
|
|
48
|
-
export declare const haptic: (pattern: HapticPattern) => void;
|
|
49
|
-
/**
|
|
50
|
-
* Check if screen is currently being cast or mirrored.
|
|
51
|
-
*/
|
|
52
19
|
export declare const getScreencastInfo: () => Promise<ScreencastInfo>;
|
|
53
|
-
/**
|
|
54
|
-
* Subscribe to screencast state changes (started / stopped).
|
|
55
|
-
* @returns unsubscribe function
|
|
56
|
-
*/
|
|
57
20
|
export declare const onScreencastChange: (callback: (info: ScreencastInfo) => void) => (() => void);
|
|
58
|
-
/**
|
|
59
|
-
* Prevent screen content from appearing in screenshots / recordings.
|
|
60
|
-
* Useful for sensitive screens (payments, passwords).
|
|
61
|
-
* @platform android
|
|
62
|
-
*/
|
|
63
|
-
export declare const setSecureScreen: (enable: boolean) => void;
|
|
64
|
-
/**
|
|
65
|
-
* Get current system font scale and display density.
|
|
66
|
-
* Useful for adapting layout to accessibility settings.
|
|
67
|
-
*/
|
|
68
|
-
export declare const getFontScaleInfo: () => Promise<FontScaleInfo>;
|
|
69
|
-
/**
|
|
70
|
-
* Subscribe to font scale changes (user changes system text size).
|
|
71
|
-
* @returns unsubscribe function
|
|
72
|
-
*/
|
|
73
|
-
export declare const onFontScaleChange: (callback: (info: FontScaleInfo) => void) => (() => void);
|