rn-system-bar 3.0.3 → 3.1.1

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.
@@ -0,0 +1,9 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
2
+ package="com.systembar">
3
+
4
+ <!-- Required for writing system brightness -->
5
+ <uses-permission android:name="android.permission.WRITE_SETTINGS"
6
+ tools:ignore="ProtectedPermissions"
7
+ xmlns:tools="http://schemas.android.com/tools"/>
8
+
9
+ </manifest>
@@ -1,16 +1,14 @@
1
1
  // ─────────────────────────────────────────────
2
2
  // rn-system-bar · SystemBarModule.kt
3
- // v4 — Navigation bar removed (handled by expo-navigation-bar).
4
- // Status bar removed (handled by RN StatusBar).
5
- // Only: Brightness, Volume, Screen flags, Orientation.
6
- // Zero deprecated APIs.
7
3
  // ─────────────────────────────────────────────
8
4
 
9
5
  package com.systembar
10
6
 
11
7
  import android.app.Activity
12
8
  import android.content.Context
9
+ import android.content.Intent
13
10
  import android.content.pm.ActivityInfo
11
+ import android.hardware.display.DisplayManager
14
12
  import android.media.AudioManager
15
13
  import android.os.Build
16
14
  import android.provider.Settings
@@ -20,6 +18,7 @@ import android.view.WindowInsetsController
20
18
  import android.view.WindowManager
21
19
 
22
20
  import com.facebook.react.bridge.*
21
+ import com.facebook.react.modules.core.DeviceEventManagerModule
23
22
 
