rn-system-bar 3.0.2 → 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/src/SystemBar.ts CHANGED
@@ -1,236 +1,310 @@
1
1
  // ─────────────────────────────────────────────
2
- // rn-system-bar · SystemBar.ts
2
+ // rn-system-bar · SystemBar.ts v5
3
+ // All features — Android + iOS
3
4
  // ─────────────────────────────────────────────
4
5
 
5
- import { NativeModules, Platform } from "react-native";
6
+ import { NativeModules, Platform, StatusBar, Vibration } from "react-native";
6
7
 
7
8
  import type {
9
+ BatteryInfo,
10
+ FontScaleInfo,
11
+ HapticPattern,
8
12
  NavigationBarBehavior,
9
13
  NavigationBarButtonStyle,
10
14
  NavigationBarStyle,
11
15
  NavigationBarVisibility,
16
+ NetworkInfo,
12
17
  Orientation,
18
+ ScreencastInfo,
13
19
  StatusBarStyle,
14
20
  VolumeStream,
15
21
  } from "./types";
16
22
 
17
- const { SystemBar } = NativeModules;
23
+ // expo-navigation-bar peer dep, loaded at runtime
24
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
25
+ const NavBar = (() => {
26
+ try { return require("expo-navigation-bar"); }
27
+ catch { return null; }
28
+ })();
18
29
 
19
- // ─── Guard helper ──────────────────────────────
20
- // Navigation-bar APIs are Android-only.
21
- // iOS has no navigation bar concept.
22
- // All other APIs work on both platforms.
30
+ const { SystemBar: Native } = NativeModules;
23
31
 
24
32
  const isAndroid = Platform.OS === "android";
33
+ const isIOS = Platform.OS === "ios";
25
34
 
