@streamplace/components 0.7.2 → 0.7.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/dist/components/mobile-player/ui/index.js +1 -0
- package/dist/components/mobile-player/ui/loading.js +104 -0
- package/dist/components/ui/resizeable.js +20 -11
- package/dist/player-store/player-store.js +3 -0
- package/dist/streamplace-store/stream.js +44 -23
- package/node-compile-cache/v22.15.0-x64-efe9a9df-0/37be0eec +0 -0
- package/node-compile-cache/v22.15.0-x64-efe9a9df-0/56540125 +0 -0
- package/node-compile-cache/{v22.15.0-x64-92db9086-0 → v22.15.0-x64-efe9a9df-0}/67b1eb60 +0 -0
- package/node-compile-cache/{v22.15.0-x64-92db9086-0 → v22.15.0-x64-efe9a9df-0}/7c275f90 +0 -0
- package/package.json +2 -2
- package/src/components/mobile-player/ui/index.ts +1 -0
- package/src/components/mobile-player/ui/loading.tsx +154 -0
- package/src/components/ui/resizeable.tsx +26 -15
- package/src/player-store/player-state.tsx +4 -0
- package/src/player-store/player-store.tsx +5 -0
- package/src/streamplace-store/stream.tsx +66 -35
- package/tsconfig.tsbuildinfo +1 -1
- package/node-compile-cache/v22.15.0-x64-92db9086-0/37be0eec +0 -0
- package/node-compile-cache/v22.15.0-x64-92db9086-0/56540125 +0 -0
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
const tslib_1 = require("tslib");
|
|
4
4
|
tslib_1.__exportStar(require("./countdown"), exports);
|
|
5
5
|
tslib_1.__exportStar(require("./input"), exports);
|
|
6
|
+
tslib_1.__exportStar(require("./loading"), exports);
|
|
6
7
|
tslib_1.__exportStar(require("./metrics"), exports);
|
|
7
8
|
tslib_1.__exportStar(require("./streamer-context-menu"), exports);
|
|
8
9
|
tslib_1.__exportStar(require("./viewer-context-menu"), exports);
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LoadingOverlay = LoadingOverlay;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
6
|
+
const react_1 = require("react");
|
|
7
|
+
const react_native_reanimated_1 = tslib_1.__importStar(require("react-native-reanimated"));
|
|
8
|
+
const atoms_1 = require("../../../lib/theme/atoms");
|
|
9
|
+
const defaultMessages = [
|
|
10
|
+
"Creating your stream",
|
|
11
|
+
"Uploading thumbnails",
|
|
12
|
+
"Getting things ready",
|
|
13
|
+
"Doing some magic",
|
|
14
|
+
"Preparing something special",
|
|
15
|
+
"Reticulating splines",
|
|
16
|
+
"Making it nice",
|
|
17
|
+
"Flipping some switches",
|
|
18
|
+
"Adding good vibes",
|
|
19
|
+
"Almost there",
|
|
20
|
+
"Summoning your Persona",
|
|
21
|
+
"Awakening our true selves",
|
|
22
|
+
"Fusion in progress",
|
|
23
|
+
"Equipping the right materia",
|
|
24
|
+
];
|
|
25
|
+
function LoadingOverlay({ visible, width, height, subtitle, messages = defaultMessages, interval = 3000, }) {
|
|
26
|
+
const [currentIndex, setCurrentIndex] = (0, react_1.useState)(0);
|
|
27
|
+
const [shouldRender, setShouldRender] = (0, react_1.useState)(visible);
|
|
28
|
+
// Animation values
|
|
29
|
+
const translateY = (0, react_native_reanimated_1.useSharedValue)(0);
|
|
30
|
+
const opacity = (0, react_native_reanimated_1.useSharedValue)(1);
|
|
31
|
+
const wholeOpacity = (0, react_native_reanimated_1.useSharedValue)(0);
|
|
32
|
+
// Handle fade-in and fade-out animations
|
|
33
|
+
(0, react_1.useEffect)(() => {
|
|
34
|
+
if (visible) {
|
|
35
|
+
setShouldRender(true); // Ensure the component is mounted
|
|
36
|
+
wholeOpacity.value = (0, react_native_reanimated_1.withTiming)(1, { duration: 500 }); // Fade in
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
wholeOpacity.value = (0, react_native_reanimated_1.withTiming)(0, { duration: 500 }, () => {
|
|
40
|
+
// Unmount after fade-out
|
|
41
|
+
(0, react_native_reanimated_1.runOnJS)(setShouldRender)(false);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}, [visible]);
|
|
45
|
+
// Cycle messages on a timer
|
|
46
|
+
(0, react_1.useEffect)(() => {
|
|
47
|
+
if (!visible) {
|
|
48
|
+
setCurrentIndex(0);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const timeout = setTimeout(() => {
|
|
52
|
+
setCurrentIndex((prev) => (prev + 1) % messages.length);
|
|
53
|
+
}, interval);
|
|
54
|
+
return () => clearTimeout(timeout);
|
|
55
|
+
}, [visible, currentIndex, interval, messages.length]);
|
|
56
|
+
// Trigger animation on each message change
|
|
57
|
+
(0, react_1.useEffect)(() => {
|
|
58
|
+
if (!visible)
|
|
59
|
+
return;
|
|
60
|
+
const fadeDuration = Math.min(interval / 2, 250); // Simplified fade duration
|
|
61
|
+
// Reset animation values
|
|
62
|
+
translateY.value = 20;
|
|
63
|
+
opacity.value = 0;
|
|
64
|
+
// Sequential fade-in and fade-out
|
|
65
|
+
translateY.value = (0, react_native_reanimated_1.withTiming)(0, { duration: fadeDuration });
|
|
66
|
+
opacity.value = (0, react_native_reanimated_1.withTiming)(1, { duration: fadeDuration }, () => {
|
|
67
|
+
// add a delay for interval - (fadeDuration*2)
|
|
68
|
+
translateY.value = (0, react_native_reanimated_1.withDelay)(interval - fadeDuration * 2, (0, react_native_reanimated_1.withTiming)(-10, { duration: fadeDuration }));
|
|
69
|
+
opacity.value = (0, react_native_reanimated_1.withDelay)(interval - fadeDuration * 2, (0, react_native_reanimated_1.withTiming)(0, { duration: fadeDuration }));
|
|
70
|
+
});
|
|
71
|
+
}, [currentIndex, visible]);
|
|
72
|
+
const animatedStyle = (0, react_native_reanimated_1.useAnimatedStyle)(() => {
|
|
73
|
+
return {
|
|
74
|
+
transform: [{ translateY: translateY.value }],
|
|
75
|
+
opacity: opacity.value,
|
|
76
|
+
};
|
|
77
|
+
});
|
|
78
|
+
const wholeAnimatedStyle = (0, react_native_reanimated_1.useAnimatedStyle)(() => ({
|
|
79
|
+
opacity: wholeOpacity.value,
|
|
80
|
+
}));
|
|
81
|
+
if (!shouldRender)
|
|
82
|
+
return null;
|
|
83
|
+
return ((0, jsx_runtime_1.jsxs)(react_native_reanimated_1.default.View, { style: [
|
|
84
|
+
{
|
|
85
|
+
position: "absolute",
|
|
86
|
+
top: 0,
|
|
87
|
+
left: 0,
|
|
88
|
+
width,
|
|
89
|
+
height,
|
|
90
|
+
backgroundColor: "rgba(0,0,0,0.7)",
|
|
91
|
+
alignItems: "center",
|
|
92
|
+
justifyContent: "center",
|
|
93
|
+
zIndex: 1000,
|
|
94
|
+
},
|
|
95
|
+
wholeAnimatedStyle,
|
|
96
|
+
], children: [(0, jsx_runtime_1.jsx)(react_native_reanimated_1.default.Text, { style: [
|
|
97
|
+
{
|
|
98
|
+
color: "white",
|
|
99
|
+
fontSize: 24,
|
|
100
|
+
fontWeight: "bold",
|
|
101
|
+
},
|
|
102
|
+
animatedStyle,
|
|
103
|
+
], children: messages[currentIndex] }), (0, jsx_runtime_1.jsx)(react_native_reanimated_1.default.Text, { style: [atoms_1.pt[5], { color: "#a0a0a0" }], children: subtitle })] }));
|
|
104
|
+
}
|
|
@@ -97,16 +97,25 @@ function Resizable({ startingPercentage, isPlayerRatioGreater, style = {}, child
|
|
|
97
97
|
minWidth: "100%",
|
|
98
98
|
},
|
|
99
99
|
style,
|
|
100
|
-
], children: [(0, jsx_runtime_1.jsx)(view_1.View, { style: [atoms_1.layout.flex.row, atoms_1.layout.flex.justifyCenter], children: (0, jsx_runtime_1.jsx)(react_native_gesture_handler_1.GestureDetector, { gesture: panGesture, children: (0, jsx_runtime_1.jsx)(view_1.View
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
100
|
+
], children: [(0, jsx_runtime_1.jsx)(view_1.View, { style: [atoms_1.layout.flex.row, atoms_1.layout.flex.justifyCenter, atoms_1.h[2]], children: (0, jsx_runtime_1.jsx)(react_native_gesture_handler_1.GestureDetector, { gesture: panGesture, children: (0, jsx_runtime_1.jsx)(view_1.View
|
|
101
|
+
// Make the touch area much larger, but keep the visible handle small
|
|
102
|
+
, {
|
|
103
|
+
// Make the touch area much larger, but keep the visible handle small
|
|
104
|
+
style: {
|
|
105
|
+
height: 30, // Large touch area
|
|
106
|
+
width: 120, // Wide enough for thumbs
|
|
107
|
+
alignItems: "center",
|
|
108
|
+
justifyContent: "center",
|
|
109
|
+
//backgroundColor: "rgba(0,255,255,0.1)",
|
|
110
|
+
transform: [{ translateY: -30 }],
|
|
111
|
+
}, children: (0, jsx_runtime_1.jsx)(view_1.View, { style: [
|
|
112
|
+
atoms_1.w[32],
|
|
113
|
+
{
|
|
114
|
+
height: 6,
|
|
115
|
+
backgroundColor: "#eeeeee66",
|
|
116
|
+
borderRadius: 999,
|
|
117
|
+
transform: [{ translateY: 5 }],
|
|
118
|
+
},
|
|
119
|
+
] }) }) }) }), children] })] }));
|
|
111
120
|
}
|
|
112
121
|
Resizable.displayName = "ResizableChatSheet";
|
|
@@ -47,6 +47,9 @@ const makePlayerStore = (id) => {
|
|
|
47
47
|
setVideoRef: (videoRef) => set(() => ({ videoRef })),
|
|
48
48
|
pipMode: false,
|
|
49
49
|
setPipMode: (pipMode) => set(() => ({ pipMode })),
|
|
50
|
+
// Picture-in-Picture action function (set by player component)
|
|
51
|
+
pipAction: undefined,
|
|
52
|
+
setPipAction: (action) => set(() => ({ pipAction: action })),
|
|
50
53
|
// Player element width/height setters for global sync
|
|
51
54
|
playerWidth: undefined,
|
|
52
55
|
setPlayerWidth: (playerWidth) => set(() => ({ playerWidth })),
|
|
@@ -5,29 +5,48 @@ exports.useUpdateStreamRecord = useUpdateStreamRecord;
|
|
|
5
5
|
const api_1 = require("@atproto/api");
|
|
6
6
|
const streamplace_store_1 = require("./streamplace-store");
|
|
7
7
|
const xrpc_1 = require("./xrpc");
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
8
|
+
const react_1 = require("react");
|
|
9
|
+
const useUploadThumbnail = () => {
|
|
10
|
+
const abortRef = (0, react_1.useRef)(null);
|
|
11
|
+
(0, react_1.useEffect)(() => {
|
|
12
|
+
return () => {
|
|
13
|
+
// On unmount, abort any ongoing upload
|
|
14
|
+
abortRef.current?.abort();
|
|
15
|
+
};
|
|
16
|
+
}, []);
|
|
17
|
+
const uploadThumbnail = async (pdsAgent, customThumbnail) => {
|
|
18
|
+
if (!customThumbnail)
|
|
19
|
+
return undefined;
|
|
20
|
+
abortRef.current = new AbortController();
|
|
21
|
+
const { signal } = abortRef.current;
|
|
22
|
+
const maxTries = 3;
|
|
23
|
+
let lastError = null;
|
|
24
|
+
for (let tries = 0; tries < maxTries; tries++) {
|
|
25
|
+
try {
|
|
26
|
+
const thumbnail = await pdsAgent.uploadBlob(customThumbnail, {
|
|
27
|
+
signal,
|
|
28
|
+
});
|
|
29
|
+
if (thumbnail.success &&
|
|
30
|
+
thumbnail.data.blob.size === customThumbnail.size) {
|
|
31
|
+
console.log("Successfully uploaded thumbnail");
|
|
32
|
+
return thumbnail.data.blob;
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
console.warn(`Blob size mismatch (attempt ${tries + 1}): received ${thumbnail.data.blob.size}, expected ${customThumbnail.size}`);
|
|
36
|
+
}
|
|
21
37
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
38
|
+
catch (e) {
|
|
39
|
+
if (signal.aborted) {
|
|
40
|
+
console.warn("Upload aborted");
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
lastError = e;
|
|
44
|
+
console.warn(`Error uploading thumbnail (attempt ${tries + 1}): ${e}`);
|
|
25
45
|
}
|
|
26
46
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
47
|
+
throw new Error(`Could not successfully upload blob after ${maxTries} attempts. Last error: ${lastError}`);
|
|
48
|
+
};
|
|
49
|
+
return uploadThumbnail;
|
|
31
50
|
};
|
|
32
51
|
async function createNewPost(agent, record) {
|
|
33
52
|
try {
|
|
@@ -39,7 +58,7 @@ async function createNewPost(agent, record) {
|
|
|
39
58
|
throw error;
|
|
40
59
|
}
|
|
41
60
|
}
|
|
42
|
-
function buildGoLivePost(text, url, profile, params, thumbnail) {
|
|
61
|
+
async function buildGoLivePost(text, url, profile, params, thumbnail, agent) {
|
|
43
62
|
const now = new Date();
|
|
44
63
|
const linkUrl = `${url.protocol}//${url.host}/${profile.handle}?${params.toString()}`;
|
|
45
64
|
const prefix = `🔴 LIVE `;
|
|
@@ -47,7 +66,7 @@ function buildGoLivePost(text, url, profile, params, thumbnail) {
|
|
|
47
66
|
const suffix = ` ${text}`;
|
|
48
67
|
const content = prefix + textUrl + suffix;
|
|
49
68
|
const rt = new api_1.RichText({ text: content });
|
|
50
|
-
rt.
|
|
69
|
+
await rt.detectFacets(agent);
|
|
51
70
|
const record = {
|
|
52
71
|
$type: "app.bsky.feed.post",
|
|
53
72
|
text: content,
|
|
@@ -72,6 +91,7 @@ function buildGoLivePost(text, url, profile, params, thumbnail) {
|
|
|
72
91
|
function useCreateStreamRecord() {
|
|
73
92
|
let agent = (0, xrpc_1.usePDSAgent)();
|
|
74
93
|
let url = (0, streamplace_store_1.useUrl)();
|
|
94
|
+
const uploadThumbnail = useUploadThumbnail();
|
|
75
95
|
return async (title, customThumbnail, submitPost = true) => {
|
|
76
96
|
if (!agent) {
|
|
77
97
|
throw new Error("No PDS agent found");
|
|
@@ -130,7 +150,7 @@ function useCreateStreamRecord() {
|
|
|
130
150
|
did: did,
|
|
131
151
|
time: new Date().toISOString(),
|
|
132
152
|
});
|
|
133
|
-
let post = buildGoLivePost(title, u, profile.data, params, thumbnail);
|
|
153
|
+
let post = await buildGoLivePost(title, u, profile.data, params, thumbnail, agent);
|
|
134
154
|
newPost = await createNewPost(agent, post);
|
|
135
155
|
if (!newPost.uri || !newPost.cid) {
|
|
136
156
|
throw new Error("Cannot read properties of undefined (reading 'uri' or 'cid')");
|
|
@@ -154,6 +174,7 @@ function useCreateStreamRecord() {
|
|
|
154
174
|
function useUpdateStreamRecord() {
|
|
155
175
|
let agent = (0, xrpc_1.usePDSAgent)();
|
|
156
176
|
let url = (0, streamplace_store_1.useUrl)();
|
|
177
|
+
const uploadThumbnail = useUploadThumbnail();
|
|
157
178
|
return async (title, livestream, customThumbnail) => {
|
|
158
179
|
if (!agent) {
|
|
159
180
|
throw new Error("No PDS agent found");
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@streamplace/components",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.3",
|
|
4
4
|
"description": "Streamplace React (Native) Components",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "src/index.tsx",
|
|
@@ -50,5 +50,5 @@
|
|
|
50
50
|
"peerDependencies": {
|
|
51
51
|
"react": "*"
|
|
52
52
|
},
|
|
53
|
-
"gitHead": "
|
|
53
|
+
"gitHead": "70367834f75a94f074bd81299c2e72c36b0dbbf0"
|
|
54
54
|
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import Animated, {
|
|
3
|
+
runOnJS,
|
|
4
|
+
useAnimatedStyle,
|
|
5
|
+
useSharedValue,
|
|
6
|
+
withDelay,
|
|
7
|
+
withTiming,
|
|
8
|
+
} from "react-native-reanimated";
|
|
9
|
+
import { pt } from "../../../lib/theme/atoms";
|
|
10
|
+
|
|
11
|
+
type LoadingOverlayProps = {
|
|
12
|
+
visible: boolean;
|
|
13
|
+
width: number;
|
|
14
|
+
height: number;
|
|
15
|
+
subtitle?: string;
|
|
16
|
+
messages?: string[];
|
|
17
|
+
interval?: number; // in milliseconds
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const defaultMessages = [
|
|
21
|
+
"Creating your stream",
|
|
22
|
+
"Uploading thumbnails",
|
|
23
|
+
"Getting things ready",
|
|
24
|
+
"Doing some magic",
|
|
25
|
+
"Preparing something special",
|
|
26
|
+
"Reticulating splines",
|
|
27
|
+
"Making it nice",
|
|
28
|
+
"Flipping some switches",
|
|
29
|
+
"Adding good vibes",
|
|
30
|
+
"Almost there",
|
|
31
|
+
"Summoning your Persona",
|
|
32
|
+
"Awakening our true selves",
|
|
33
|
+
"Fusion in progress",
|
|
34
|
+
"Equipping the right materia",
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
export function LoadingOverlay({
|
|
38
|
+
visible,
|
|
39
|
+
width,
|
|
40
|
+
height,
|
|
41
|
+
subtitle,
|
|
42
|
+
messages = defaultMessages,
|
|
43
|
+
interval = 3000,
|
|
44
|
+
}: LoadingOverlayProps) {
|
|
45
|
+
const [currentIndex, setCurrentIndex] = useState(0);
|
|
46
|
+
const [shouldRender, setShouldRender] = useState(visible);
|
|
47
|
+
|
|
48
|
+
// Animation values
|
|
49
|
+
const translateY = useSharedValue(0);
|
|
50
|
+
const opacity = useSharedValue(1);
|
|
51
|
+
|
|
52
|
+
const wholeOpacity = useSharedValue(0);
|
|
53
|
+
|
|
54
|
+
// Handle fade-in and fade-out animations
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
if (visible) {
|
|
57
|
+
setShouldRender(true); // Ensure the component is mounted
|
|
58
|
+
wholeOpacity.value = withTiming(1, { duration: 500 }); // Fade in
|
|
59
|
+
} else {
|
|
60
|
+
wholeOpacity.value = withTiming(0, { duration: 500 }, () => {
|
|
61
|
+
// Unmount after fade-out
|
|
62
|
+
runOnJS(setShouldRender)(false);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}, [visible]);
|
|
66
|
+
|
|
67
|
+
// Cycle messages on a timer
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
if (!visible) {
|
|
70
|
+
setCurrentIndex(0);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const timeout = setTimeout(() => {
|
|
75
|
+
setCurrentIndex((prev) => (prev + 1) % messages.length);
|
|
76
|
+
}, interval);
|
|
77
|
+
|
|
78
|
+
return () => clearTimeout(timeout);
|
|
79
|
+
}, [visible, currentIndex, interval, messages.length]);
|
|
80
|
+
|
|
81
|
+
// Trigger animation on each message change
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
if (!visible) return;
|
|
84
|
+
|
|
85
|
+
const fadeDuration = Math.min(interval / 2, 250); // Simplified fade duration
|
|
86
|
+
|
|
87
|
+
// Reset animation values
|
|
88
|
+
translateY.value = 20;
|
|
89
|
+
opacity.value = 0;
|
|
90
|
+
|
|
91
|
+
// Sequential fade-in and fade-out
|
|
92
|
+
translateY.value = withTiming(0, { duration: fadeDuration });
|
|
93
|
+
opacity.value = withTiming(1, { duration: fadeDuration }, () => {
|
|
94
|
+
// add a delay for interval - (fadeDuration*2)
|
|
95
|
+
|
|
96
|
+
translateY.value = withDelay(
|
|
97
|
+
interval - fadeDuration * 2,
|
|
98
|
+
withTiming(-10, { duration: fadeDuration }),
|
|
99
|
+
);
|
|
100
|
+
opacity.value = withDelay(
|
|
101
|
+
interval - fadeDuration * 2,
|
|
102
|
+
withTiming(0, { duration: fadeDuration }),
|
|
103
|
+
);
|
|
104
|
+
});
|
|
105
|
+
}, [currentIndex, visible]);
|
|
106
|
+
|
|
107
|
+
const animatedStyle = useAnimatedStyle(() => {
|
|
108
|
+
return {
|
|
109
|
+
transform: [{ translateY: translateY.value }],
|
|
110
|
+
opacity: opacity.value,
|
|
111
|
+
};
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const wholeAnimatedStyle = useAnimatedStyle(() => ({
|
|
115
|
+
opacity: wholeOpacity.value,
|
|
116
|
+
}));
|
|
117
|
+
|
|
118
|
+
if (!shouldRender) return null;
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
<Animated.View
|
|
122
|
+
style={[
|
|
123
|
+
{
|
|
124
|
+
position: "absolute",
|
|
125
|
+
top: 0,
|
|
126
|
+
left: 0,
|
|
127
|
+
width,
|
|
128
|
+
height,
|
|
129
|
+
backgroundColor: "rgba(0,0,0,0.7)",
|
|
130
|
+
alignItems: "center",
|
|
131
|
+
justifyContent: "center",
|
|
132
|
+
zIndex: 1000,
|
|
133
|
+
},
|
|
134
|
+
wholeAnimatedStyle,
|
|
135
|
+
]}
|
|
136
|
+
>
|
|
137
|
+
<Animated.Text
|
|
138
|
+
style={[
|
|
139
|
+
{
|
|
140
|
+
color: "white",
|
|
141
|
+
fontSize: 24,
|
|
142
|
+
fontWeight: "bold",
|
|
143
|
+
},
|
|
144
|
+
animatedStyle,
|
|
145
|
+
]}
|
|
146
|
+
>
|
|
147
|
+
{messages[currentIndex]}
|
|
148
|
+
</Animated.Text>
|
|
149
|
+
<Animated.Text style={[pt[5], { color: "#a0a0a0" }]}>
|
|
150
|
+
{subtitle}
|
|
151
|
+
</Animated.Text>
|
|
152
|
+
</Animated.View>
|
|
153
|
+
);
|
|
154
|
+
}
|
|
@@ -15,7 +15,7 @@ import Animated, {
|
|
|
15
15
|
} from "react-native-reanimated";
|
|
16
16
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
17
17
|
import { useKeyboardSlide } from "../../hooks";
|
|
18
|
-
import { bottom, layout, p, w, zIndex } from "../../lib/theme/atoms";
|
|
18
|
+
import { bottom, h, layout, p, w, zIndex } from "../../lib/theme/atoms";
|
|
19
19
|
import { View } from "./view";
|
|
20
20
|
|
|
21
21
|
const AnimatedView = Animated.createAnimatedComponent(View);
|
|
@@ -154,24 +154,35 @@ export function Resizable({
|
|
|
154
154
|
style,
|
|
155
155
|
]}
|
|
156
156
|
>
|
|
157
|
-
<View style={[layout.flex.row, layout.flex.justifyCenter]}>
|
|
157
|
+
<View style={[layout.flex.row, layout.flex.justifyCenter, h[2]]}>
|
|
158
158
|
<GestureDetector gesture={panGesture}>
|
|
159
159
|
<View
|
|
160
|
-
|
|
161
|
-
style={
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
160
|
+
// Make the touch area much larger, but keep the visible handle small
|
|
161
|
+
style={{
|
|
162
|
+
height: 30, // Large touch area
|
|
163
|
+
width: 120, // Wide enough for thumbs
|
|
164
|
+
alignItems: "center",
|
|
165
|
+
justifyContent: "center",
|
|
166
|
+
//backgroundColor: "rgba(0,255,255,0.1)",
|
|
167
|
+
transform: [{ translateY: -30 }],
|
|
168
|
+
}}
|
|
169
|
+
>
|
|
170
|
+
<View
|
|
171
|
+
style={[
|
|
172
|
+
w[32],
|
|
173
|
+
{
|
|
174
|
+
height: 6,
|
|
175
|
+
backgroundColor: "#eeeeee66",
|
|
176
|
+
borderRadius: 999,
|
|
177
|
+
|
|
178
|
+
transform: [{ translateY: 5 }],
|
|
179
|
+
},
|
|
180
|
+
]}
|
|
181
|
+
/>
|
|
182
|
+
</View>
|
|
173
183
|
</GestureDetector>
|
|
174
184
|
</View>
|
|
185
|
+
|
|
175
186
|
{children}
|
|
176
187
|
</AnimatedView>
|
|
177
188
|
</>
|
|
@@ -119,6 +119,10 @@ export interface PlayerState {
|
|
|
119
119
|
| undefined,
|
|
120
120
|
) => void;
|
|
121
121
|
|
|
122
|
+
pipAction: (() => void) | undefined;
|
|
123
|
+
/** Function to set the Picture-in-Picture action */
|
|
124
|
+
setPipAction: (action: (() => void) | undefined) => void;
|
|
125
|
+
|
|
122
126
|
/** Player element width (CSS value or number) */
|
|
123
127
|
playerWidth?: string | number;
|
|
124
128
|
/** Function to set the player width */
|
|
@@ -83,6 +83,11 @@ export const makePlayerStore = (id?: string): StoreApi<PlayerState> => {
|
|
|
83
83
|
pipMode: false,
|
|
84
84
|
setPipMode: (pipMode: boolean) => set(() => ({ pipMode })),
|
|
85
85
|
|
|
86
|
+
// Picture-in-Picture action function (set by player component)
|
|
87
|
+
pipAction: undefined,
|
|
88
|
+
setPipAction: (action: (() => void) | undefined) =>
|
|
89
|
+
set(() => ({ pipAction: action })),
|
|
90
|
+
|
|
86
91
|
// Player element width/height setters for global sync
|
|
87
92
|
playerWidth: undefined,
|
|
88
93
|
setPlayerWidth: (playerWidth: number) => set(() => ({ playerWidth })),
|