24
23
  class SystemBarModule(
25
24
  private val reactContext: ReactApplicationContext
@@ -29,9 +28,12 @@ class SystemBarModule(
29
28
 
30
29
  private fun activity(): Activity? = reactContext.currentActivity
31
30
 
32
- private fun audioManager(): AudioManager =
31
+ private fun audioManager() =
33
32
  reactContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager
34
33
 
34
+ private fun displayManager() =
35
+ reactContext.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
36
+
35
37
  private fun streamType(stream: String): Int = when (stream) {
36
38
  "ring" -> AudioManager.STREAM_RING
37
39
  "notification" -> AudioManager.STREAM_NOTIFICATION
@@ -40,37 +42,81 @@ class SystemBarModule(
40
42
  else -> AudioManager.STREAM_MUSIC
41
43
  }
42
44
 
45
+ // ═══════════════════════════════════════════════
46
+ // NAVIGATION BAR COLOR
47
+ // Direct Window API — edge-to-edge safe.
48
+ // ═══════════════════════════════════════════════
49
+
50
+ @ReactMethod
51
+ fun setNavigationBarColor(color: String) {
52
+ activity()?.runOnUiThread {
53
+ try {
54
+ activity()!!.window.navigationBarColor =
55
+ android.graphics.Color.parseColor(color)
56
+ } catch (_: Exception) {}
57
+ }
58
+ }
59
+
43
60
  // ═══════════════════════════════════════════════
44
61
  // 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.
45
72
  // ═══════════════════════════════════════════════
46
73
 
47
74
  @ReactMethod
48
75
  fun setBrightness(level: Float) {
49
- val act = activity() ?: return
50
- act.runOnUiThread {
51
- val lp = act.window.attributes
52
- lp.screenBrightness = level.coerceIn(0f, 1f)
53
- act.window.attributes = lp
76
+ val clamped = level.coerceIn(0.01f, 1f)
77
+
78
+ // 1️⃣ Window brightness — instant, no permission needed
79
+ activity()?.runOnUiThread {
80
+ val lp = activity()!!.window.attributes
81
+ lp.screenBrightness = clamped
82
+ activity()!!.window.attributes = lp
83
+ }
84
+
85
+ // 2️⃣ System brightness — persisted, needs WRITE_SETTINGS
86
+ val sysValue = (clamped * 255).toInt().coerceIn(1, 255)
87
+
88
+ if (Settings.System.canWrite(reactContext)) {
89
+ // Permission granted — write system brightness
90
+ Settings.System.putInt(
91
+ reactContext.contentResolver,
92
+ Settings.System.SCREEN_BRIGHTNESS,
93
+ sysValue
94
+ )
95
+ // Also disable auto-brightness so manual value sticks
96
+ Settings.System.putInt(
97
+ reactContext.contentResolver,
98
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
99
+ Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL
100
+ )
101
+ } else {
102
+ // No permission — open system settings so user can grant it once
103
+ val intent = Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS).apply {
104
+ flags = Intent.FLAG_ACTIVITY_NEW_TASK
105
+ }
106
+ try { reactContext.startActivity(intent) } catch (_: Exception) {}
54
107
  }
55
108
  }
56
109
 
57
110
  @ReactMethod
58
111
  fun getBrightness(promise: Promise) {
59
112
  try {
60
- val act = activity()
61
- if (act != null) {
62
- val lp = act.window.attributes
63
- if (lp.screenBrightness >= 0f) {
64
- promise.resolve(lp.screenBrightness.toDouble())
65
- return
66
- }
67
- }
68
- val sysBrightness = Settings.System.getInt(
113
+ // Always read from system setting so value matches device brightness bar
114
+ val sys = Settings.System.getInt(
69
115
  reactContext.contentResolver,
70
116
  Settings.System.SCREEN_BRIGHTNESS,
71
117
  128
72
118
  )
73
- promise.resolve(sysBrightness / 255.0)
119
+ promise.resolve(sys / 255.0)
74
120
  } catch (e: Exception) {
75
121
  promise.reject("BRIGHTNESS_ERROR", e.message, e)
76
122
  }
@@ -83,30 +129,26 @@ class SystemBarModule(
83
129
  private var suppressVolumeHUD = false
84
130
 
85
131
  @ReactMethod
86
- fun setVolumeHUDVisible(visible: Boolean) {
87
- suppressVolumeHUD = !visible
88
- }
132
+ fun setVolumeHUDVisible(visible: Boolean) { suppressVolumeHUD = !visible }
89
133
 
90
134
  @ReactMethod
91
135
  fun setVolume(level: Float, stream: String) {
92
136
  try {
93
- val am = audioManager()
137
+ val am = audioManager()
94
138
  val type = streamType(stream)
95
- val max = am.getStreamMaxVolume(type)
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)
139
+ val max = am.getStreamMaxVolume(type)
140
+ val vol = (level.coerceIn(0f, 1f) * max).toInt()
141
+ am.setStreamVolume(type, vol, if (suppressVolumeHUD) 0 else AudioManager.FLAG_SHOW_UI)
99
142
  } catch (_: Exception) {}
100
143
  }
101
144
 
102
145
  @ReactMethod
103
146
  fun getVolume(stream: String, promise: Promise) {
104
147
  try {
105
- val am = audioManager()
148
+ val am = audioManager()
106
149
  val type = streamType(stream)
107
- val current = am.getStreamVolume(type)
108
- val max = am.getStreamMaxVolume(type)
109
- promise.resolve(if (max > 0) current.toDouble() / max else 0.0)
150
+ val max = am.getStreamMaxVolume(type)
151
+ promise.resolve(if (max > 0) am.getStreamVolume(type).toDouble() / max else 0.0)
110
152
  } catch (e: Exception) {
111
153
  promise.reject("VOLUME_ERROR", e.message, e)
112
154
  }
@@ -118,38 +160,26 @@ class SystemBarModule(
118
160
 
119
161
  @ReactMethod
120
162
  fun keepScreenOn(enable: Boolean) {
121
- val act = activity() ?: return
122
- act.runOnUiThread {
123
- if (enable) {
124
- act.window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
125
- } else {
126
- act.window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
127
- }
163
+ activity()?.runOnUiThread {
164
+ if (enable) activity()!!.window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
165
+ else activity()!!.window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
128
166
  }
129
167
  }
130
168
 
131
169
  @ReactMethod
132
170
  fun immersiveMode(enable: Boolean) {
133
- val act = activity() ?: return
134
- act.runOnUiThread {
171
+ activity()?.runOnUiThread {
135
172
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
136
- // API 30+ — WindowInsetsController (modern, no deprecated flags)
137
- val controller = act.window.insetsController ?: return@runOnUiThread
173
+ val c = activity()!!.window.insetsController ?: return@runOnUiThread
138
174
  if (enable) {
139
- controller.hide(
140
- WindowInsets.Type.statusBars() or WindowInsets.Type.navigationBars()
141
- )
142
- controller.systemBarsBehavior =
143
- WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
175
+ c.hide(WindowInsets.Type.statusBars() or WindowInsets.Type.navigationBars())
176
+ c.systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
144
177
  } else {
145
- controller.show(
146
- WindowInsets.Type.statusBars() or WindowInsets.Type.navigationBars()
147
- )
178
+ c.show(WindowInsets.Type.statusBars() or WindowInsets.Type.navigationBars())
148
179
  }
149
180
  } else {
150
- // API 21–29: only path available; suppressed intentionally
151
181
  @Suppress("DEPRECATION")
152
- act.window.decorView.systemUiVisibility = if (enable) {
182
+ activity()!!.window.decorView.systemUiVisibility = if (enable) {
153
183
  View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
154
184
  View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
155
185
  View.SYSTEM_UI_FLAG_FULLSCREEN or
@@ -162,14 +192,21 @@ class SystemBarModule(
162
192
  }
163
193
  }
164
194
 
195
+ @ReactMethod
196
+ fun setSecureScreen(enable: Boolean) {
197
+ activity()?.runOnUiThread {
198
+ if (enable) activity()!!.window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
199
+ else activity()!!.window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
200
+ }
201
+ }
202
+
165
203
  // ═══════════════════════════════════════════════
166
204
  // ORIENTATION
167
205
  // ═══════════════════════════════════════════════
168
206
 
169
207
  @ReactMethod
170
208
  fun setOrientation(mode: String) {
171
- val act = activity() ?: return
172
- act.requestedOrientation = when (mode) {
209
+ activity()?.requestedOrientation = when (mode) {
173
210
  "portrait" -> ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
174
211
  "landscape" -> ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
175
212
  "landscape-left" -> ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE
@@ -177,4 +214,56 @@ class SystemBarModule(
177
214
  else -> ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR
178
215
  }
179
216
  }
180
- }
217
+
218
+ // ═══════════════════════════════════════════════
219
+ // SCREENCAST
220
+ // ═══════════════════════════════════════════════
221
+
222
+ private var displayListener: DisplayManager.DisplayListener? = null
223
+
224
+ private fun emit(event: String, data: WritableMap) {
225
+ reactContext
226
+ .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
227
+ .emit(event, data)
228
+ }
229
+
230
+ private fun buildScreencastMap(): WritableMap {
231
+ val dm = displayManager()
232
+ val map = Arguments.createMap()
233
+ val presentations = dm.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION)
234
+ val isCasting = presentations.isNotEmpty()
235
+ map.putBoolean("isCasting", isCasting)
236
+ if (isCasting) map.putString("displayName", presentations[0].name)
237
+ else map.putNull("displayName")
238
+ return map
239
+ }
240
+
241
+ @ReactMethod
242
+ fun getScreencastInfo(promise: Promise) {
243
+ try { promise.resolve(buildScreencastMap()) }
244
+ catch (e: Exception) { promise.reject("SCREENCAST_ERROR", e.message, e) }
245
+ }
246
+
247
+ @ReactMethod
248
+ fun startScreencastListener() {
249
+ if (displayListener != null) return
250
+ val listener = object : DisplayManager.DisplayListener {
251
+ override fun onDisplayAdded(id: Int) { emit("SystemBar_ScreencastChange", buildScreencastMap()) }
252
+ override fun onDisplayRemoved(id: Int) { emit("SystemBar_ScreencastChange", buildScreencastMap()) }
253
+ override fun onDisplayChanged(id: Int) { emit("SystemBar_ScreencastChange", buildScreencastMap()) }
254
+ }
255
+ displayListener = listener
256
+ displayManager().registerDisplayListener(listener, null)
257
+ }
258
+
259
+ @ReactMethod
260
+ fun stopScreencastListener() {
261
+ displayListener?.let { displayManager().unregisterDisplayListener(it) }
262
+ displayListener = null
263
+ }
264
+
265
+ // ── Cleanup ──────────────────────────────────
266
+ override fun onCatalystInstanceDestroy() {
267
+ stopScreencastListener()
268
+ }
269
+ }
@@ -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,5 @@
4
4
 
5
5
  export * from "./src/SystemBar";
6
6
  export * from "./src/types";
7
+ export { useSystemBar, useScreencast } from "./src/useSystemBar";
8
+ export type { SystemBarConfig } from "./src/useSystemBar";
@@ -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, NSObject)
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
- // Android-only stubs (no-ops on iOS)
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