26
- const androidOnly = (name: string) => {
35
+ const androidOnly = (name: string): boolean => {
27
36
  if (!isAndroid) {
28
- if (__DEV__) {
29
- console.warn(
30
- `[rn-system-bar] ${name}() is Android-only and has no effect on iOS.`
31
- );
32
- }
37
+ if (__DEV__) console.warn(`[rn-system-bar] ${name}() is Android-only, no-op on iOS.`);
33
38
  return false;
34
39
  }
35
40
  return true;
36
41
  };
37
42
 
38
- const checkModule = () => {
39
- if (!SystemBar) {
40
- throw new Error(
41
- "[rn-system-bar] Native module not found. " +
42
- "Did you forget to run `pod install` (iOS) or rebuild the Android project?"
43
- );
44
- }
43
+ const checkNative = () => {
44
+ if (!Native) throw new Error("[rn-system-bar] Native module not found. Rebuild your project.");
45
45
  };
46
46
 
47
47
  // ═══════════════════════════════════════════════
48
- // NAVIGATION BAR (Android-only)
48
+ // NAVIGATION BAR (Android — expo-navigation-bar)
49
49
  // ═══════════════════════════════════════════════
50
50
 
51
- /**
52
- * Set navigation bar background color.
53
- * @param color Hex color string e.g. "#000000"
54
- * @platform android
55
- */
56
- export const setNavigationBarColor = (color: string): void => {
57
- if (!androidOnly("setNavigationBarColor")) return;
58
- checkModule();
59
- SystemBar.setNavigationBarColor(color);
51
+ export const setNavigationBarColor = (color: string): Promise<void> => {
52
+ if (!androidOnly("setNavigationBarColor")) return Promise.resolve();
53
+ return NavBar?.setBackgroundColorAsync(color) ?? Promise.resolve();
60
54
  };
61
55
 
62
- /**
63
- * Show or hide the navigation bar.
64
- * @platform android
65
- */
66
- export const setNavigationBarVisibility = (
67
- mode: NavigationBarVisibility
68
- ): void => {
69
- if (!androidOnly("setNavigationBarVisibility")) return;
70
- checkModule();
71
- SystemBar.setNavigationBarVisibility(mode);
56
+ export const setNavigationBarVisibility = (mode: NavigationBarVisibility): Promise<void> => {
57
+ if (!androidOnly("setNavigationBarVisibility")) return Promise.resolve();
58
+ return NavBar?.setVisibilityAsync(mode) ?? Promise.resolve();
72
59
  };
73
60
 
74
- /**
75
- * Set icon/button style of the navigation bar.
76
- * @param style "light" = white icons | "dark" = black icons
77
- * @platform android
78
- */
79
- export const setNavigationBarButtonStyle = (
80
- style: NavigationBarButtonStyle
81
- ): void => {
82
- if (!androidOnly("setNavigationBarButtonStyle")) return;
83
- checkModule();
84
- SystemBar.setNavigationBarButtonStyle(style);
61
+ export const setNavigationBarButtonStyle = (style: NavigationBarButtonStyle): Promise<void> => {
62
+ if (!androidOnly("setNavigationBarButtonStyle")) return Promise.resolve();
63
+ return NavBar?.setButtonStyleAsync(style) ?? Promise.resolve();
85
64
  };
86
65
 
87
- /**
88
- * Set the overall visual style of the navigation bar.
89
- * @platform android
90
- */
91
- export const setNavigationBarStyle = (style: NavigationBarStyle): void => {
92
- if (!androidOnly("setNavigationBarStyle")) return;
93
- checkModule();
94
- SystemBar.setNavigationBarStyle(style);
66
+ export const setNavigationBarStyle = (style: NavigationBarStyle): Promise<void> => {
67
+ if (!androidOnly("setNavigationBarStyle")) return Promise.resolve();
68
+ const btn: NavigationBarButtonStyle = style === "dark" || style === "auto" ? "dark" : "light";
69
+ return NavBar?.setButtonStyleAsync(btn) ?? Promise.resolve();
95
70
  };
96
71
 
97
- /**
98
- * Set the gesture behavior of the navigation bar.
99
- * @platform android
100
- */
101
- export const setNavigationBarBehavior = (
102
- behavior: NavigationBarBehavior
103
- ): void => {
104
- if (!androidOnly("setNavigationBarBehavior")) return;
105
- checkModule();
106
- SystemBar.setNavigationBarBehavior(behavior);
72
+ export const setNavigationBarBehavior = (behavior: NavigationBarBehavior): Promise<void> => {
73
+ if (!androidOnly("setNavigationBarBehavior")) return Promise.resolve();
74
+ return NavBar?.setBehaviorAsync(behavior) ?? Promise.resolve();
107
75
  };
108
76
 
109
77
  // ═══════════════════════════════════════════════
110
78
  // STATUS BAR
111
79
  // ═══════════════════════════════════════════════
112
80
 
113
- /**
114
- * Set status bar background color.
115
- * @param color Hex color string e.g. "#FF0000"
116
- * @platform android (iOS ignores background color by OS design)
117
- */
118
- export const setStatusBarColor = (color: string): void => {
81
+ export const setStatusBarColor = (color: string, animated = false): void => {
119
82
  if (!androidOnly("setStatusBarColor")) return;
120
- checkModule();
121
- SystemBar.setStatusBarColor(color);
83
+ StatusBar.setBackgroundColor(color, animated);
122
84
  };
123
85
 
124
- /**
125
- * Set status bar icon style (light/dark icons).
126
- * Works on both Android and iOS.
127
- */
128
- export const setStatusBarStyle = (style: StatusBarStyle): void => {
129
- checkModule();
130
- SystemBar.setStatusBarStyle(style);
86
+ export const setStatusBarStyle = (style: StatusBarStyle, animated = false): void => {
87
+ StatusBar.setBarStyle(style === "light" ? "light-content" : "dark-content", animated);
131
88
  };
132
89
 
133
- /**
134
- * Show or hide the status bar.
135
- */
136
- export const setStatusBarVisibility = (visible: boolean): void => {
137
- checkModule();
138
- SystemBar.setStatusBarVisibility(visible);
90
+ export const setStatusBarVisibility = (visible: boolean, animated = false): void => {
91
+ StatusBar.setHidden(!visible, animated ? "slide" : "none");
139
92
  };
140
93
 
141
94
  // ═══════════════════════════════════════════════
142
95
  // BRIGHTNESS
143
96
  // ═══════════════════════════════════════════════
144
97
 
98
+ export const setBrightness = (level: number): void => {
99
+ checkNative();
100
+ Native.setBrightness(Math.max(0, Math.min(1, level)));
101
+ };
102
+
103
+ export const getBrightness = (): Promise<number> => {
104
+ checkNative();
105
+ return Native.getBrightness();
106
+ };
107
+
108
+ // ═══════════════════════════════════════════════
109
+ // VOLUME
110
+ // ═══════════════════════════════════════════════
111
+
112
+ export const setVolume = (level: number, stream: VolumeStream = "music"): void => {
113
+ checkNative();
114
+ Native.setVolume(Math.max(0, Math.min(1, level)), stream);
115
+ };
116
+
117
+ export const getVolume = (stream: VolumeStream = "music"): Promise<number> => {
118
+ checkNative();
119
+ return Native.getVolume(stream);
120
+ };
121
+
122
+ export const setVolumeHUDVisible = (visible: boolean): void => {
123
+ if (!androidOnly("setVolumeHUDVisible")) return;
124
+ checkNative();
125
+ Native.setVolumeHUDVisible(visible);
126
+ };
127
+
128
+ // ═══════════════════════════════════════════════
129
+ // SCREEN
130
+ // ═══════════════════════════════════════════════
131
+
132
+ export const keepScreenOn = (enable: boolean): void => {
133
+ checkNative();
134
+ Native.keepScreenOn(enable);
135
+ };
136
+
137
+ export const immersiveMode = (enable: boolean): void => {
138
+ if (!androidOnly("immersiveMode")) return;
139
+ checkNative();
140
+ Native.immersiveMode(enable);
141
+ };
142
+
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
+
145
156
  /**
146
- * Set screen brightness.
147
- * @param level 0.0 (min) 1.0 (max)
157
+ * Get current network connection info.
158
+ * Includes: type, isConnected, isAirplaneMode, ssid, cellularGeneration
148
159
  */
149
- export const setBrightness = (level: number): void => {
150
- checkModule();
151
- SystemBar.setBrightness(Math.max(0, Math.min(1, level)));
160
+ export const getNetworkInfo = (): Promise<NetworkInfo> => {
161
+ checkNative();
162
+ return Native.getNetworkInfo();
152
163
  };
153
164
 
154
165
  /**
155
- * Get current screen brightness.
156
- * @returns Promise<number> 0.0 1.0
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();
157
171
  */
158
- export const getBrightness = (): Promise<number> => {
159
- checkModule();
160
- return SystemBar.getBrightness();
172
+ export const onNetworkChange = (
173
+ callback: (info: NetworkInfo) => void
174
+ ): (() => void) => {
175
+ 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
+ };
161
184
  };
162
185
 
163
186
  // ═══════════════════════════════════════════════
164
- // VOLUME
187
+ // 🆕 BATTERY
165
188
  // ═══════════════════════════════════════════════
166
189
 
167
190
  /**
168
- * Set volume level.
169
- * @param level 0.0 (mute) – 1.0 (max)
170
- * @param stream Audio stream to target (default: "music")
191
+ * Get current battery info: level (0–100), state, isCharging, isLow.
171
192
  */
172
- export const setVolume = (
173
- level: number,
174
- stream: VolumeStream = "music"
175
- ): void => {
176
- checkModule();
177
- SystemBar.setVolume(Math.max(0, Math.min(1, level)), stream);
193
+ export const getBatteryInfo = (): Promise<BatteryInfo> => {
194
+ checkNative();
195
+ return Native.getBatteryInfo();
178
196
  };
179
197
 
180
198
  /**
181
- * Get current volume level.
182
- * @param stream Audio stream to query (default: "music")
183
- * @returns Promise<number> 0.0 – 1.0
199
+ * Subscribe to battery level / state changes.
200
+ * @returns unsubscribe function
184
201
  */
185
- export const getVolume = (stream: VolumeStream = "music"): Promise<number> => {
186
- checkModule();
187
- return SystemBar.getVolume(stream);
202
+ export const onBatteryChange = (
203
+ callback: (info: BatteryInfo) => void
204
+ ): (() => void) => {
205
+ 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
+ };
188
213
  };
189
214
 
215
+ // ═══════════════════════════════════════════════
216
+ // 🆕 HAPTIC FEEDBACK
217
+ // ═══════════════════════════════════════════════
218
+
190
219
  /**
191
- * Show or hide the system Volume HUD when changing volume.
192
- * @param visible false = suppress the volume popup
193
- * @platform android
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"
194
226
  */
195
- export const setVolumeHUDVisible = (visible: boolean): void => {
196
- if (!androidOnly("setVolumeHUDVisible")) return;
197
- checkModule();
198
- SystemBar.setVolumeHUDVisible(visible);
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
+ }
199
240
  };
200
241
 
201
242
  // ═══════════════════════════════════════════════
202
- // SCREEN
243
+ // 🆕 SCREENCAST DETECTION
203
244
  // ═══════════════════════════════════════════════
204
245
 
205
246
  /**
206
- * Prevent the screen from sleeping.
207
- * @param enable true = keep screen on | false = allow sleep
247
+ * Check if screen is currently being cast or mirrored.
208
248
  */
209
- export const keepScreenOn = (enable: boolean): void => {
210
- checkModule();
211
- SystemBar.keepScreenOn(enable);
249
+ export const getScreencastInfo = (): Promise<ScreencastInfo> => {
250
+ checkNative();
251
+ return Native.getScreencastInfo();
252
+ };
253
+
254
+ /**
255
+ * Subscribe to screencast state changes (started / stopped).
256
+ * @returns unsubscribe function
257
+ */
258
+ export const onScreencastChange = (
259
+ callback: (info: ScreencastInfo) => void
260
+ ): (() => void) => {
261
+ checkNative();
262
+ const { DeviceEventEmitter } = require("react-native");
263
+ Native.startScreencastListener();
264
+ const sub = DeviceEventEmitter.addListener("SystemBar_ScreencastChange", callback);
265
+ return () => {
266
+ sub.remove();
267
+ Native.stopScreencastListener();
268
+ };
212
269
  };
213
270
 
214
271
  /**
215
- * Enable or disable immersive fullscreen mode.
216
- * Hides both status bar and navigation bar.
272
+ * Prevent screen content from appearing in screenshots / recordings.
273
+ * Useful for sensitive screens (payments, passwords).
217
274
  * @platform android
218
275
  */
219
- export const immersiveMode = (enable: boolean): void => {
220
- if (!androidOnly("immersiveMode")) return;
221
- checkModule();
222
- SystemBar.immersiveMode(enable);
276
+ export const setSecureScreen = (enable: boolean): void => {
277
+ if (!androidOnly("setSecureScreen")) return;
278
+ checkNative();
279
+ Native.setSecureScreen(enable);
223
280
  };
224
281
 
225
282
  // ═══════════════════════════════════════════════
226
- // ORIENTATION
283
+ // 🆕 FONT SCALE / DISPLAY INFO
227
284
  // ═══════════════════════════════════════════════
228
285
 
229
286
  /**
230
- * Lock or unlock screen orientation.
231
- * @param mode "portrait" | "landscape" | "landscape-left" | "landscape-right" | "auto"
287
+ * Get current system font scale and display density.
288
+ * Useful for adapting layout to accessibility settings.
232
289
  */
233
- export const setOrientation = (mode: Orientation): void => {
234
- checkModule();
235
- SystemBar.setOrientation(mode);
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
+ };
236
310
  };
package/src/types.ts CHANGED
@@ -1,30 +1,21 @@
1
1
  // ─────────────────────────────────────────────
2
- // rn-system-bar · types.ts
2
+ // rn-system-bar · types.ts v5
3
3
  // ─────────────────────────────────────────────
4
4
 
5
+ // ── Navigation Bar ────────────────────────────
5
6
  export type NavigationBarBehavior =
6
- | "overlay-swipe" // Swipe from edge reveals bar briefly (overlay)
7
- | "inset-swipe" // Swipe insets content when bar appears
8
- | "inset-touch"; // Touch insets content when bar appears
7
+ | "overlay-swipe"
8
+ | "inset-swipe"
9
+ | "inset-touch";
9
10
 
10
- export type NavigationBarButtonStyle =
11
- | "light" // White / light-coloured icons
12
- | "dark"; // Black / dark-coloured icons
11
+ export type NavigationBarButtonStyle = "light" | "dark";
12
+ export type NavigationBarStyle = "auto" | "inverted" | "light" | "dark";
13
+ export type NavigationBarVisibility = "visible" | "hidden";
13
14
 
14
- export type NavigationBarStyle =
15
- | "auto" // System decides
16
- | "inverted" // Inverted from system
17
- | "light" // Light background nav bar
18
- | "dark"; // Dark background nav bar
19
-
20
- export type NavigationBarVisibility =
21
- | "visible"
22
- | "hidden";
23
-
24
- export type StatusBarStyle =
25
- | "light" // Light icons (for dark backgrounds)
26
- | "dark"; // Dark icons (for light backgrounds)
15
+ // ── Status Bar ────────────────────────────────
16
+ export type StatusBarStyle = "light" | "dark";
27
17
 
18
+ // ── Orientation ───────────────────────────────
28
19
  export type Orientation =
29
20
  | "portrait"
30
21
  | "landscape"
@@ -32,6 +23,7 @@ export type Orientation =
32
23
  | "landscape-right"
33
24
  | "auto";
34
25
 
26
+ // ── Volume ────────────────────────────────────
35
27
  export type VolumeStream =
36
28
  | "music"
37
29
  | "ring"
@@ -39,32 +31,67 @@ export type VolumeStream =
39
31
  | "alarm"
40
32
  | "system";
41
33
 
42
- export interface SystemBarAPI {
43
- // Navigation Bar
44
- setNavigationBarColor(color: string): void;
45
- setNavigationBarVisibility(mode: NavigationBarVisibility): void;
46
- setNavigationBarButtonStyle(style: NavigationBarButtonStyle): void;
47
- setNavigationBarStyle(style: NavigationBarStyle): void;
48
- setNavigationBarBehavior(behavior: NavigationBarBehavior): void;
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
+ }
49
54
 
50
- // Status Bar
51
- setStatusBarColor(color: string): void;
52
- setStatusBarStyle(style: StatusBarStyle): void;
53
- setStatusBarVisibility(visible: boolean): void;
55
+ // ── Battery ───────────────────────────────────
56
+ export type BatteryState =
57
+ | "charging"
58
+ | "discharging"
59
+ | "full"
60
+ | "unknown";
54
61
 
55
- // Brightness
56
- setBrightness(level: number): void;
57
- getBrightness(): Promise<number>;
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;
71
+ }
58
72
 
59
- // Volume
60
- setVolume(level: number, stream?: VolumeStream): void;
61
- getVolume(stream?: VolumeStream): Promise<number>;
62
- setVolumeHUDVisible(visible: boolean): void;
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
63
82
 
64
- // Screen
65
- keepScreenOn(enable: boolean): void;
66
- immersiveMode(enable: boolean): void;
83
+ // ── Screencast ────────────────────────────────
84
+ export interface ScreencastInfo {
85
+ /** true if screen is currently being cast / mirrored */
86
+ isCasting: boolean;
87
+ /** Name of the display being cast to, or null */
88
+ displayName: string | null;
89
+ }
67
90
 
68
- // Orientation
69
- setOrientation(mode: Orientation): void;
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;
70
97
  }