rn-system-bar 3.1.6 → 3.1.8
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/README.md +81 -40
- package/android/src/main/java/com/systembar/SystemBarModule.kt +288 -13
- package/index.ts +23 -2
- package/ios/SystemBarModule.m +13 -6
- package/ios/SystemBarModule.swift +44 -9
- package/lib/index.d.ts +3 -2
- package/lib/index.js +13 -2
- package/lib/specs/NativeSystemBar.d.ts +9 -0
- package/lib/src/SystemBar.d.ts +83 -5
- package/lib/src/SystemBar.js +161 -12
- package/lib/src/types.d.ts +61 -1
- package/lib/src/useSystemBar.d.ts +35 -4
- package/lib/src/useSystemBar.js +71 -7
- package/lib/src/useTheme.d.ts +24 -0
- package/lib/src/useTheme.js +81 -0
- package/package.json +3 -2
- package/specs/NativeSystemBar.ts +21 -6
- package/src/SystemBar.ts +194 -21
- package/src/types.ts +85 -1
- package/src/useSystemBar.ts +206 -28
- package/src/useTheme.ts +95 -0
- package/plugin/withRnSystemBar.js +0 -65
- package/plugin/withRnSystemBar.ts +0 -68
package/lib/src/useSystemBar.js
CHANGED
|
@@ -36,9 +36,22 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
36
36
|
};
|
|
37
37
|
})();
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.useScreencast = exports.useSystemBar = void 0;
|
|
39
|
+
exports.useAppCast = exports.useScreencast = exports.useSystemScreencast = exports.useTheme = exports.useThemeSystemBar = exports.useSystemBar = void 0;
|
|
40
40
|
const react_1 = require("react");
|
|
41
41
|
const SystemBar = __importStar(require("./SystemBar"));
|
|
42
|
+
const useTheme_1 = require("./useTheme");
|
|
43
|
+
// ─────────────────────────────────────────────
|
|
44
|
+
// useSystemBar
|
|
45
|
+
// Apply system bar settings declaratively.
|
|
46
|
+
//
|
|
47
|
+
// @example
|
|
48
|
+
// useSystemBar({
|
|
49
|
+
// navigationBarColor: "transparent",
|
|
50
|
+
// navigationBarButtonStyle: "light",
|
|
51
|
+
// statusBarStyle: "light",
|
|
52
|
+
// keepScreenOn: true,
|
|
53
|
+
// });
|
|
54
|
+
// ─────────────────────────────────────────────
|
|
42
55
|
const useSystemBar = (config) => {
|
|
43
56
|
const configRef = (0, react_1.useRef)("");
|
|
44
57
|
const configStr = JSON.stringify(config);
|
|
@@ -47,7 +60,6 @@ const useSystemBar = (config) => {
|
|
|
47
60
|
return;
|
|
48
61
|
configRef.current = configStr;
|
|
49
62
|
const apply = async () => {
|
|
50
|
-
// All nav bar calls are now sync (no expo-navigation-bar)
|
|
51
63
|
if (config.navigationBarColor !== undefined)
|
|
52
64
|
SystemBar.setNavigationBarColor(config.navigationBarColor);
|
|
53
65
|
if (config.navigationBarVisibility !== undefined)
|
|
@@ -58,6 +70,8 @@ const useSystemBar = (config) => {
|
|
|
58
70
|
SystemBar.setNavigationBarStyle(config.navigationBarStyle);
|
|
59
71
|
if (config.navigationBarBehavior !== undefined)
|
|
60
72
|
SystemBar.setNavigationBarBehavior(config.navigationBarBehavior);
|
|
73
|
+
if (config.statusBarColor !== undefined)
|
|
74
|
+
SystemBar.setStatusBarColor(config.statusBarColor);
|
|
61
75
|
if (config.statusBarStyle !== undefined)
|
|
62
76
|
SystemBar.setStatusBarStyle(config.statusBarStyle);
|
|
63
77
|
if (config.statusBarVisible !== undefined)
|
|
@@ -81,20 +95,70 @@ const useSystemBar = (config) => {
|
|
|
81
95
|
}, [configStr]);
|
|
82
96
|
};
|
|
83
97
|
exports.useSystemBar = useSystemBar;
|
|
98
|
+
const useThemeSystemBar = (config) => {
|
|
99
|
+
var _a, _b, _c;
|
|
100
|
+
const theme = (0, useTheme_1.useTheme)();
|
|
101
|
+
const resolved = Object.assign(Object.assign({}, ((_a = config.base) !== null && _a !== void 0 ? _a : {})), (theme.isDark ? ((_b = config.dark) !== null && _b !== void 0 ? _b : {}) : ((_c = config.light) !== null && _c !== void 0 ? _c : {})));
|
|
102
|
+
(0, exports.useSystemBar)(resolved);
|
|
103
|
+
return theme;
|
|
104
|
+
};
|
|
105
|
+
exports.useThemeSystemBar = useThemeSystemBar;
|
|
84
106
|
// ─────────────────────────────────────────────
|
|
85
|
-
//
|
|
107
|
+
// Re-export useTheme + theme types
|
|
86
108
|
// ─────────────────────────────────────────────
|
|
87
|
-
|
|
109
|
+
var useTheme_2 = require("./useTheme");
|
|
110
|
+
Object.defineProperty(exports, "useTheme", { enumerable: true, get: function () { return useTheme_2.useTheme; } });
|
|
111
|
+
// ─────────────────────────────────────────────
|
|
112
|
+
// useSystemScreencast
|
|
113
|
+
// Tracks OS-level external display state.
|
|
114
|
+
// Android: DisplayManager (HDMI / Miracast / Presentation display).
|
|
115
|
+
// iOS: UIScreen.screens (AirPlay mirror).
|
|
116
|
+
//
|
|
117
|
+
// @example
|
|
118
|
+
// const { isCasting, displayName, displays } = useSystemScreencast();
|
|
119
|
+
// ─────────────────────────────────────────────
|
|
120
|
+
const useSystemScreencast = () => {
|
|
88
121
|
const [info, setInfo] = (0, react_1.useState)({
|
|
89
122
|
isCasting: false,
|
|
90
123
|
displayName: null,
|
|
91
124
|
displays: [],
|
|
92
125
|
});
|
|
93
126
|
(0, react_1.useEffect)(() => {
|
|
94
|
-
SystemBar.
|
|
95
|
-
|
|
127
|
+
SystemBar.getSystemScreencastInfo()
|
|
128
|
+
.then(setInfo)
|
|
129
|
+
.catch(() => { });
|
|
130
|
+
const unsub = SystemBar.onSystemScreencastChange(setInfo);
|
|
96
131
|
return () => unsub();
|
|
97
132
|
}, []);
|
|
98
133
|
return info;
|
|
99
134
|
};
|
|
100
|
-
exports.
|
|
135
|
+
exports.useSystemScreencast = useSystemScreencast;
|
|
136
|
+
/** @deprecated Renamed to useSystemScreencast() */
|
|
137
|
+
exports.useScreencast = exports.useSystemScreencast;
|
|
138
|
+
const useAppCast = () => {
|
|
139
|
+
const [info, setInfo] = (0, react_1.useState)({
|
|
140
|
+
state: "idle",
|
|
141
|
+
devices: [],
|
|
142
|
+
connectedDevice: null,
|
|
143
|
+
error: null,
|
|
144
|
+
});
|
|
145
|
+
(0, react_1.useEffect)(() => {
|
|
146
|
+
// Sync initial state
|
|
147
|
+
SystemBar.getAppCastInfo()
|
|
148
|
+
.then(setInfo)
|
|
149
|
+
.catch(() => { });
|
|
150
|
+
// Subscribe to all cast events (device found/lost, state changes, errors)
|
|
151
|
+
const unsub = SystemBar.onAppCastChange(setInfo);
|
|
152
|
+
return () => {
|
|
153
|
+
unsub();
|
|
154
|
+
// Auto-stop scan on unmount — avoids background battery drain
|
|
155
|
+
SystemBar.stopAppCastScan();
|
|
156
|
+
};
|
|
157
|
+
}, []);
|
|
158
|
+
const scan = (0, react_1.useCallback)(() => SystemBar.startAppCastScan(), []);
|
|
159
|
+
const stopScan = (0, react_1.useCallback)(() => SystemBar.stopAppCastScan(), []);
|
|
160
|
+
const connect = (0, react_1.useCallback)((id, pin) => SystemBar.connectAppCast(id, pin), []);
|
|
161
|
+
const disconnect = (0, react_1.useCallback)(() => SystemBar.disconnectAppCast(), []);
|
|
162
|
+
return Object.assign(Object.assign({}, info), { scan, stopScan, connect, disconnect });
|
|
163
|
+
};
|
|
164
|
+
exports.useAppCast = useAppCast;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ThemeMode, ThemeState } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* Set the global theme mode from anywhere (outside a component).
|
|
4
|
+
* Triggers all `useTheme()` consumers.
|
|
5
|
+
*/
|
|
6
|
+
export declare function setGlobalThemeMode(mode: ThemeMode): void;
|
|
7
|
+
/**
|
|
8
|
+
* React hook — subscribe to theme state.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* const { isDark, mode, setMode } = useTheme();
|
|
12
|
+
*
|
|
13
|
+
* // Auto nav bar colour based on theme
|
|
14
|
+
* useEffect(() => {
|
|
15
|
+
* setNavigationBarColor(isDark ? "#000000" : "#ffffff");
|
|
16
|
+
* setNavigationBarButtonStyle(isDark ? "light" : "dark");
|
|
17
|
+
* }, [isDark]);
|
|
18
|
+
*
|
|
19
|
+
* // Manual override
|
|
20
|
+
* <Button onPress={() => setMode("dark")} title="Force Dark" />
|
|
21
|
+
* <Button onPress={() => setMode("light")} title="Force Light" />
|
|
22
|
+
* <Button onPress={() => setMode("system")}title="Follow OS" />
|
|
23
|
+
*/
|
|
24
|
+
export declare function useTheme(): ThemeState;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ─────────────────────────────────────────────
|
|
3
|
+
// rn-system-bar · useTheme.ts
|
|
4
|
+
//
|
|
5
|
+
// Reads the OS colour scheme via React Native's
|
|
6
|
+
// `Appearance` API and lets the user override it
|
|
7
|
+
// with "dark" | "light" | "system".
|
|
8
|
+
//
|
|
9
|
+
// Usage:
|
|
10
|
+
// const { isDark, mode, setMode } = useTheme();
|
|
11
|
+
// const { isDark } = useTheme(); // read-only
|
|
12
|
+
// ─────────────────────────────────────────────
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.setGlobalThemeMode = setGlobalThemeMode;
|
|
15
|
+
exports.useTheme = useTheme;
|
|
16
|
+
const react_1 = require("react");
|
|
17
|
+
const react_native_1 = require("react-native");
|
|
18
|
+
// ── Module-level singleton so all consumers share state ──────────────────────
|
|
19
|
+
let _mode = "system";
|
|
20
|
+
let _systemIsDark = react_native_1.Appearance.getColorScheme() === "dark";
|
|
21
|
+
const _listeners = new Set();
|
|
22
|
+
function _notify() {
|
|
23
|
+
const isDark = resolveIsDark(_mode, _systemIsDark);
|
|
24
|
+
_listeners.forEach((fn) => fn({ isDark, mode: _mode }));
|
|
25
|
+
}
|
|
26
|
+
function resolveIsDark(mode, systemIsDark) {
|
|
27
|
+
if (mode === "dark")
|
|
28
|
+
return true;
|
|
29
|
+
if (mode === "light")
|
|
30
|
+
return false;
|
|
31
|
+
return systemIsDark; // "system"
|
|
32
|
+
}
|
|
33
|
+
// Keep in sync with OS changes
|
|
34
|
+
react_native_1.Appearance.addChangeListener(({ colorScheme }) => {
|
|
35
|
+
_systemIsDark = colorScheme === "dark";
|
|
36
|
+
_notify();
|
|
37
|
+
});
|
|
38
|
+
// ── Public API ───────────────────────────────────────────────────────────────
|
|
39
|
+
/**
|
|
40
|
+
* Set the global theme mode from anywhere (outside a component).
|
|
41
|
+
* Triggers all `useTheme()` consumers.
|
|
42
|
+
*/
|
|
43
|
+
function setGlobalThemeMode(mode) {
|
|
44
|
+
_mode = mode;
|
|
45
|
+
_notify();
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* React hook — subscribe to theme state.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* const { isDark, mode, setMode } = useTheme();
|
|
52
|
+
*
|
|
53
|
+
* // Auto nav bar colour based on theme
|
|
54
|
+
* useEffect(() => {
|
|
55
|
+
* setNavigationBarColor(isDark ? "#000000" : "#ffffff");
|
|
56
|
+
* setNavigationBarButtonStyle(isDark ? "light" : "dark");
|
|
57
|
+
* }, [isDark]);
|
|
58
|
+
*
|
|
59
|
+
* // Manual override
|
|
60
|
+
* <Button onPress={() => setMode("dark")} title="Force Dark" />
|
|
61
|
+
* <Button onPress={() => setMode("light")} title="Force Light" />
|
|
62
|
+
* <Button onPress={() => setMode("system")}title="Follow OS" />
|
|
63
|
+
*/
|
|
64
|
+
function useTheme() {
|
|
65
|
+
const [state, setState] = (0, react_1.useState)(() => ({
|
|
66
|
+
isDark: resolveIsDark(_mode, _systemIsDark),
|
|
67
|
+
mode: _mode,
|
|
68
|
+
}));
|
|
69
|
+
(0, react_1.useEffect)(() => {
|
|
70
|
+
// Sync on mount in case singleton changed between renders
|
|
71
|
+
setState({ isDark: resolveIsDark(_mode, _systemIsDark), mode: _mode });
|
|
72
|
+
_listeners.add(setState);
|
|
73
|
+
return () => {
|
|
74
|
+
_listeners.delete(setState);
|
|
75
|
+
};
|
|
76
|
+
}, []);
|
|
77
|
+
const setMode = (0, react_1.useCallback)((newMode) => {
|
|
78
|
+
setGlobalThemeMode(newMode);
|
|
79
|
+
}, []);
|
|
80
|
+
return Object.assign(Object.assign({}, state), { setMode });
|
|
81
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rn-system-bar",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.8",
|
|
4
4
|
"description": "Control Android & iOS system bars, brightness, volume, orientation and screen flags from React Native.",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"react-native": "lib/index.js",
|
|
@@ -37,7 +37,8 @@
|
|
|
37
37
|
"scripts": {
|
|
38
38
|
"build": "rimraf lib && npx tsc",
|
|
39
39
|
"npm:prepublish": "npm publish --access public",
|
|
40
|
-
"installing": "cd .. && npm install rn-system-bar@latest && cd rn-system-bar"
|
|
40
|
+
"installing": "cd .. && npm install rn-system-bar@latest && cd rn-system-bar",
|
|
41
|
+
"dev": "npm run build && npm run npm:prepublish && npm run installing"
|
|
41
42
|
},
|
|
42
43
|
"expo": {
|
|
43
44
|
"plugins": [
|
package/specs/NativeSystemBar.ts
CHANGED
|
@@ -7,33 +7,48 @@ import type { TurboModule } from "react-native";
|
|
|
7
7
|
import { TurboModuleRegistry } from "react-native";
|
|
8
8
|
|
|
9
9
|
export interface Spec extends TurboModule {
|
|
10
|
-
// Navigation Bar (Android-only)
|
|
10
|
+
// ── Navigation Bar (Android-only) ──────────
|
|
11
|
+
// color: hex string | "transparent" | "translucent"
|
|
11
12
|
setNavigationBarColor(color: string): void;
|
|
12
13
|
setNavigationBarVisibility(mode: string): void;
|
|
13
14
|
setNavigationBarButtonStyle(style: string): void;
|
|
14
15
|
setNavigationBarStyle(style: string): void;
|
|
15
16
|
setNavigationBarBehavior(behavior: string): void;
|
|
16
17
|
|
|
17
|
-
// Status Bar
|
|
18
|
+
// ── Status Bar ─────────────────────────────
|
|
19
|
+
// color: hex string | "transparent" | "translucent"
|
|
18
20
|
setStatusBarColor(color: string): void;
|
|
19
21
|
setStatusBarStyle(style: string): void;
|
|
20
22
|
setStatusBarVisibility(visible: boolean): void;
|
|
21
23
|
|
|
22
|
-
// Brightness
|
|
24
|
+
// ── Brightness ─────────────────────────────
|
|
23
25
|
setBrightness(level: number): void;
|
|
24
26
|
getBrightness(): Promise<number>;
|
|
25
27
|
|
|
26
|
-
// Volume
|
|
28
|
+
// ── Volume ─────────────────────────────────
|
|
27
29
|
setVolume(level: number, stream: string): void;
|
|
28
30
|
getVolume(stream: string): Promise<number>;
|
|
29
31
|
setVolumeHUDVisible(visible: boolean): void;
|
|
30
32
|
|
|
31
|
-
// Screen
|
|
33
|
+
// ── Screen ─────────────────────────────────
|
|
32
34
|
keepScreenOn(enable: boolean): void;
|
|
33
35
|
immersiveMode(enable: boolean): void;
|
|
36
|
+
setSecureScreen(enable: boolean): void;
|
|
34
37
|
|
|
35
|
-
// Orientation
|
|
38
|
+
// ── Orientation ────────────────────────────
|
|
36
39
|
setOrientation(mode: string): void;
|
|
40
|
+
|
|
41
|
+
// ── System Screencast ──────────────────────
|
|
42
|
+
getSystemScreencastInfo(): Promise<Object>;
|
|
43
|
+
startSystemScreencastListener(): void;
|
|
44
|
+
stopSystemScreencastListener(): void;
|
|
45
|
+
|
|
46
|
+
// ── App-only Cast (Android only) ───────────
|
|
47
|
+
startAppCastScan(): void;
|
|
48
|
+
stopAppCastScan(): void;
|
|
49
|
+
connectAppCast(deviceId: string, pairingPin: string | null): void;
|
|
50
|
+
disconnectAppCast(): void;
|
|
51
|
+
getAppCastInfo(): Promise<Object>;
|
|
37
52
|
}
|
|
38
53
|
|
|
39
54
|
export default TurboModuleRegistry.getEnforcing<Spec>("SystemBar");
|
package/src/SystemBar.ts
CHANGED
|
@@ -7,13 +7,18 @@
|
|
|
7
7
|
import { NativeModules, Platform } from "react-native";
|
|
8
8
|
|
|
9
9
|
import type {
|
|
10
|
+
AppCastInfo,
|
|
11
|
+
AppCastState,
|
|
10
12
|
NavigationBarBehavior,
|
|
11
13
|
NavigationBarButtonStyle,
|
|
14
|
+
NavigationBarColorValue,
|
|
12
15
|
NavigationBarStyle,
|
|
13
16
|
NavigationBarVisibility,
|
|
14
17
|
Orientation,
|
|
15
18
|
ScreencastInfo,
|
|
19
|
+
StatusBarColorValue,
|
|
16
20
|
StatusBarStyle,
|
|
21
|
+
SystemScreencastInfo,
|
|
17
22
|
VolumeStream,
|
|
18
23
|
} from "./types";
|
|
19
24
|
|
|
@@ -23,33 +28,59 @@ const isAndroid = Platform.OS === "android";
|
|
|
23
28
|
|
|
24
29
|
const androidOnly = (name: string): boolean => {
|
|
25
30
|
if (!isAndroid) {
|
|
26
|
-
if (__DEV__)
|
|
31
|
+
if (__DEV__)
|
|
32
|
+
console.warn(`[rn-system-bar] ${name}() is Android-only, no-op on iOS.`);
|
|
27
33
|
return false;
|
|
28
34
|
}
|
|
29
35
|
return true;
|
|
30
36
|
};
|
|
31
37
|
|
|
32
38
|
const checkNative = () => {
|
|
33
|
-
if (!Native)
|
|
39
|
+
if (!Native)
|
|
40
|
+
throw new Error(
|
|
41
|
+
"[rn-system-bar] Native module not found. Rebuild your project.",
|
|
42
|
+
);
|
|
34
43
|
};
|
|
35
44
|
|
|
36
45
|
// ═══════════════════════════════════════════════
|
|
37
46
|
// NAVIGATION BAR (Android — 100% native)
|
|
38
47
|
// ═══════════════════════════════════════════════
|
|
39
48
|
|
|
40
|
-
|
|
49
|
+
/**
|
|
50
|
+
* Set the navigation bar background colour.
|
|
51
|
+
*
|
|
52
|
+
* @param color
|
|
53
|
+
* - Any hex string → solid colour e.g. `"#1a1a2e"`
|
|
54
|
+
* - `"transparent"` → fully transparent (content draws behind bar)
|
|
55
|
+
* - `"translucent"` → semi-transparent (system scrim over content)
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* setNavigationBarColor("#000000"); // solid black
|
|
59
|
+
* setNavigationBarColor("transparent"); // glass / edge-to-edge
|
|
60
|
+
* setNavigationBarColor("translucent"); // frosted glass
|
|
61
|
+
*/
|
|
62
|
+
export const setNavigationBarColor = (color: NavigationBarColorValue): void => {
|
|
41
63
|
if (!androidOnly("setNavigationBarColor")) return;
|
|
42
64
|
checkNative();
|
|
43
|
-
Native.setNavigationBarColor(color);
|
|
65
|
+
Native.setNavigationBarColor(color); // Kotlin handles the special values
|
|
44
66
|
};
|
|
45
67
|
|
|
46
|
-
|
|
68
|
+
/**
|
|
69
|
+
* Hide or show the navigation bar.
|
|
70
|
+
*
|
|
71
|
+
* @param mode `"visible"` | `"hidden"`
|
|
72
|
+
*/
|
|
73
|
+
export const setNavigationBarVisibility = (
|
|
74
|
+
mode: NavigationBarVisibility,
|
|
75
|
+
): void => {
|
|
47
76
|
if (!androidOnly("setNavigationBarVisibility")) return;
|
|
48
77
|
checkNative();
|
|
49
78
|
Native.setNavigationBarVisibility(mode);
|
|
50
79
|
};
|
|
51
80
|
|
|
52
|
-
export const setNavigationBarButtonStyle = (
|
|
81
|
+
export const setNavigationBarButtonStyle = (
|
|
82
|
+
style: NavigationBarButtonStyle,
|
|
83
|
+
): void => {
|
|
53
84
|
if (!androidOnly("setNavigationBarButtonStyle")) return;
|
|
54
85
|
checkNative();
|
|
55
86
|
Native.setNavigationBarButtonStyle(style);
|
|
@@ -61,7 +92,9 @@ export const setNavigationBarStyle = (style: NavigationBarStyle): void => {
|
|
|
61
92
|
Native.setNavigationBarStyle(style);
|
|
62
93
|
};
|
|
63
94
|
|
|
64
|
-
export const setNavigationBarBehavior = (
|
|
95
|
+
export const setNavigationBarBehavior = (
|
|
96
|
+
behavior: NavigationBarBehavior,
|
|
97
|
+
): void => {
|
|
65
98
|
if (!androidOnly("setNavigationBarBehavior")) return;
|
|
66
99
|
checkNative();
|
|
67
100
|
Native.setNavigationBarBehavior(behavior);
|
|
@@ -71,6 +104,20 @@ export const setNavigationBarBehavior = (behavior: NavigationBarBehavior): void
|
|
|
71
104
|
// STATUS BAR (native — no RN StatusBar)
|
|
72
105
|
// ═══════════════════════════════════════════════
|
|
73
106
|
|
|
107
|
+
/**
|
|
108
|
+
* Set the status bar background colour (Android only).
|
|
109
|
+
*
|
|
110
|
+
* @param color
|
|
111
|
+
* - Any hex string → solid colour
|
|
112
|
+
* - `"transparent"` → fully transparent
|
|
113
|
+
* - `"translucent"` → semi-transparent
|
|
114
|
+
*/
|
|
115
|
+
export const setStatusBarColor = (color: StatusBarColorValue): void => {
|
|
116
|
+
if (!androidOnly("setStatusBarColor")) return;
|
|
117
|
+
checkNative();
|
|
118
|
+
Native.setStatusBarColor(color);
|
|
119
|
+
};
|
|
120
|
+
|
|
74
121
|
export const setStatusBarStyle = (style: StatusBarStyle): void => {
|
|
75
122
|
checkNative();
|
|
76
123
|
Native.setStatusBarStyle(style);
|
|
@@ -96,18 +143,18 @@ export const getBrightness = (): Promise<number> => {
|
|
|
96
143
|
};
|
|
97
144
|
|
|
98
145
|
/**
|
|
99
|
-
* Subscribe to system brightness changes (polls every
|
|
146
|
+
* Subscribe to system brightness changes (polls every 500 ms on Android).
|
|
100
147
|
* @returns unsubscribe function
|
|
101
148
|
*/
|
|
102
149
|
export const onBrightnessChange = (
|
|
103
|
-
callback: (brightness: number) => void
|
|
150
|
+
callback: (brightness: number) => void,
|
|
104
151
|
): (() => void) => {
|
|
105
152
|
checkNative();
|
|
106
153
|
const { DeviceEventEmitter } = require("react-native");
|
|
107
154
|
Native.startBrightnessListener();
|
|
108
155
|
const sub = DeviceEventEmitter.addListener(
|
|
109
156
|
"SystemBar_BrightnessChange",
|
|
110
|
-
(e: { brightness: number }) => callback(e.brightness)
|
|
157
|
+
(e: { brightness: number }) => callback(e.brightness),
|
|
111
158
|
);
|
|
112
159
|
return () => {
|
|
113
160
|
sub.remove();
|
|
@@ -119,7 +166,10 @@ export const onBrightnessChange = (
|
|
|
119
166
|
// VOLUME
|
|
120
167
|
// ═══════════════════════════════════════════════
|
|
121
168
|
|
|
122
|
-
export const setVolume = (
|
|
169
|
+
export const setVolume = (
|
|
170
|
+
level: number,
|
|
171
|
+
stream: VolumeStream = "music",
|
|
172
|
+
): void => {
|
|
123
173
|
checkNative();
|
|
124
174
|
Native.setVolume(Math.max(0, Math.min(1, level)), stream);
|
|
125
175
|
};
|
|
@@ -140,14 +190,15 @@ export const setVolumeHUDVisible = (visible: boolean): void => {
|
|
|
140
190
|
* @returns unsubscribe function
|
|
141
191
|
*/
|
|
142
192
|
export const onVolumeChange = (
|
|
143
|
-
callback: (volume: number, stream: VolumeStream) => void
|
|
193
|
+
callback: (volume: number, stream: VolumeStream) => void,
|
|
144
194
|
): (() => void) => {
|
|
145
195
|
checkNative();
|
|
146
196
|
const { DeviceEventEmitter } = require("react-native");
|
|
147
197
|
Native.startVolumeListener();
|
|
148
198
|
const sub = DeviceEventEmitter.addListener(
|
|
149
199
|
"SystemBar_VolumeChange",
|
|
150
|
-
(e: { volume: number; stream: VolumeStream }) =>
|
|
200
|
+
(e: { volume: number; stream: VolumeStream }) =>
|
|
201
|
+
callback(e.volume, e.stream),
|
|
151
202
|
);
|
|
152
203
|
return () => {
|
|
153
204
|
sub.remove();
|
|
@@ -186,23 +237,145 @@ export const setOrientation = (mode: Orientation): void => {
|
|
|
186
237
|
};
|
|
187
238
|
|
|
188
239
|
// ═══════════════════════════════════════════════
|
|
189
|
-
// SCREENCAST
|
|
240
|
+
// SYSTEM SCREENCAST (external display / HDMI / Miracast)
|
|
241
|
+
// Reads DisplayManager — detects any externally mirrored display.
|
|
242
|
+
// ═══════════════════════════════════════════════
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* One-shot snapshot of system-level external display state.
|
|
246
|
+
* Works on both Android (DisplayManager) and iOS (UIScreen.screens).
|
|
247
|
+
*/
|
|
248
|
+
export const getSystemScreencastInfo = (): Promise<SystemScreencastInfo> => {
|
|
249
|
+
checkNative();
|
|
250
|
+
return Native.getSystemScreencastInfo();
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Subscribe to system external-display changes.
|
|
255
|
+
* Fires when an HDMI / Miracast / AirPlay display connects or disconnects.
|
|
256
|
+
* @returns unsubscribe function
|
|
257
|
+
*/
|
|
258
|
+
export const onSystemScreencastChange = (
|
|
259
|
+
callback: (info: SystemScreencastInfo) => void,
|
|
260
|
+
): (() => void) => {
|
|
261
|
+
checkNative();
|
|
262
|
+
const { DeviceEventEmitter } = require("react-native");
|
|
263
|
+
Native.startSystemScreencastListener();
|
|
264
|
+
const sub = DeviceEventEmitter.addListener(
|
|
265
|
+
"SystemBar_SystemScreencastChange",
|
|
266
|
+
callback,
|
|
267
|
+
);
|
|
268
|
+
return () => {
|
|
269
|
+
sub.remove();
|
|
270
|
+
Native.stopSystemScreencastListener();
|
|
271
|
+
};
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
// ─────────────────────────────────────────────
|
|
275
|
+
// Legacy aliases (backward compat)
|
|
276
|
+
// ─────────────────────────────────────────────
|
|
277
|
+
/** @deprecated Use getSystemScreencastInfo() */
|
|
278
|
+
export const getScreencastInfo = getSystemScreencastInfo;
|
|
279
|
+
/** @deprecated Use onSystemScreencastChange() */
|
|
280
|
+
export const onScreencastChange = onSystemScreencastChange;
|
|
281
|
+
|
|
282
|
+
// ═══════════════════════════════════════════════
|
|
283
|
+
// APP-ONLY CAST (MediaRouter — Chromecast / TV)
|
|
284
|
+
// Mirrors only this app's screen — NOT the whole system.
|
|
285
|
+
// Flow: startAppCastScan() → onAppCastChange (devices arrive)
|
|
286
|
+
// → connectAppCast(deviceId) → onAppCastChange (state = "connected")
|
|
287
|
+
// → disconnectAppCast()
|
|
190
288
|
// ═══════════════════════════════════════════════
|
|
191
289
|
|
|
192
|
-
|
|
290
|
+
/**
|
|
291
|
+
* Start scanning for nearby castable devices (Chromecast, TV, etc.).
|
|
292
|
+
* Listen for results via `onAppCastChange`.
|
|
293
|
+
* Android: uses MediaRouter. iOS: no-op (AirPlay is system-only).
|
|
294
|
+
*/
|
|
295
|
+
export const startAppCastScan = (): void => {
|
|
296
|
+
if (!androidOnly("startAppCastScan")) return;
|
|
297
|
+
checkNative();
|
|
298
|
+
Native.startAppCastScan();
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Stop the device discovery scan.
|
|
303
|
+
*/
|
|
304
|
+
export const stopAppCastScan = (): void => {
|
|
305
|
+
if (!androidOnly("stopAppCastScan")) return;
|
|
306
|
+
checkNative();
|
|
307
|
+
Native.stopAppCastScan();
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Connect to a discovered device and begin casting this app's screen.
|
|
312
|
+
* @param deviceId The `id` field from `AppCastDevice` (MediaRouter route ID).
|
|
313
|
+
* @param pairingPin Optional PIN string if `requiresPairing` is true.
|
|
314
|
+
*/
|
|
315
|
+
export const connectAppCast = (deviceId: string, pairingPin?: string): void => {
|
|
316
|
+
if (!androidOnly("connectAppCast")) return;
|
|
317
|
+
checkNative();
|
|
318
|
+
Native.connectAppCast(deviceId, pairingPin ?? null);
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Disconnect the active in-app cast session.
|
|
323
|
+
*/
|
|
324
|
+
export const disconnectAppCast = (): void => {
|
|
325
|
+
if (!androidOnly("disconnectAppCast")) return;
|
|
326
|
+
checkNative();
|
|
327
|
+
Native.disconnectAppCast();
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Get the current in-app cast snapshot (state + device list).
|
|
332
|
+
*/
|
|
333
|
+
export const getAppCastInfo = (): Promise<AppCastInfo> => {
|
|
334
|
+
if (!isAndroid) {
|
|
335
|
+
return Promise.resolve({
|
|
336
|
+
state: "idle" as AppCastState,
|
|
337
|
+
devices: [],
|
|
338
|
+
connectedDevice: null,
|
|
339
|
+
error: null,
|
|
340
|
+
});
|
|
341
|
+
}
|
|
193
342
|
checkNative();
|
|
194
|
-
return Native.
|
|
343
|
+
return Native.getAppCastInfo();
|
|
195
344
|
};
|
|
196
345
|
|
|
197
|
-
|
|
198
|
-
|
|
346
|
+
/**
|
|
347
|
+
* Subscribe to in-app cast state changes.
|
|
348
|
+
* Fires on: device discovered/lost, state changes (scanning → connecting → connected),
|
|
349
|
+
* pairing requests, errors.
|
|
350
|
+
*
|
|
351
|
+
* @example
|
|
352
|
+
* const unsub = onAppCastChange((info) => {
|
|
353
|
+
* if (info.state === "connected") console.log("Casting to", info.connectedDevice?.name);
|
|
354
|
+
* if (info.error) console.warn("Cast error:", info.error);
|
|
355
|
+
* });
|
|
356
|
+
*
|
|
357
|
+
* @returns unsubscribe function
|
|
358
|
+
*/
|
|
359
|
+
export const onAppCastChange = (
|
|
360
|
+
callback: (info: AppCastInfo) => void,
|
|
199
361
|
): (() => void) => {
|
|
362
|
+
if (!isAndroid) {
|
|
363
|
+
// iOS: immediately call with idle state, return no-op
|
|
364
|
+
callback({
|
|
365
|
+
state: "idle",
|
|
366
|
+
devices: [],
|
|
367
|
+
connectedDevice: null,
|
|
368
|
+
error: null,
|
|
369
|
+
});
|
|
370
|
+
return () => {};
|
|
371
|
+
}
|
|
200
372
|
checkNative();
|
|
201
373
|
const { DeviceEventEmitter } = require("react-native");
|
|
202
|
-
|
|
203
|
-
|
|
374
|
+
const sub = DeviceEventEmitter.addListener(
|
|
375
|
+
"SystemBar_AppCastChange",
|
|
376
|
+
callback,
|
|
377
|
+
);
|
|
204
378
|
return () => {
|
|
205
379
|
sub.remove();
|
|
206
|
-
Native.stopScreencastListener();
|
|
207
380
|
};
|
|
208
381
|
};
|