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/src/SystemBar.ts CHANGED
@@ -1,27 +1,20 @@
1
1
  // ─────────────────────────────────────────────
2
- // rn-system-bar · SystemBar.ts v5
3
- // All features — Android + iOS
2
+ // rn-system-bar · SystemBar.ts
4
3
  // ─────────────────────────────────────────────
5
4
 
6
- import { NativeModules, Platform, StatusBar, Vibration } from "react-native";
5
+ import { NativeModules, Platform, StatusBar } from "react-native";
7
6
 
8
7
  import type {
9
- BatteryInfo,
10
- FontScaleInfo,
11
- HapticPattern,
12
8
  NavigationBarBehavior,
13
9
  NavigationBarButtonStyle,
14
10
  NavigationBarStyle,
15
11
  NavigationBarVisibility,
16
- NetworkInfo,
17
12
  Orientation,
18
13
  ScreencastInfo,
19
14
  StatusBarStyle,
20
15
  VolumeStream,
21
16
  } from "./types";
22
17
 
23
- // expo-navigation-bar — peer dep, loaded at runtime
24
- // eslint-disable-next-line @typescript-eslint/no-var-requires
25
18
  const NavBar = (() => {
26
19
  try { return require("expo-navigation-bar"); }
27
20
  catch { return null; }
@@ -30,7 +23,6 @@ const NavBar = (() => {
30
23
  const { SystemBar: Native } = NativeModules;
31
24
 
32
25
  const isAndroid = Platform.OS === "android";
33
- const isIOS = Platform.OS === "ios";
34
26
 
35
27
  const androidOnly = (name: string): boolean => {
36
28
  if (!isAndroid) {
@@ -45,12 +37,15 @@ const checkNative = () => {
45
37
  };
46
38
 
47
39
  // ═══════════════════════════════════════════════
48
- // NAVIGATION BAR (Android — expo-navigation-bar)
40
+ // NAVIGATION BAR (Android-only)
41
+ // Color → native Window API (edge-to-edge safe)
42
+ // Others → expo-navigation-bar (work in edge-to-edge)
49
43
  // ═══════════════════════════════════════════════
50
44
 
51
- export const setNavigationBarColor = (color: string): Promise<void> => {
52
- if (!androidOnly("setNavigationBarColor")) return Promise.resolve();
53
- return NavBar?.setBackgroundColorAsync(color) ?? Promise.resolve();
45
+ export const setNavigationBarColor = (color: string): void => {
46
+ if (!androidOnly("setNavigationBarColor")) return;
47
+ checkNative();
48
+ Native.setNavigationBarColor(color);
54
49
  };
55
50
 
56
51
  export const setNavigationBarVisibility = (mode: NavigationBarVisibility): Promise<void> => {
@@ -93,11 +88,18 @@ export const setStatusBarVisibility = (visible: boolean, animated = false): void
93
88
 
94
89
  // ═══════════════════════════════════════════════
95
90
  // BRIGHTNESS
91
+ //
92
+ // setBrightness → updates BOTH window brightness (instant)
93
+ // AND system brightness (persisted).
94
+ // On first call: opens WRITE_SETTINGS if not granted.
95
+ //
96
+ // getBrightness → reads SYSTEM brightness so slider always
97
+ // matches the device brightness bar.
96
98
  // ═══════════════════════════════════════════════
97
99
 
98
100
  export const setBrightness = (level: number): void => {
99
101
  checkNative();
100
- Native.setBrightness(Math.max(0, Math.min(1, level)));
102
+ Native.setBrightness(Math.max(0.01, Math.min(1, level)));
101
103
  };
102
104
 
103
105
  export const getBrightness = (): Promise<number> => {
@@ -140,121 +142,30 @@ export const immersiveMode = (enable: boolean): void => {
140
142
  Native.immersiveMode(enable);
141
143
  };
142
144
 
143
- // ═══════════════════════════════════════════════
144
- // ORIENTATION
145
- // ═══════════════════════════════════════════════
146
-
147
- export const setOrientation = (mode: Orientation): void => {
148
- checkNative();
149
- Native.setOrientation(mode);
150
- };
151
-
152
- // ═══════════════════════════════════════════════
153
- // 🆕 NETWORK INFO
154
- // ═══════════════════════════════════════════════
155
-
156
- /**
157
- * Get current network connection info.
158
- * Includes: type, isConnected, isAirplaneMode, ssid, cellularGeneration
159
- */
160
- export const getNetworkInfo = (): Promise<NetworkInfo> => {
161
- checkNative();
162
- return Native.getNetworkInfo();
163
- };
164
-
165
- /**
166
- * Subscribe to network changes.
167
- * @returns unsubscribe function — call it in useEffect cleanup
168
- * @example
169
- * const unsub = onNetworkChange((info) => console.log(info));
170
- * return () => unsub();
171
- */
172
- export const onNetworkChange = (
173
- callback: (info: NetworkInfo) => void
174
- ): (() => void) => {
145
+ export const setSecureScreen = (enable: boolean): void => {
146
+ if (!androidOnly("setSecureScreen")) return;
175
147
  checkNative();
176
- // Native side emits "SystemBar_NetworkChange" events
177
- const { DeviceEventEmitter } = require("react-native");
178
- Native.startNetworkListener();
179
- const sub = DeviceEventEmitter.addListener("SystemBar_NetworkChange", callback);
180
- return () => {
181
- sub.remove();
182
- Native.stopNetworkListener();
183
- };
148
+ Native.setSecureScreen(enable);
184
149
  };
185
150
 
186
151
  // ═══════════════════════════════════════════════
187
- // 🆕 BATTERY
152
+ // ORIENTATION
188
153
  // ═══════════════════════════════════════════════
189
154
 
190
- /**
191
- * Get current battery info: level (0–100), state, isCharging, isLow.
192
- */
193
- export const getBatteryInfo = (): Promise<BatteryInfo> => {
194
- checkNative();
195
- return Native.getBatteryInfo();
196
- };
197
-
198
- /**
199
- * Subscribe to battery level / state changes.
200
- * @returns unsubscribe function
201
- */
202
- export const onBatteryChange = (
203
- callback: (info: BatteryInfo) => void
204
- ): (() => void) => {
155
+ export const setOrientation = (mode: Orientation): void => {
205
156
  checkNative();
206
- const { DeviceEventEmitter } = require("react-native");
207
- Native.startBatteryListener();
208
- const sub = DeviceEventEmitter.addListener("SystemBar_BatteryChange", callback);
209
- return () => {
210
- sub.remove();
211
- Native.stopBatteryListener();
212
- };
213
- };
214
-
215
- // ═══════════════════════════════════════════════
216
- // 🆕 HAPTIC FEEDBACK
217
- // ═══════════════════════════════════════════════
218
-
219
- /**
220
- * Trigger haptic feedback.
221
- *
222
- * iOS: Uses UIImpactFeedbackGenerator / UINotificationFeedbackGenerator.
223
- * Android: Uses Vibrator / VibrationEffect (API 26+).
224
- *
225
- * @param pattern "light" | "medium" | "heavy" | "success" | "warning" | "error" | "selection"
226
- */
227
- export const haptic = (pattern: HapticPattern): void => {
228
- if (isIOS) {
229
- checkNative();
230
- Native.haptic(pattern);
231
- return;
232
- }
233
-
234
- // Android fallback using RN's built-in Vibration API
235
- // when native module is unavailable
236
- if (isAndroid) {
237
- checkNative();
238
- Native.haptic(pattern);
239
- }
157
+ Native.setOrientation(mode);
240
158
  };
241
159
 
242
160
  // ═══════════════════════════════════════════════
243
- // 🆕 SCREENCAST DETECTION
161
+ // SCREENCAST
244
162
  // ═══════════════════════════════════════════════
245
163
 
246
- /**
247
- * Check if screen is currently being cast or mirrored.
248
- */
249
164
  export const getScreencastInfo = (): Promise<ScreencastInfo> => {
250
165
  checkNative();
251
166
  return Native.getScreencastInfo();
252
167
  };
253
168
 
254
- /**
255
- * Subscribe to screencast state changes (started / stopped).
256
- * @returns unsubscribe function
257
- */
258
169
  export const onScreencastChange = (
259
170
  callback: (info: ScreencastInfo) => void
260
171
  ): (() => void) => {
@@ -267,44 +178,3 @@ export const onScreencastChange = (
267
178
  Native.stopScreencastListener();
268
179
  };
269
180
  };
270
-
271
- /**
272
- * Prevent screen content from appearing in screenshots / recordings.
273
- * Useful for sensitive screens (payments, passwords).
274
- * @platform android
275
- */
276
- export const setSecureScreen = (enable: boolean): void => {
277
- if (!androidOnly("setSecureScreen")) return;
278
- checkNative();
279
- Native.setSecureScreen(enable);
280
- };
281
-
282
- // ═══════════════════════════════════════════════
283
- // 🆕 FONT SCALE / DISPLAY INFO
284
- // ═══════════════════════════════════════════════
285
-
286
- /**
287
- * Get current system font scale and display density.
288
- * Useful for adapting layout to accessibility settings.
289
- */
290
- export const getFontScaleInfo = (): Promise<FontScaleInfo> => {
291
- checkNative();
292
- return Native.getFontScaleInfo();
293
- };
294
-
295
- /**
296
- * Subscribe to font scale changes (user changes system text size).
297
- * @returns unsubscribe function
298
- */
299
- export const onFontScaleChange = (
300
- callback: (info: FontScaleInfo) => void
301
- ): (() => void) => {
302
- checkNative();
303
- const { DeviceEventEmitter } = require("react-native");
304
- Native.startFontScaleListener();
305
- const sub = DeviceEventEmitter.addListener("SystemBar_FontScaleChange", callback);
306
- return () => {
307
- sub.remove();
308
- Native.stopFontScaleListener();
309
- };
310
- };
package/src/types.ts CHANGED
@@ -1,8 +1,7 @@
1
1
  // ─────────────────────────────────────────────
2
- // rn-system-bar · types.ts v5
2
+ // rn-system-bar · types.ts
3
3
  // ─────────────────────────────────────────────
4
4
 
5
- // ── Navigation Bar ────────────────────────────
6
5
  export type NavigationBarBehavior =
7
6
  | "overlay-swipe"
8
7
  | "inset-swipe"
@@ -11,11 +10,8 @@ export type NavigationBarBehavior =
11
10
  export type NavigationBarButtonStyle = "light" | "dark";
12
11
  export type NavigationBarStyle = "auto" | "inverted" | "light" | "dark";
13
12
  export type NavigationBarVisibility = "visible" | "hidden";
14
-
15
- // ── Status Bar ────────────────────────────────
16
13
  export type StatusBarStyle = "light" | "dark";
17
14
 
18
- // ── Orientation ───────────────────────────────
19
15
  export type Orientation =
20
16
  | "portrait"
21
17
  | "landscape"
@@ -23,7 +19,6 @@ export type Orientation =
23
19
  | "landscape-right"
24
20
  | "auto";
25
21
 
26
- // ── Volume ────────────────────────────────────
27
22
  export type VolumeStream =
28
23
  | "music"
29
24
  | "ring"
@@ -31,67 +26,14 @@ export type VolumeStream =
31
26
  | "alarm"
32
27
  | "system";
33
28
 
34
- // ── Network ───────────────────────────────────
35
- export type NetworkType =
36
- | "wifi"
37
- | "cellular"
38
- | "ethernet"
39
- | "none"
40
- | "unknown";
41
-
42
- export interface NetworkInfo {
43
- /** Current connection type */
44
- type: NetworkType;
45
- /** true if any network is reachable */
46
- isConnected: boolean;
47
- /** true if in airplane mode */
48
- isAirplaneMode: boolean;
49
- /** WiFi SSID — Android only; null on iOS / when not connected */
50
- ssid: string | null;
51
- /** Cellular network generation: "2G" | "3G" | "4G" | "5G" | null */
52
- cellularGeneration: "2G" | "3G" | "4G" | "5G" | null;
53
- }
54
-
55
- // ── Battery ───────────────────────────────────
56
- export type BatteryState =
57
- | "charging"
58
- | "discharging"
59
- | "full"
60
- | "unknown";
61
-
62
- export interface BatteryInfo {
63
- /** 0–100 */
64
- level: number;
65
- /** Current charging state */
66
- state: BatteryState;
67
- /** Shorthand: is it plugged in? */
68
- isCharging: boolean;
69
- /** Low battery threshold (<= 20%) */
70
- isLow: boolean;
29
+ export interface ScreencastDisplay {
30
+ id: number;
31
+ name: string;
32
+ isValid: boolean;
71
33
  }
72
34
 
73
- // ── Haptics ───────────────────────────────────
74
- export type HapticPattern =
75
- | "light" // subtle tap
76
- | "medium" // standard tap
77
- | "heavy" // strong tap
78
- | "success" // double bump — success feedback
79
- | "warning" // single bump — warning feedback
80
- | "error" // triple bump — error feedback
81
- | "selection"; // light tick — selection change
82
-
83
- // ── Screencast ────────────────────────────────
84
35
  export interface ScreencastInfo {
85
- /** true if screen is currently being cast / mirrored */
86
36
  isCasting: boolean;
87
- /** Name of the display being cast to, or null */
88
37
  displayName: string | null;
89
- }
90
-
91
- // ── Font Scale ────────────────────────────────
92
- export interface FontScaleInfo {
93
- /** System font scale multiplier, e.g. 1.0 = default, 1.3 = large */
94
- fontScale: number;
95
- /** Display density (Android: dp ratio, iOS: scale) */
96
- density: number;
38
+ displays: ScreencastDisplay[];
97
39
  }
@@ -1,19 +1,13 @@
1
1
  // ─────────────────────────────────────────────
2
- // rn-system-bar · useSystemBar.ts v5
3
- // Hooks: useSystemBar, useBattery, useNetwork,
4
- // useScreencast, useFontScale
2
+ // rn-system-bar · useSystemBar.ts
5
3
  // ─────────────────────────────────────────────
6
4
 
7
5
  import { useEffect, useRef, useState } from "react";
8
6
  import type {
9
- BatteryInfo,
10
- FontScaleInfo,
11
- HapticPattern,
12
7
  NavigationBarBehavior,
13
8
  NavigationBarButtonStyle,
14
9
  NavigationBarStyle,
15
10
  NavigationBarVisibility,
16
- NetworkInfo,
17
11
  Orientation,
18
12
  ScreencastInfo,
19
13
  StatusBarStyle,
@@ -21,7 +15,8 @@ import type {
21
15
  import * as SystemBar from "./SystemBar";
22
16
 
23
17
  // ─────────────────────────────────────────────
24
- // useSystemBar — apply config safely
18
+ // useSystemBar — apply system bar config safely
19
+ // All async calls run inside useEffect — no Suspense crash
25
20
  // ─────────────────────────────────────────────
26
21
  export interface SystemBarConfig {
27
22
  navigationBarColor?: string;
@@ -51,11 +46,11 @@ export const useSystemBar = (config: SystemBarConfig) => {
51
46
  const apply = async () => {
52
47
  const p: Promise<void>[] = [];
53
48
 
54
- if (config.navigationBarColor !== undefined) p.push(SystemBar.setNavigationBarColor(config.navigationBarColor));
55
- if (config.navigationBarVisibility !== undefined) p.push(SystemBar.setNavigationBarVisibility(config.navigationBarVisibility));
49
+ if (config.navigationBarColor !== undefined) SystemBar.setNavigationBarColor(config.navigationBarColor);
50
+ if (config.navigationBarVisibility !== undefined) p.push(SystemBar.setNavigationBarVisibility(config.navigationBarVisibility));
56
51
  if (config.navigationBarButtonStyle !== undefined) p.push(SystemBar.setNavigationBarButtonStyle(config.navigationBarButtonStyle));
57
- if (config.navigationBarStyle !== undefined) p.push(SystemBar.setNavigationBarStyle(config.navigationBarStyle));
58
- if (config.navigationBarBehavior !== undefined) p.push(SystemBar.setNavigationBarBehavior(config.navigationBarBehavior));
52
+ if (config.navigationBarStyle !== undefined) p.push(SystemBar.setNavigationBarStyle(config.navigationBarStyle));
53
+ if (config.navigationBarBehavior !== undefined) p.push(SystemBar.setNavigationBarBehavior(config.navigationBarBehavior));
59
54
 
60
55
  await Promise.allSettled(p);
61
56
 
@@ -77,115 +72,20 @@ export const useSystemBar = (config: SystemBarConfig) => {
77
72
  };
78
73
 
79
74
  // ─────────────────────────────────────────────
80
- // 🆕 useBattery
75
+ // useScreencast
81
76
  // ─────────────────────────────────────────────
82
- /**
83
- * Reactive battery info. Auto-updates on level/state changes.
84
- * @example
85
- * const { level, isCharging, isLow, state } = useBattery();
86
- */
87
- export const useBattery = () => {
88
- const [info, setInfo] = useState<BatteryInfo>({
89
- level: -1,
90
- state: "unknown",
91
- isCharging: false,
92
- isLow: false,
93
- });
94
-
95
- useEffect(() => {
96
- let unsub: (() => void) | undefined;
97
- SystemBar.getBatteryInfo().then(setInfo).catch(() => {});
98
- unsub = SystemBar.onBatteryChange(setInfo);
99
- return () => unsub?.();
100
- }, []);
101
-
102
- return info;
103
- };
104
-
105
- // ─────────────────────────────────────────────
106
- // 🆕 useNetwork
107
- // ─────────────────────────────────────────────
108
- /**
109
- * Reactive network info. Auto-updates on connection changes.
110
- * @example
111
- * const { isConnected, type, isAirplaneMode } = useNetwork();
112
- */
113
- export const useNetwork = () => {
114
- const [info, setInfo] = useState<NetworkInfo>({
115
- type: "unknown",
116
- isConnected: false,
117
- isAirplaneMode: false,
118
- ssid: null,
119
- cellularGeneration: null,
120
- });
121
-
122
- useEffect(() => {
123
- let unsub: (() => void) | undefined;
124
- SystemBar.getNetworkInfo().then(setInfo).catch(() => {});
125
- unsub = SystemBar.onNetworkChange(setInfo);
126
- return () => unsub?.();
127
- }, []);
128
-
129
- return info;
130
- };
131
-
132
- // ─────────────────────────────────────────────
133
- // 🆕 useScreencast
134
- // ─────────────────────────────────────────────
135
- /**
136
- * Reactive screencast detection.
137
- * @example
138
- * const { isCasting, displayName } = useScreencast();
139
- */
140
77
  export const useScreencast = () => {
141
78
  const [info, setInfo] = useState<ScreencastInfo>({
142
79
  isCasting: false,
143
80
  displayName: null,
81
+ displays: [],
144
82
  });
145
83
 
146
84
  useEffect(() => {
147
- let unsub: (() => void) | undefined;
148
85
  SystemBar.getScreencastInfo().then(setInfo).catch(() => {});
149
- unsub = SystemBar.onScreencastChange(setInfo);
150
- return () => unsub?.();
86
+ const unsub = SystemBar.onScreencastChange(setInfo);
87
+ return () => unsub();
151
88
  }, []);
152
89
 
153
90
  return info;
154
91
  };
155
-
156
- // ─────────────────────────────────────────────
157
- // 🆕 useFontScale
158
- // ─────────────────────────────────────────────
159
- /**
160
- * Reactive font scale info. Updates when user changes system text size.
161
- * @example
162
- * const { fontScale, density } = useFontScale();
163
- */
164
- export const useFontScale = () => {
165
- const [info, setInfo] = useState<FontScaleInfo>({
166
- fontScale: 1.0,
167
- density: 1.0,
168
- });
169
-
170
- useEffect(() => {
171
- let unsub: (() => void) | undefined;
172
- SystemBar.getFontScaleInfo().then(setInfo).catch(() => {});
173
- unsub = SystemBar.onFontScaleChange(setInfo);
174
- return () => unsub?.();
175
- }, []);
176
-
177
- return info;
178
- };
179
-
180
- // ─────────────────────────────────────────────
181
- // 🆕 useHaptic
182
- // ─────────────────────────────────────────────
183
- /**
184
- * Returns a stable haptic trigger function.
185
- * @example
186
- * const triggerHaptic = useHaptic();
187
- * <Button onPress={() => triggerHaptic("success")} />
188
- */
189
- export const useHaptic = () => {
190
- return (pattern: HapticPattern) => SystemBar.haptic(pattern);
191
- };