rn-system-bar 3.0.3 → 3.1.0
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/java/com/systembar/SystemBarModule.kt +345 -75
- package/android/src/main/java/com/systembar/SystemBarPackage.kt +2 -1
- package/index.ts +9 -0
- package/ios/SystemBarModule.m +32 -12
- package/ios/SystemBarModule.swift +241 -83
- package/lib/index.d.ts +4 -0
- package/lib/index.js +29 -0
- package/lib/specs/NativeSystemBar.d.ts +21 -0
- package/lib/specs/NativeSystemBar.js +8 -0
- package/lib/src/SystemBar.d.ts +73 -0
- package/lib/src/SystemBar.js +292 -0
- package/lib/src/types.d.ts +44 -0
- package/lib/src/types.js +5 -0
- package/lib/src/useSystemBar.d.ts +49 -0
- package/lib/src/useSystemBar.js +194 -0
- package/package.json +14 -4
- package/rn-system-bar.podspec +1 -1
- package/src/SystemBar.ts +199 -122
- package/src/types.ts +70 -43
- package/src/useSystemBar.ts +191 -0
|
@@ -1,25 +1,36 @@
|
|
|
1
1
|
// ─────────────────────────────────────────────
|
|
2
|
-
// rn-system-bar · SystemBarModule.kt
|
|
3
|
-
//
|
|
4
|
-
// Status bar removed (handled by RN StatusBar).
|
|
5
|
-
// Only: Brightness, Volume, Screen flags, Orientation.
|
|
6
|
-
// Zero deprecated APIs.
|
|
2
|
+
// rn-system-bar · SystemBarModule.kt v5
|
|
3
|
+
// New: Network, Battery, Haptics, Screencast, FontScale
|
|
7
4
|
// ─────────────────────────────────────────────
|
|
8
5
|
|
|
9
6
|
package com.systembar
|
|
10
7
|
|
|
11
8
|
import android.app.Activity
|
|
9
|
+
import android.content.BroadcastReceiver
|
|
12
10
|
import android.content.Context
|
|
11
|
+
import android.content.Intent
|
|
12
|
+
import android.content.IntentFilter
|
|
13
13
|
import android.content.pm.ActivityInfo
|
|
14
|
+
import android.hardware.display.DisplayManager
|
|
14
15
|
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
|
|
15
21
|
import android.os.Build
|
|
22
|
+
import android.os.VibrationEffect
|
|
23
|
+
import android.os.Vibrator
|
|
24
|
+
import android.os.VibratorManager
|
|
16
25
|
import android.provider.Settings
|
|
26
|
+
import android.view.Display
|
|
17
27
|
import android.view.View
|
|
18
28
|
import android.view.WindowInsets
|
|
19
29
|
import android.view.WindowInsetsController
|
|
20
30
|
import android.view.WindowManager
|
|
21
31
|
|
|
22
32
|
import com.facebook.react.bridge.*
|
|
33
|
+
import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
23
34
|
|
|
24
35
|
class SystemBarModule(
|
|
25
36
|
private val reactContext: ReactApplicationContext
|
|
@@ -29,8 +40,23 @@ class SystemBarModule(
|
|
|
29
40
|
|
|
30
41
|
private fun activity(): Activity? = reactContext.currentActivity
|
|
31
42
|
|
|
32
|
-
private fun
|
|
33
|
-
reactContext
|
|
43
|
+
private fun emit(event: String, data: WritableMap) {
|
|
44
|
+
reactContext
|
|
45
|
+
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
46
|
+
.emit(event, data)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private fun audioManager() = reactContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
|
50
|
+
private fun connectivityManager() = reactContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
|
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
|
+
}
|
|
34
60
|
|
|
35
61
|
private fun streamType(stream: String): Int = when (stream) {
|
|
36
62
|
"ring" -> AudioManager.STREAM_RING
|
|
@@ -46,34 +72,23 @@ class SystemBarModule(
|
|
|
46
72
|
|
|
47
73
|
@ReactMethod
|
|
48
74
|
fun setBrightness(level: Float) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
val lp = act.window.attributes
|
|
75
|
+
activity()?.runOnUiThread {
|
|
76
|
+
val lp = activity()!!.window.attributes
|
|
52
77
|
lp.screenBrightness = level.coerceIn(0f, 1f)
|
|
53
|
-
|
|
78
|
+
activity()!!.window.attributes = lp
|
|
54
79
|
}
|
|
55
80
|
}
|
|
56
81
|
|
|
57
82
|
@ReactMethod
|
|
58
83
|
fun getBrightness(promise: Promise) {
|
|
59
84
|
try {
|
|
60
|
-
val
|
|
61
|
-
if (
|
|
62
|
-
|
|
63
|
-
if (lp.screenBrightness >= 0f) {
|
|
64
|
-
promise.resolve(lp.screenBrightness.toDouble())
|
|
65
|
-
return
|
|
66
|
-
}
|
|
85
|
+
val lp = activity()?.window?.attributes
|
|
86
|
+
if (lp != null && lp.screenBrightness >= 0f) {
|
|
87
|
+
promise.resolve(lp.screenBrightness.toDouble()); return
|
|
67
88
|
}
|
|
68
|
-
val
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
128
|
|
72
|
-
)
|
|
73
|
-
promise.resolve(sysBrightness / 255.0)
|
|
74
|
-
} catch (e: Exception) {
|
|
75
|
-
promise.reject("BRIGHTNESS_ERROR", e.message, e)
|
|
76
|
-
}
|
|
89
|
+
val sys = Settings.System.getInt(reactContext.contentResolver, Settings.System.SCREEN_BRIGHTNESS, 128)
|
|
90
|
+
promise.resolve(sys / 255.0)
|
|
91
|
+
} catch (e: Exception) { promise.reject("BRIGHTNESS_ERROR", e.message, e) }
|
|
77
92
|
}
|
|
78
93
|
|
|
79
94
|
// ═══════════════════════════════════════════════
|
|
@@ -82,34 +97,24 @@ class SystemBarModule(
|
|
|
82
97
|
|
|
83
98
|
private var suppressVolumeHUD = false
|
|
84
99
|
|
|
85
|
-
@ReactMethod
|
|
86
|
-
fun setVolumeHUDVisible(visible: Boolean) {
|
|
87
|
-
suppressVolumeHUD = !visible
|
|
88
|
-
}
|
|
100
|
+
@ReactMethod fun setVolumeHUDVisible(visible: Boolean) { suppressVolumeHUD = !visible }
|
|
89
101
|
|
|
90
102
|
@ReactMethod
|
|
91
103
|
fun setVolume(level: Float, stream: String) {
|
|
92
104
|
try {
|
|
93
|
-
val am = audioManager()
|
|
94
|
-
val
|
|
95
|
-
|
|
96
|
-
val vol = (level.coerceIn(0f, 1f) * max).toInt()
|
|
97
|
-
val flags = if (suppressVolumeHUD) 0 else AudioManager.FLAG_SHOW_UI
|
|
98
|
-
am.setStreamVolume(type, vol, flags)
|
|
105
|
+
val am = audioManager(); val type = streamType(stream)
|
|
106
|
+
val vol = (level.coerceIn(0f, 1f) * am.getStreamMaxVolume(type)).toInt()
|
|
107
|
+
am.setStreamVolume(type, vol, if (suppressVolumeHUD) 0 else AudioManager.FLAG_SHOW_UI)
|
|
99
108
|
} catch (_: Exception) {}
|
|
100
109
|
}
|
|
101
110
|
|
|
102
111
|
@ReactMethod
|
|
103
112
|
fun getVolume(stream: String, promise: Promise) {
|
|
104
113
|
try {
|
|
105
|
-
val am = audioManager()
|
|
106
|
-
val type = streamType(stream)
|
|
107
|
-
val current = am.getStreamVolume(type)
|
|
114
|
+
val am = audioManager(); val type = streamType(stream)
|
|
108
115
|
val max = am.getStreamMaxVolume(type)
|
|
109
|
-
promise.resolve(if (max > 0)
|
|
110
|
-
} catch (e: Exception) {
|
|
111
|
-
promise.reject("VOLUME_ERROR", e.message, e)
|
|
112
|
-
}
|
|
116
|
+
promise.resolve(if (max > 0) am.getStreamVolume(type).toDouble() / max else 0.0)
|
|
117
|
+
} catch (e: Exception) { promise.reject("VOLUME_ERROR", e.message, e) }
|
|
113
118
|
}
|
|
114
119
|
|
|
115
120
|
// ═══════════════════════════════════════════════
|
|
@@ -118,46 +123,30 @@ class SystemBarModule(
|
|
|
118
123
|
|
|
119
124
|
@ReactMethod
|
|
120
125
|
fun keepScreenOn(enable: Boolean) {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
act.window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
|
125
|
-
} else {
|
|
126
|
-
act.window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
|
127
|
-
}
|
|
126
|
+
activity()?.runOnUiThread {
|
|
127
|
+
if (enable) activity()!!.window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
|
128
|
+
else activity()!!.window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
|
128
129
|
}
|
|
129
130
|
}
|
|
130
131
|
|
|
131
132
|
@ReactMethod
|
|
132
133
|
fun immersiveMode(enable: Boolean) {
|
|
133
|
-
|
|
134
|
-
act.runOnUiThread {
|
|
134
|
+
activity()?.runOnUiThread {
|
|
135
135
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
136
|
-
|
|
137
|
-
val controller = act.window.insetsController ?: return@runOnUiThread
|
|
136
|
+
val c = activity()!!.window.insetsController ?: return@runOnUiThread
|
|
138
137
|
if (enable) {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
)
|
|
142
|
-
controller.systemBarsBehavior =
|
|
143
|
-
WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
|
138
|
+
c.hide(WindowInsets.Type.statusBars() or WindowInsets.Type.navigationBars())
|
|
139
|
+
c.systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
|
144
140
|
} else {
|
|
145
|
-
|
|
146
|
-
WindowInsets.Type.statusBars() or WindowInsets.Type.navigationBars()
|
|
147
|
-
)
|
|
141
|
+
c.show(WindowInsets.Type.statusBars() or WindowInsets.Type.navigationBars())
|
|
148
142
|
}
|
|
149
143
|
} else {
|
|
150
|
-
// API 21–29: only path available; suppressed intentionally
|
|
151
144
|
@Suppress("DEPRECATION")
|
|
152
|
-
|
|
153
|
-
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
|
|
154
|
-
View.
|
|
155
|
-
View.SYSTEM_UI_FLAG_FULLSCREEN or
|
|
156
|
-
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or
|
|
145
|
+
activity()!!.window.decorView.systemUiVisibility = if (enable) {
|
|
146
|
+
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
|
|
147
|
+
View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or
|
|
157
148
|
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
|
158
|
-
} else {
|
|
159
|
-
View.SYSTEM_UI_FLAG_VISIBLE
|
|
160
|
-
}
|
|
149
|
+
} else { View.SYSTEM_UI_FLAG_VISIBLE }
|
|
161
150
|
}
|
|
162
151
|
}
|
|
163
152
|
}
|
|
@@ -168,8 +157,7 @@ class SystemBarModule(
|
|
|
168
157
|
|
|
169
158
|
@ReactMethod
|
|
170
159
|
fun setOrientation(mode: String) {
|
|
171
|
-
|
|
172
|
-
act.requestedOrientation = when (mode) {
|
|
160
|
+
activity()?.requestedOrientation = when (mode) {
|
|
173
161
|
"portrait" -> ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
|
|
174
162
|
"landscape" -> ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
|
|
175
163
|
"landscape-left" -> ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE
|
|
@@ -177,4 +165,286 @@ class SystemBarModule(
|
|
|
177
165
|
else -> ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR
|
|
178
166
|
}
|
|
179
167
|
}
|
|
180
|
-
|
|
168
|
+
|
|
169
|
+
// ═══════════════════════════════════════════════
|
|
170
|
+
// 🆕 NETWORK INFO
|
|
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
|
|
261
|
+
// ═══════════════════════════════════════════════
|
|
262
|
+
|
|
263
|
+
private var batteryReceiver: BroadcastReceiver? = null
|
|
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
|
+
// ═══════════════════════════════════════════════
|
|
322
|
+
|
|
323
|
+
@ReactMethod
|
|
324
|
+
fun haptic(pattern: String) {
|
|
325
|
+
val vib = vibrator()
|
|
326
|
+
if (!vib.hasVibrator()) return
|
|
327
|
+
|
|
328
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
329
|
+
val effect = when (pattern) {
|
|
330
|
+
"light" -> VibrationEffect.createOneShot(20, 80)
|
|
331
|
+
"medium" -> VibrationEffect.createOneShot(40, 128)
|
|
332
|
+
"heavy" -> VibrationEffect.createOneShot(60, 255)
|
|
333
|
+
"success" -> VibrationEffect.createWaveform(longArrayOf(0, 30, 60, 30), intArrayOf(0, 180, 0, 255), -1)
|
|
334
|
+
"warning" -> VibrationEffect.createWaveform(longArrayOf(0, 50, 80, 50), intArrayOf(0, 200, 0, 200), -1)
|
|
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
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// ═══════════════════════════════════════════════
|
|
356
|
+
// 🆕 SCREENCAST
|
|
357
|
+
// ═══════════════════════════════════════════════
|
|
358
|
+
|
|
359
|
+
private var displayListener: DisplayManager.DisplayListener? = null
|
|
360
|
+
|
|
361
|
+
private fun buildScreencastMap(): WritableMap {
|
|
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)
|
|
368
|
+
else map.putNull("displayName")
|
|
369
|
+
return map
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
@ReactMethod
|
|
373
|
+
fun getScreencastInfo(promise: Promise) {
|
|
374
|
+
try { promise.resolve(buildScreencastMap()) }
|
|
375
|
+
catch (e: Exception) { promise.reject("SCREENCAST_ERROR", e.message, e) }
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
@ReactMethod
|
|
379
|
+
fun startScreencastListener() {
|
|
380
|
+
if (displayListener != null) return
|
|
381
|
+
val listener = object : DisplayManager.DisplayListener {
|
|
382
|
+
override fun onDisplayAdded(id: Int) { emit("SystemBar_ScreencastChange", buildScreencastMap()) }
|
|
383
|
+
override fun onDisplayRemoved(id: Int) { emit("SystemBar_ScreencastChange", buildScreencastMap()) }
|
|
384
|
+
override fun onDisplayChanged(id: Int) { emit("SystemBar_ScreencastChange", buildScreencastMap()) }
|
|
385
|
+
}
|
|
386
|
+
displayListener = listener
|
|
387
|
+
displayManager().registerDisplayListener(listener, null)
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
@ReactMethod
|
|
391
|
+
fun stopScreencastListener() {
|
|
392
|
+
displayListener?.let { displayManager().unregisterDisplayListener(it) }
|
|
393
|
+
displayListener = null
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
@ReactMethod
|
|
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 ──────────────────
|
|
444
|
+
override fun onCatalystInstanceDestroy() {
|
|
445
|
+
stopNetworkListener()
|
|
446
|
+
stopBatteryListener()
|
|
447
|
+
stopScreencastListener()
|
|
448
|
+
stopFontScaleListener()
|
|
449
|
+
}
|
|
450
|
+
}
|
|
@@ -9,13 +9,14 @@ import com.facebook.react.bridge.NativeModule
|
|
|
9
9
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
10
10
|
import com.facebook.react.uimanager.ViewManager
|
|
11
11
|
|
|
12
|
+
// @Suppress at class level — covers all overrides of deprecated ReactPackage members
|
|
13
|
+
@Suppress("DEPRECATION", "OVERRIDE_DEPRECATION")
|
|
12
14
|
class SystemBarPackage : ReactPackage {
|
|
13
15
|
|
|
14
16
|
override fun createNativeModules(
|
|
15
17
|
reactContext: ReactApplicationContext
|
|
16
18
|
): List<NativeModule> = listOf(SystemBarModule(reactContext))
|
|
17
19
|
|
|
18
|
-
@Suppress("DEPRECATION")
|
|
19
20
|
override fun createViewManagers(
|
|
20
21
|
reactContext: ReactApplicationContext
|
|
21
22
|
): List<ViewManager<*, *>> = emptyList()
|
package/index.ts
CHANGED
|
@@ -4,3 +4,12 @@
|
|
|
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";
|
|
15
|
+
export type { SystemBarConfig } from "./src/useSystemBar";
|
package/ios/SystemBarModule.m
CHANGED
|
@@ -1,40 +1,60 @@
|
|
|
1
1
|
// ─────────────────────────────────────────────
|
|
2
|
-
// rn-system-bar · SystemBarModule.m
|
|
2
|
+
// rn-system-bar · SystemBarModule.m v5
|
|
3
3
|
// Objective-C bridge — exposes Swift to React Native
|
|
4
4
|
// ─────────────────────────────────────────────
|
|
5
5
|
|
|
6
6
|
#import <React/RCTBridgeModule.h>
|
|
7
|
+
#import <React/RCTEventEmitter.h>
|
|
7
8
|
|
|
8
|
-
@interface RCT_EXTERN_MODULE(SystemBar,
|
|
9
|
-
|
|
10
|
-
// Status Bar
|
|
11
|
-
RCT_EXTERN_METHOD(setStatusBarStyle:(NSString *)style)
|
|
12
|
-
RCT_EXTERN_METHOD(setStatusBarVisibility:(BOOL)visible)
|
|
9
|
+
@interface RCT_EXTERN_MODULE(SystemBar, RCTEventEmitter)
|
|
13
10
|
|
|
14
11
|
// Brightness
|
|
15
12
|
RCT_EXTERN_METHOD(setBrightness:(float)level)
|
|
16
|
-
RCT_EXTERN_METHOD(getBrightness:(RCTPromiseResolveBlock)resolve
|
|
17
|
-
rejecter:(RCTPromiseRejectBlock)reject)
|
|
13
|
+
RCT_EXTERN_METHOD(getBrightness:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
|
|
18
14
|
|
|
19
15
|
// Volume
|
|
20
16
|
RCT_EXTERN_METHOD(setVolume:(float)level stream:(NSString *)stream)
|
|
21
|
-
RCT_EXTERN_METHOD(getVolume:(NSString *)stream
|
|
22
|
-
resolver:(RCTPromiseResolveBlock)resolve
|
|
23
|
-
rejecter:(RCTPromiseRejectBlock)reject)
|
|
17
|
+
RCT_EXTERN_METHOD(getVolume:(NSString *)stream resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
|
|
24
18
|
|
|
25
19
|
// Screen
|
|
26
20
|
RCT_EXTERN_METHOD(keepScreenOn:(BOOL)enable)
|
|
21
|
+
RCT_EXTERN_METHOD(setSecureScreen:(BOOL)enable)
|
|
27
22
|
|
|
28
23
|
// Orientation
|
|
29
24
|
RCT_EXTERN_METHOD(setOrientation:(NSString *)mode)
|
|
30
25
|
|
|
31
|
-
//
|
|
26
|
+
// Network
|
|
27
|
+
RCT_EXTERN_METHOD(getNetworkInfo:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
|
|
28
|
+
RCT_EXTERN_METHOD(startNetworkListener)
|
|
29
|
+
RCT_EXTERN_METHOD(stopNetworkListener)
|
|
30
|
+
|
|
31
|
+
// Battery
|
|
32
|
+
RCT_EXTERN_METHOD(getBatteryInfo:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
|
|
33
|
+
RCT_EXTERN_METHOD(startBatteryListener)
|
|
34
|
+
RCT_EXTERN_METHOD(stopBatteryListener)
|
|
35
|
+
|
|
36
|
+
// Haptics
|
|
37
|
+
RCT_EXTERN_METHOD(haptic:(NSString *)pattern)
|
|
38
|
+
|
|
39
|
+
// Screencast
|
|
40
|
+
RCT_EXTERN_METHOD(getScreencastInfo:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
|
|
41
|
+
RCT_EXTERN_METHOD(startScreencastListener)
|
|
42
|
+
RCT_EXTERN_METHOD(stopScreencastListener)
|
|
43
|
+
|
|
44
|
+
// Font Scale
|
|
45
|
+
RCT_EXTERN_METHOD(getFontScaleInfo:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
|
|
46
|
+
RCT_EXTERN_METHOD(startFontScaleListener)
|
|
47
|
+
RCT_EXTERN_METHOD(stopFontScaleListener)
|
|
48
|
+
|
|
49
|
+
// Android-only stubs
|
|
32
50
|
RCT_EXTERN_METHOD(setNavigationBarColor:(NSString *)color)
|
|
33
51
|
RCT_EXTERN_METHOD(setNavigationBarVisibility:(NSString *)mode)
|
|
34
52
|
RCT_EXTERN_METHOD(setNavigationBarButtonStyle:(NSString *)style)
|
|
35
53
|
RCT_EXTERN_METHOD(setNavigationBarStyle:(NSString *)style)
|
|
36
54
|
RCT_EXTERN_METHOD(setNavigationBarBehavior:(NSString *)behavior)
|
|
37
55
|
RCT_EXTERN_METHOD(setStatusBarColor:(NSString *)color)
|
|
56
|
+
RCT_EXTERN_METHOD(setStatusBarStyle:(NSString *)style)
|
|
57
|
+
RCT_EXTERN_METHOD(setStatusBarVisibility:(BOOL)visible)
|
|
38
58
|
RCT_EXTERN_METHOD(setVolumeHUDVisible:(BOOL)visible)
|
|
39
59
|
RCT_EXTERN_METHOD(immersiveMode:(BOOL)enable)
|
|
40
60
|
|