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.
- package/android/src/main/AndroidManifest.xml +9 -0
- package/android/src/main/java/com/systembar/SystemBarModule.kt +145 -56
- package/android/src/main/java/com/systembar/SystemBarPackage.kt +2 -1
- package/index.ts +2 -0
- package/ios/SystemBarModule.m +32 -12
- package/ios/SystemBarModule.swift +241 -83
- package/lib/index.d.ts +4 -0
- package/lib/index.js +25 -0
- package/lib/specs/NativeSystemBar.d.ts +21 -0
- package/lib/specs/NativeSystemBar.js +8 -0
- package/lib/src/SystemBar.d.ts +20 -0
- package/lib/src/SystemBar.js +176 -0
- package/lib/src/types.d.ts +11 -0
- package/lib/src/types.js +5 -0
- package/lib/src/useSystemBar.d.ts +19 -0
- package/lib/src/useSystemBar.js +102 -0
- package/package.json +15 -4
- package/src/SystemBar.ts +82 -135
- package/src/types.ts +10 -48
- package/src/useSystemBar.ts +90 -0
|
@@ -1,46 +1,47 @@
|
|
|
1
1
|
// ─────────────────────────────────────────────
|
|
2
|
-
// rn-system-bar · SystemBarModule.swift
|
|
3
|
-
//
|
|
2
|
+
// rn-system-bar · SystemBarModule.swift v5
|
|
3
|
+
// New: Network, Battery, Haptics, Screencast, FontScale
|
|
4
4
|
// ─────────────────────────────────────────────
|
|
5
5
|
|
|
6
6
|
import Foundation
|
|
7
7
|
import UIKit
|
|
8
8
|
import AVFoundation
|
|
9
|
+
import CoreHaptics
|
|
10
|
+
import Network
|
|
11
|
+
import SystemConfiguration
|
|
9
12
|
|
|
10
13
|
@objc(SystemBar)
|
|
11
|
-
class SystemBarModule:
|
|
14
|
+
class SystemBarModule: RCTEventEmitter {
|
|
12
15
|
|
|
13
|
-
// ──
|
|
14
|
-
|
|
15
|
-
return
|
|
16
|
+
// ── Event names ────────────────────────────
|
|
17
|
+
override func supportedEvents() -> [String]! {
|
|
18
|
+
return [
|
|
19
|
+
"SystemBar_NetworkChange",
|
|
20
|
+
"SystemBar_BatteryChange",
|
|
21
|
+
"SystemBar_ScreencastChange",
|
|
22
|
+
"SystemBar_FontScaleChange",
|
|
23
|
+
]
|
|
16
24
|
}
|
|
17
25
|
|
|
18
|
-
|
|
19
|
-
// STATUS BAR (iOS supports these natively)
|
|
20
|
-
// ─────────────────────────────────────────────
|
|
26
|
+
override static func requiresMainQueueSetup() -> Bool { return false }
|
|
21
27
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
: UIStatusBarStyle.darkContent
|
|
28
|
+
// ── Listeners state ────────────────────────
|
|
29
|
+
private var networkMonitor: NWPathMonitor?
|
|
30
|
+
private var batteryObservers: [NSObjectProtocol] = []
|
|
31
|
+
private var screencastObserver: NSObjectProtocol?
|
|
32
|
+
private var fontScaleObserver: NSObjectProtocol?
|
|
33
|
+
private var hasListeners = false
|
|
29
34
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
35
|
+
override func startObserving() { hasListeners = true }
|
|
36
|
+
override func stopObserving() { hasListeners = false }
|
|
33
37
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
DispatchQueue.main.async {
|
|
37
|
-
UIApplication.shared.isStatusBarHidden = !visible
|
|
38
|
-
}
|
|
38
|
+
private func emit(_ event: String, body: Any) {
|
|
39
|
+
if hasListeners { sendEvent(withName: event, body: body) }
|
|
39
40
|
}
|
|
40
41
|
|
|
41
|
-
//
|
|
42
|
+
// ═══════════════════════════════════════════════
|
|
42
43
|
// BRIGHTNESS
|
|
43
|
-
//
|
|
44
|
+
// ═══════════════════════════════════════════════
|
|
44
45
|
|
|
45
46
|
@objc
|
|
46
47
|
func setBrightness(_ level: Float) {
|
|
@@ -51,86 +52,244 @@ class SystemBarModule: NSObject {
|
|
|
51
52
|
|
|
52
53
|
@objc
|
|
53
54
|
func getBrightness(_ resolve: @escaping RCTPromiseResolveBlock,
|
|
54
|
-
rejecter reject:
|
|
55
|
-
DispatchQueue.main.async {
|
|
56
|
-
resolve(Double(UIScreen.main.brightness))
|
|
57
|
-
}
|
|
55
|
+
rejecter reject: RCTPromiseRejectBlock) {
|
|
56
|
+
DispatchQueue.main.async { resolve(Double(UIScreen.main.brightness)) }
|
|
58
57
|
}
|
|
59
58
|
|
|
60
|
-
//
|
|
59
|
+
// ═══════════════════════════════════════════════
|
|
61
60
|
// VOLUME
|
|
62
|
-
//
|
|
61
|
+
// ═══════════════════════════════════════════════
|
|
63
62
|
|
|
64
|
-
@objc
|
|
65
|
-
func setVolume(_ level: Float, stream: String) {
|
|
66
|
-
// AVAudioSession volume cannot be set programmatically on iOS
|
|
67
|
-
// (Apple restriction). Use MPVolumeView or inform user.
|
|
68
|
-
// We silently ignore to prevent crashes.
|
|
69
|
-
}
|
|
63
|
+
@objc func setVolume(_ level: Float, stream: String) { /* iOS restriction — no-op */ }
|
|
70
64
|
|
|
71
65
|
@objc
|
|
72
66
|
func getVolume(_ stream: String,
|
|
73
67
|
resolver resolve: @escaping RCTPromiseResolveBlock,
|
|
74
|
-
rejecter reject:
|
|
75
|
-
|
|
76
|
-
resolve(Double(session.outputVolume))
|
|
68
|
+
rejecter reject: RCTPromiseRejectBlock) {
|
|
69
|
+
resolve(Double(AVAudioSession.sharedInstance().outputVolume))
|
|
77
70
|
}
|
|
78
71
|
|
|
79
|
-
//
|
|
72
|
+
// ═══════════════════════════════════════════════
|
|
80
73
|
// SCREEN
|
|
81
|
-
//
|
|
74
|
+
// ═══════════════════════════════════════════════
|
|
82
75
|
|
|
83
|
-
@objc
|
|
84
|
-
|
|
85
|
-
DispatchQueue.main.async {
|
|
86
|
-
UIApplication.shared.isIdleTimerDisabled = enable
|
|
87
|
-
}
|
|
76
|
+
@objc func keepScreenOn(_ enable: Bool) {
|
|
77
|
+
DispatchQueue.main.async { UIApplication.shared.isIdleTimerDisabled = enable }
|
|
88
78
|
}
|
|
89
79
|
|
|
90
|
-
//
|
|
80
|
+
// ═══════════════════════════════════════════════
|
|
91
81
|
// ORIENTATION
|
|
92
|
-
//
|
|
82
|
+
// ═══════════════════════════════════════════════
|
|
93
83
|
|
|
94
|
-
@objc
|
|
95
|
-
func setOrientation(_ mode: String) {
|
|
84
|
+
@objc func setOrientation(_ mode: String) {
|
|
96
85
|
DispatchQueue.main.async {
|
|
97
|
-
var mask: UIInterfaceOrientationMask
|
|
86
|
+
var mask: UIInterfaceOrientationMask
|
|
98
87
|
switch mode {
|
|
99
|
-
case "portrait":
|
|
100
|
-
|
|
101
|
-
case "landscape":
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
mask = .landscapeLeft
|
|
105
|
-
case "landscape-right":
|
|
106
|
-
mask = .landscapeRight
|
|
107
|
-
default: // "auto"
|
|
108
|
-
mask = .all
|
|
88
|
+
case "portrait": mask = .portrait
|
|
89
|
+
case "landscape": mask = .landscape
|
|
90
|
+
case "landscape-left": mask = .landscapeLeft
|
|
91
|
+
case "landscape-right":mask = .landscapeRight
|
|
92
|
+
default: mask = .all
|
|
109
93
|
}
|
|
110
|
-
|
|
111
|
-
// iOS 16+
|
|
112
94
|
if #available(iOS 16.0, *) {
|
|
113
|
-
|
|
114
|
-
.
|
|
115
|
-
windowScene?.requestGeometryUpdate(
|
|
116
|
-
.iOS(interfaceOrientations: mask)
|
|
117
|
-
)
|
|
95
|
+
(UIApplication.shared.connectedScenes.first as? UIWindowScene)?
|
|
96
|
+
.requestGeometryUpdate(.iOS(interfaceOrientations: mask))
|
|
118
97
|
} else {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
98
|
+
UIDevice.current.setValue(UIInterfaceOrientation.portrait.rawValue, forKey: "orientation")
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ═══════════════════════════════════════════════
|
|
104
|
+
// 🆕 NETWORK INFO
|
|
105
|
+
// ═══════════════════════════════════════════════
|
|
106
|
+
|
|
107
|
+
private func currentNetworkMap() -> [String: Any] {
|
|
108
|
+
var isConnected = false
|
|
109
|
+
var type = "unknown"
|
|
110
|
+
|
|
111
|
+
// Use NWPathMonitor snapshot via reachability
|
|
112
|
+
if let reachability = SCNetworkReachabilityCreateWithName(nil, "apple.com") {
|
|
113
|
+
var flags: SCNetworkReachabilityFlags = []
|
|
114
|
+
SCNetworkReachabilityGetFlags(reachability, &flags)
|
|
115
|
+
isConnected = flags.contains(.reachable) && !flags.contains(.connectionRequired)
|
|
116
|
+
if flags.contains(.isWWAN) { type = "cellular" }
|
|
117
|
+
else if isConnected { type = "wifi" }
|
|
118
|
+
else { type = "none" }
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
let airplaneMode = false // iOS doesn't expose this to apps
|
|
122
|
+
return [
|
|
123
|
+
"type": type,
|
|
124
|
+
"isConnected": isConnected,
|
|
125
|
+
"isAirplaneMode": airplaneMode,
|
|
126
|
+
"ssid": NSNull(),
|
|
127
|
+
"cellularGeneration": NSNull(),
|
|
128
|
+
]
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
@objc
|
|
132
|
+
func getNetworkInfo(_ resolve: @escaping RCTPromiseResolveBlock,
|
|
133
|
+
rejecter reject: RCTPromiseRejectBlock) {
|
|
134
|
+
resolve(currentNetworkMap())
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
@objc func startNetworkListener() {
|
|
138
|
+
networkMonitor?.cancel()
|
|
139
|
+
let monitor = NWPathMonitor()
|
|
140
|
+
monitor.pathUpdateHandler = { [weak self] _ in
|
|
141
|
+
self?.emit("SystemBar_NetworkChange", body: self?.currentNetworkMap() ?? [:])
|
|
142
|
+
}
|
|
143
|
+
monitor.start(queue: DispatchQueue.global(qos: .background))
|
|
144
|
+
networkMonitor = monitor
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
@objc func stopNetworkListener() {
|
|
148
|
+
networkMonitor?.cancel(); networkMonitor = nil
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// ═══════════════════════════════════════════════
|
|
152
|
+
// 🆕 BATTERY
|
|
153
|
+
// ═══════════════════════════════════════════════
|
|
154
|
+
|
|
155
|
+
private func currentBatteryMap() -> [String: Any] {
|
|
156
|
+
UIDevice.current.isBatteryMonitoringEnabled = true
|
|
157
|
+
let raw = UIDevice.current.batteryLevel // -1 if unknown
|
|
158
|
+
let level = raw >= 0 ? Int(raw * 100) : -1
|
|
159
|
+
let state: String
|
|
160
|
+
switch UIDevice.current.batteryState {
|
|
161
|
+
case .charging: state = "charging"
|
|
162
|
+
case .full: state = "full"
|
|
163
|
+
case .unplugged: state = "discharging"
|
|
164
|
+
default: state = "unknown"
|
|
165
|
+
}
|
|
166
|
+
let isCharging = state == "charging" || state == "full"
|
|
167
|
+
return [
|
|
168
|
+
"level": level,
|
|
169
|
+
"state": state,
|
|
170
|
+
"isCharging": isCharging,
|
|
171
|
+
"isLow": level >= 0 && level <= 20 && !isCharging,
|
|
172
|
+
]
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
@objc
|
|
176
|
+
func getBatteryInfo(_ resolve: @escaping RCTPromiseResolveBlock,
|
|
177
|
+
rejecter reject: RCTPromiseRejectBlock) {
|
|
178
|
+
UIDevice.current.isBatteryMonitoringEnabled = true
|
|
179
|
+
resolve(currentBatteryMap())
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
@objc func startBatteryListener() {
|
|
183
|
+
UIDevice.current.isBatteryMonitoringEnabled = true
|
|
184
|
+
let nc = NotificationCenter.default
|
|
185
|
+
batteryObservers = [
|
|
186
|
+
nc.addObserver(forName: UIDevice.batteryLevelDidChangeNotification, object: nil, queue: .main) { [weak self] _ in
|
|
187
|
+
self?.emit("SystemBar_BatteryChange", body: self?.currentBatteryMap() ?? [:])
|
|
188
|
+
},
|
|
189
|
+
nc.addObserver(forName: UIDevice.batteryStateDidChangeNotification, object: nil, queue: .main) { [weak self] _ in
|
|
190
|
+
self?.emit("SystemBar_BatteryChange", body: self?.currentBatteryMap() ?? [:])
|
|
191
|
+
},
|
|
192
|
+
]
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
@objc func stopBatteryListener() {
|
|
196
|
+
batteryObservers.forEach { NotificationCenter.default.removeObserver($0) }
|
|
197
|
+
batteryObservers = []
|
|
198
|
+
UIDevice.current.isBatteryMonitoringEnabled = false
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// ═══════════════════════════════════════════════
|
|
202
|
+
// 🆕 HAPTIC FEEDBACK
|
|
203
|
+
// ═══════════════════════════════════════════════
|
|
204
|
+
|
|
205
|
+
@objc func haptic(_ pattern: String) {
|
|
206
|
+
DispatchQueue.main.async {
|
|
207
|
+
switch pattern {
|
|
208
|
+
case "light":
|
|
209
|
+
UIImpactFeedbackGenerator(style: .light).impactOccurred()
|
|
210
|
+
case "medium":
|
|
211
|
+
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
|
|
212
|
+
case "heavy":
|
|
213
|
+
UIImpactFeedbackGenerator(style: .heavy).impactOccurred()
|
|
214
|
+
case "success":
|
|
215
|
+
UINotificationFeedbackGenerator().notificationOccurred(.success)
|
|
216
|
+
case "warning":
|
|
217
|
+
UINotificationFeedbackGenerator().notificationOccurred(.warning)
|
|
218
|
+
case "error":
|
|
219
|
+
UINotificationFeedbackGenerator().notificationOccurred(.error)
|
|
220
|
+
case "selection":
|
|
221
|
+
UISelectionFeedbackGenerator().selectionChanged()
|
|
222
|
+
default:
|
|
223
|
+
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
|
|
124
224
|
}
|
|
125
225
|
}
|
|
126
226
|
}
|
|
127
227
|
|
|
128
|
-
//
|
|
129
|
-
//
|
|
130
|
-
//
|
|
131
|
-
|
|
132
|
-
|
|
228
|
+
// ═══════════════════════════════════════════════
|
|
229
|
+
// 🆕 SCREENCAST
|
|
230
|
+
// ═══════════════════════════════════════════════
|
|
231
|
+
|
|
232
|
+
private func currentScreencastMap() -> [String: Any] {
|
|
233
|
+
let isCasting = UIScreen.screens.count > 1
|
|
234
|
+
return [
|
|
235
|
+
"isCasting": isCasting,
|
|
236
|
+
"displayName": isCasting ? "External Display" : NSNull(),
|
|
237
|
+
]
|
|
238
|
+
}
|
|
133
239
|
|
|
240
|
+
@objc
|
|
241
|
+
func getScreencastInfo(_ resolve: @escaping RCTPromiseResolveBlock,
|
|
242
|
+
rejecter reject: RCTPromiseRejectBlock) {
|
|
243
|
+
resolve(currentScreencastMap())
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
@objc func startScreencastListener() {
|
|
247
|
+
screencastObserver = NotificationCenter.default.addObserver(
|
|
248
|
+
forName: UIScreen.didConnectNotification, object: nil, queue: .main
|
|
249
|
+
) { [weak self] _ in
|
|
250
|
+
self?.emit("SystemBar_ScreencastChange", body: self?.currentScreencastMap() ?? [:])
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
@objc func stopScreencastListener() {
|
|
255
|
+
if let obs = screencastObserver { NotificationCenter.default.removeObserver(obs) }
|
|
256
|
+
screencastObserver = nil
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// setSecureScreen — iOS doesn't support this via public API (no-op)
|
|
260
|
+
@objc func setSecureScreen(_ enable: Bool) {}
|
|
261
|
+
|
|
262
|
+
// ═══════════════════════════════════════════════
|
|
263
|
+
// 🆕 FONT SCALE
|
|
264
|
+
// ═══════════════════════════════════════════════
|
|
265
|
+
|
|
266
|
+
private func currentFontScaleMap() -> [String: Any] {
|
|
267
|
+
return [
|
|
268
|
+
"fontScale": Double(UIApplication.shared.preferredContentSizeCategory == .large ? 1.0 : 1.0),
|
|
269
|
+
"density": Double(UIScreen.main.scale),
|
|
270
|
+
]
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
@objc
|
|
274
|
+
func getFontScaleInfo(_ resolve: @escaping RCTPromiseResolveBlock,
|
|
275
|
+
rejecter reject: RCTPromiseRejectBlock) {
|
|
276
|
+
resolve(currentFontScaleMap())
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
@objc func startFontScaleListener() {
|
|
280
|
+
fontScaleObserver = NotificationCenter.default.addObserver(
|
|
281
|
+
forName: UIContentSizeCategory.didChangeNotification, object: nil, queue: .main
|
|
282
|
+
) { [weak self] _ in
|
|
283
|
+
self?.emit("SystemBar_FontScaleChange", body: self?.currentFontScaleMap() ?? [:])
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
@objc func stopFontScaleListener() {
|
|
288
|
+
if let obs = fontScaleObserver { NotificationCenter.default.removeObserver(obs) }
|
|
289
|
+
fontScaleObserver = nil
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// ── Android-only stubs ─────────────────────
|
|
134
293
|
@objc func setNavigationBarColor(_ color: String) {}
|
|
135
294
|
@objc func setNavigationBarVisibility(_ mode: String) {}
|
|
136
295
|
@objc func setNavigationBarButtonStyle(_ style: String) {}
|
|
@@ -139,7 +298,6 @@ class SystemBarModule: NSObject {
|
|
|
139
298
|
@objc func setStatusBarColor(_ color: String) {}
|
|
140
299
|
@objc func setVolumeHUDVisible(_ visible: Bool) {}
|
|
141
300
|
@objc func immersiveMode(_ enable: Bool) {}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
@objc static func requiresMainQueueSetup() -> Bool { return false }
|
|
301
|
+
@objc func setStatusBarStyle(_ style: String) {}
|
|
302
|
+
@objc func setStatusBarVisibility(_ visible: Bool) {}
|
|
145
303
|
}
|
package/lib/index.d.ts
ADDED
package/lib/index.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ─────────────────────────────────────────────
|
|
3
|
+
// rn-system-bar · index.ts
|
|
4
|
+
// ─────────────────────────────────────────────
|
|
5
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
8
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
9
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
10
|
+
}
|
|
11
|
+
Object.defineProperty(o, k2, desc);
|
|
12
|
+
}) : (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
o[k2] = m[k];
|
|
15
|
+
}));
|
|
16
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
17
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
18
|
+
};
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
exports.useScreencast = exports.useSystemBar = void 0;
|
|
21
|
+
__exportStar(require("./src/SystemBar"), exports);
|
|
22
|
+
__exportStar(require("./src/types"), exports);
|
|
23
|
+
var useSystemBar_1 = require("./src/useSystemBar");
|
|
24
|
+
Object.defineProperty(exports, "useSystemBar", { enumerable: true, get: function () { return useSystemBar_1.useSystemBar; } });
|
|
25
|
+
Object.defineProperty(exports, "useScreencast", { enumerable: true, get: function () { return useSystemBar_1.useScreencast; } });
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { TurboModule } from "react-native";
|
|
2
|
+
export interface Spec extends TurboModule {
|
|
3
|
+
setNavigationBarColor(color: string): void;
|
|
4
|
+
setNavigationBarVisibility(mode: string): void;
|
|
5
|
+
setNavigationBarButtonStyle(style: string): void;
|
|
6
|
+
setNavigationBarStyle(style: string): void;
|
|
7
|
+
setNavigationBarBehavior(behavior: string): void;
|
|
8
|
+
setStatusBarColor(color: string): void;
|
|
9
|
+
setStatusBarStyle(style: string): void;
|
|
10
|
+
setStatusBarVisibility(visible: boolean): void;
|
|
11
|
+
setBrightness(level: number): void;
|
|
12
|
+
getBrightness(): Promise<number>;
|
|
13
|
+
setVolume(level: number, stream: string): void;
|
|
14
|
+
getVolume(stream: string): Promise<number>;
|
|
15
|
+
setVolumeHUDVisible(visible: boolean): void;
|
|
16
|
+
keepScreenOn(enable: boolean): void;
|
|
17
|
+
immersiveMode(enable: boolean): void;
|
|
18
|
+
setOrientation(mode: string): void;
|
|
19
|
+
}
|
|
20
|
+
declare const _default: Spec;
|
|
21
|
+
export default _default;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ─────────────────────────────────────────────
|
|
3
|
+
// rn-system-bar · NativeSystemBar.ts
|
|
4
|
+
// TurboModule spec — New Architecture (JSI)
|
|
5
|
+
// ─────────────────────────────────────────────
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const react_native_1 = require("react-native");
|
|
8
|
+
exports.default = react_native_1.TurboModuleRegistry.getEnforcing("SystemBar");
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { NavigationBarBehavior, NavigationBarButtonStyle, NavigationBarStyle, NavigationBarVisibility, Orientation, ScreencastInfo, StatusBarStyle, VolumeStream } from "./types";
|
|
2
|
+
export declare const setNavigationBarColor: (color: string) => void;
|
|
3
|
+
export declare const setNavigationBarVisibility: (mode: NavigationBarVisibility) => Promise<void>;
|
|
4
|
+
export declare const setNavigationBarButtonStyle: (style: NavigationBarButtonStyle) => Promise<void>;
|
|
5
|
+
export declare const setNavigationBarStyle: (style: NavigationBarStyle) => Promise<void>;
|
|
6
|
+
export declare const setNavigationBarBehavior: (behavior: NavigationBarBehavior) => Promise<void>;
|
|
7
|
+
export declare const setStatusBarColor: (color: string, animated?: boolean) => void;
|
|
8
|
+
export declare const setStatusBarStyle: (style: StatusBarStyle, animated?: boolean) => void;
|
|
9
|
+
export declare const setStatusBarVisibility: (visible: boolean, animated?: boolean) => void;
|
|
10
|
+
export declare const setBrightness: (level: number) => void;
|
|
11
|
+
export declare const getBrightness: () => Promise<number>;
|
|
12
|
+
export declare const setVolume: (level: number, stream?: VolumeStream) => void;
|
|
13
|
+
export declare const getVolume: (stream?: VolumeStream) => Promise<number>;
|
|
14
|
+
export declare const setVolumeHUDVisible: (visible: boolean) => void;
|
|
15
|
+
export declare const keepScreenOn: (enable: boolean) => void;
|
|
16
|
+
export declare const immersiveMode: (enable: boolean) => void;
|
|
17
|
+
export declare const setSecureScreen: (enable: boolean) => void;
|
|
18
|
+
export declare const setOrientation: (mode: Orientation) => void;
|
|
19
|
+
export declare const getScreencastInfo: () => Promise<ScreencastInfo>;
|
|
20
|
+
export declare const onScreencastChange: (callback: (info: ScreencastInfo) => void) => (() => void);
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ─────────────────────────────────────────────
|
|
3
|
+
// rn-system-bar · SystemBar.ts
|
|
4
|
+
// ─────────────────────────────────────────────
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.onScreencastChange = exports.getScreencastInfo = exports.setOrientation = exports.setSecureScreen = exports.immersiveMode = exports.keepScreenOn = exports.setVolumeHUDVisible = exports.getVolume = exports.setVolume = exports.getBrightness = exports.setBrightness = exports.setStatusBarVisibility = exports.setStatusBarStyle = exports.setStatusBarColor = exports.setNavigationBarBehavior = exports.setNavigationBarStyle = exports.setNavigationBarButtonStyle = exports.setNavigationBarVisibility = exports.setNavigationBarColor = void 0;
|
|
7
|
+
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
|
+
const { SystemBar: Native } = react_native_1.NativeModules;
|
|
17
|
+
const isAndroid = react_native_1.Platform.OS === "android";
|
|
18
|
+
const androidOnly = (name) => {
|
|
19
|
+
if (!isAndroid) {
|
|
20
|
+
if (__DEV__)
|
|
21
|
+
console.warn(`[rn-system-bar] ${name}() is Android-only, no-op on iOS.`);
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
return true;
|
|
25
|
+
};
|
|
26
|
+
const checkNative = () => {
|
|
27
|
+
if (!Native)
|
|
28
|
+
throw new Error("[rn-system-bar] Native module not found. Rebuild your project.");
|
|
29
|
+
};
|
|
30
|
+
// ═══════════════════════════════════════════════
|
|
31
|
+
// NAVIGATION BAR (Android-only)
|
|
32
|
+
// Color → native Window API (edge-to-edge safe)
|
|
33
|
+
// Others → expo-navigation-bar (work in edge-to-edge)
|
|
34
|
+
// ═══════════════════════════════════════════════
|
|
35
|
+
const setNavigationBarColor = (color) => {
|
|
36
|
+
if (!androidOnly("setNavigationBarColor"))
|
|
37
|
+
return;
|
|
38
|
+
checkNative();
|
|
39
|
+
Native.setNavigationBarColor(color);
|
|
40
|
+
};
|
|
41
|
+
exports.setNavigationBarColor = setNavigationBarColor;
|
|
42
|
+
const setNavigationBarVisibility = (mode) => {
|
|
43
|
+
var _a;
|
|
44
|
+
if (!androidOnly("setNavigationBarVisibility"))
|
|
45
|
+
return Promise.resolve();
|
|
46
|
+
return (_a = NavBar === null || NavBar === void 0 ? void 0 : NavBar.setVisibilityAsync(mode)) !== null && _a !== void 0 ? _a : Promise.resolve();
|
|
47
|
+
};
|
|
48
|
+
exports.setNavigationBarVisibility = setNavigationBarVisibility;
|
|
49
|
+
const setNavigationBarButtonStyle = (style) => {
|
|
50
|
+
var _a;
|
|
51
|
+
if (!androidOnly("setNavigationBarButtonStyle"))
|
|
52
|
+
return Promise.resolve();
|
|
53
|
+
return (_a = NavBar === null || NavBar === void 0 ? void 0 : NavBar.setButtonStyleAsync(style)) !== null && _a !== void 0 ? _a : Promise.resolve();
|
|
54
|
+
};
|
|
55
|
+
exports.setNavigationBarButtonStyle = setNavigationBarButtonStyle;
|
|
56
|
+
const setNavigationBarStyle = (style) => {
|
|
57
|
+
var _a;
|
|
58
|
+
if (!androidOnly("setNavigationBarStyle"))
|
|
59
|
+
return Promise.resolve();
|
|
60
|
+
const btn = style === "dark" || style === "auto" ? "dark" : "light";
|
|
61
|
+
return (_a = NavBar === null || NavBar === void 0 ? void 0 : NavBar.setButtonStyleAsync(btn)) !== null && _a !== void 0 ? _a : Promise.resolve();
|
|
62
|
+
};
|
|
63
|
+
exports.setNavigationBarStyle = setNavigationBarStyle;
|
|
64
|
+
const setNavigationBarBehavior = (behavior) => {
|
|
65
|
+
var _a;
|
|
66
|
+
if (!androidOnly("setNavigationBarBehavior"))
|
|
67
|
+
return Promise.resolve();
|
|
68
|
+
return (_a = NavBar === null || NavBar === void 0 ? void 0 : NavBar.setBehaviorAsync(behavior)) !== null && _a !== void 0 ? _a : Promise.resolve();
|
|
69
|
+
};
|
|
70
|
+
exports.setNavigationBarBehavior = setNavigationBarBehavior;
|
|
71
|
+
// ═══════════════════════════════════════════════
|
|
72
|
+
// STATUS BAR
|
|
73
|
+
// ═══════════════════════════════════════════════
|
|
74
|
+
const setStatusBarColor = (color, animated = false) => {
|
|
75
|
+
if (!androidOnly("setStatusBarColor"))
|
|
76
|
+
return;
|
|
77
|
+
react_native_1.StatusBar.setBackgroundColor(color, animated);
|
|
78
|
+
};
|
|
79
|
+
exports.setStatusBarColor = setStatusBarColor;
|
|
80
|
+
const setStatusBarStyle = (style, animated = false) => {
|
|
81
|
+
react_native_1.StatusBar.setBarStyle(style === "light" ? "light-content" : "dark-content", animated);
|
|
82
|
+
};
|
|
83
|
+
exports.setStatusBarStyle = setStatusBarStyle;
|
|
84
|
+
const setStatusBarVisibility = (visible, animated = false) => {
|
|
85
|
+
react_native_1.StatusBar.setHidden(!visible, animated ? "slide" : "none");
|
|
86
|
+
};
|
|
87
|
+
exports.setStatusBarVisibility = setStatusBarVisibility;
|
|
88
|
+
// ═══════════════════════════════════════════════
|
|
89
|
+
// 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
|
+
// ═══════════════════════════════════════════════
|
|
98
|
+
const setBrightness = (level) => {
|
|
99
|
+
checkNative();
|
|
100
|
+
Native.setBrightness(Math.max(0.01, Math.min(1, level)));
|
|
101
|
+
};
|
|
102
|
+
exports.setBrightness = setBrightness;
|
|
103
|
+
const getBrightness = () => {
|
|
104
|
+
checkNative();
|
|
105
|
+
return Native.getBrightness();
|
|
106
|
+
};
|
|
107
|
+
exports.getBrightness = getBrightness;
|
|
108
|
+
// ═══════════════════════════════════════════════
|
|
109
|
+
// VOLUME
|
|
110
|
+
// ═══════════════════════════════════════════════
|
|
111
|
+
const setVolume = (level, stream = "music") => {
|
|
112
|
+
checkNative();
|
|
113
|
+
Native.setVolume(Math.max(0, Math.min(1, level)), stream);
|
|
114
|
+
};
|
|
115
|
+
exports.setVolume = setVolume;
|
|
116
|
+
const getVolume = (stream = "music") => {
|
|
117
|
+
checkNative();
|
|
118
|
+
return Native.getVolume(stream);
|
|
119
|
+
};
|
|
120
|
+
exports.getVolume = getVolume;
|
|
121
|
+
const setVolumeHUDVisible = (visible) => {
|
|
122
|
+
if (!androidOnly("setVolumeHUDVisible"))
|
|
123
|
+
return;
|
|
124
|
+
checkNative();
|
|
125
|
+
Native.setVolumeHUDVisible(visible);
|
|
126
|
+
};
|
|
127
|
+
exports.setVolumeHUDVisible = setVolumeHUDVisible;
|
|
128
|
+
// ═══════════════════════════════════════════════
|
|
129
|
+
// SCREEN
|
|
130
|
+
// ═══════════════════════════════════════════════
|
|
131
|
+
const keepScreenOn = (enable) => {
|
|
132
|
+
checkNative();
|
|
133
|
+
Native.keepScreenOn(enable);
|
|
134
|
+
};
|
|
135
|
+
exports.keepScreenOn = keepScreenOn;
|
|
136
|
+
const immersiveMode = (enable) => {
|
|
137
|
+
if (!androidOnly("immersiveMode"))
|
|
138
|
+
return;
|
|
139
|
+
checkNative();
|
|
140
|
+
Native.immersiveMode(enable);
|
|
141
|
+
};
|
|
142
|
+
exports.immersiveMode = immersiveMode;
|
|
143
|
+
const setSecureScreen = (enable) => {
|
|
144
|
+
if (!androidOnly("setSecureScreen"))
|
|
145
|
+
return;
|
|
146
|
+
checkNative();
|
|
147
|
+
Native.setSecureScreen(enable);
|
|
148
|
+
};
|
|
149
|
+
exports.setSecureScreen = setSecureScreen;
|
|
150
|
+
// ═══════════════════════════════════════════════
|
|
151
|
+
// ORIENTATION
|
|
152
|
+
// ═══════════════════════════════════════════════
|
|
153
|
+
const setOrientation = (mode) => {
|
|
154
|
+
checkNative();
|
|
155
|
+
Native.setOrientation(mode);
|
|
156
|
+
};
|
|
157
|
+
exports.setOrientation = setOrientation;
|
|
158
|
+
// ═══════════════════════════════════════════════
|
|
159
|
+
// SCREENCAST
|
|
160
|
+
// ═══════════════════════════════════════════════
|
|
161
|
+
const getScreencastInfo = () => {
|
|
162
|
+
checkNative();
|
|
163
|
+
return Native.getScreencastInfo();
|
|
164
|
+
};
|
|
165
|
+
exports.getScreencastInfo = getScreencastInfo;
|
|
166
|
+
const onScreencastChange = (callback) => {
|
|
167
|
+
checkNative();
|
|
168
|
+
const { DeviceEventEmitter } = require("react-native");
|
|
169
|
+
Native.startScreencastListener();
|
|
170
|
+
const sub = DeviceEventEmitter.addListener("SystemBar_ScreencastChange", callback);
|
|
171
|
+
return () => {
|
|
172
|
+
sub.remove();
|
|
173
|
+
Native.stopScreencastListener();
|
|
174
|
+
};
|
|
175
|
+
};
|
|
176
|
+
exports.onScreencastChange = onScreencastChange;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type NavigationBarBehavior = "overlay-swipe" | "inset-swipe" | "inset-touch";
|
|
2
|
+
export type NavigationBarButtonStyle = "light" | "dark";
|
|
3
|
+
export type NavigationBarStyle = "auto" | "inverted" | "light" | "dark";
|
|
4
|
+
export type NavigationBarVisibility = "visible" | "hidden";
|
|
5
|
+
export type StatusBarStyle = "light" | "dark";
|
|
6
|
+
export type Orientation = "portrait" | "landscape" | "landscape-left" | "landscape-right" | "auto";
|
|
7
|
+
export type VolumeStream = "music" | "ring" | "notification" | "alarm" | "system";
|
|
8
|
+
export interface ScreencastInfo {
|
|
9
|
+
isCasting: boolean;
|
|
10
|
+
displayName: string | null;
|
|
11
|
+
}
|