playback styles

This commit is contained in:
2025-09-30 11:55:54 +02:00
parent fc17b09e7a
commit 81ddbb2cd4
2 changed files with 201 additions and 136 deletions
+182 -132
View File
@@ -23,7 +23,6 @@ import {
useWindowDimensions,
} from "react-native";
import Carousel from "react-native-reanimated-carousel";
import { responsiveHeight } from "react-native-responsive-dimensions";
import { setGlobal } from "reactn";
import { icons, img } from "../../assets";
import KaraokeLyrics from "../../components/KaraokeLyrics";
@@ -67,6 +66,7 @@ const CommentsPanel = ({
commentsCount,
onCommentAdded,
inputRef,
panelHeight,
}) => {
const { currentUID, currentUserData } = useUser() || {};
const [text, setText] = useState("");
@@ -177,98 +177,113 @@ const CommentsPanel = ({
<BlurView
intensity={Platform.OS !== "ios" ? 10 : 20}
tint="dark"
style={styles.commentsWrapper}
style={[
styles.commentsWrapper,
panelHeight ? { height: panelHeight } : null,
]}
>
<ScrollView
ref={scrollRef}
contentContainerStyle={styles.commentsContent}
keyboardDismissMode="interactive"
keyboardShouldPersistTaps="handled"
onScroll={handleScroll}
scrollEventThrottle={120}
showsVerticalScrollIndicator={false}
>
<Text style={styles.sectionTitle}>Description</Text>
<View style={styles.descriptionCard}>
<Text style={styles.descriptionText}>
{descriptionText || "Description chanson..."}
</Text>
</View>
<Text style={[styles.sectionTitle, styles.sectionSpacing]}>
{`Commentaires${commentsCount ? ` (${commentsCount})` : ""}`}
</Text>
{Array.isArray(comments) && comments.length > 0 ? (
comments.map((c, index) => (
<View
key={c.id}
style={[styles.commentRow, index > 0 && styles.commentRowSpacing]}
>
{c?.profilePicture ? (
<ExpoImage
source={{ uri: c.profilePicture }}
cachePolicy="memory-disk"
priority="high"
contentFit="cover"
transition={100}
style={styles.commentAvatar}
/>
) : (
<View style={styles.commentAvatarFallback} />
)}
<View style={styles.commentBubble}>
<View style={styles.commentHeader}>
<Text style={styles.commentAuthor}>
{c?.userId === currentUID ? "vous" : c?.userName || ""}
</Text>
<Text style={styles.commentMeta}>
{formatRelativeTime(c?.createdAt)}
</Text>
</View>
<Text style={styles.commentBody}>{c?.text}</Text>
</View>
</View>
))
) : (
<Text style={styles.emptyState}>
Aucun commentaire pour le moment
</Text>
)}
</ScrollView>
<View style={styles.commentInputContainer}>
<TextInput
ref={inputRef}
value={text}
onChangeText={setText}
placeholder={
canComment ? "Commente" : "Connecte-toi pour laisser un commentaire"
}
placeholderTextColor="#FFFFFF90"
editable={canComment}
style={[styles.commentInput, !canComment && { opacity: 0.6 }]}
/>
<Pressable
onPress={onSend}
disabled={!canComment || !text.trim()}
style={({ pressed }) => [
styles.sendButton,
(!canComment || !text.trim()) && styles.sendButtonDisabled,
pressed && canComment && styles.sendButtonPressed,
]}
<View style={styles.commentsInner}>
<ScrollView
ref={scrollRef}
style={styles.commentsScroll}
contentContainerStyle={styles.commentsContent}
keyboardDismissMode="interactive"
keyboardShouldPersistTaps="handled"
onScroll={handleScroll}
scrollEventThrottle={120}
showsVerticalScrollIndicator={false}
>
<Image
source={icons.send}
style={styles.sendIcon}
resizeMode="contain"
<Text style={styles.sectionTitle}>Description</Text>
<View style={styles.descriptionCard}>
<Text style={styles.descriptionText}>
{descriptionText || "Description chanson..."}
</Text>
</View>
<Text style={[styles.sectionTitle, styles.sectionSpacing]}>
{`Commentaires${commentsCount ? ` (${commentsCount})` : ""}`}
</Text>
{Array.isArray(comments) && comments.length > 0 ? (
comments.map((c, index) => (
<View
key={c.id}
style={[
styles.commentRow,
index > 0 && styles.commentRowSpacing,
]}
>
{c?.profilePicture ? (
<ExpoImage
source={{ uri: c.profilePicture }}
cachePolicy="memory-disk"
priority="high"
contentFit="cover"
transition={100}
style={styles.commentAvatar}
/>
) : (
<View style={styles.commentAvatarFallback} />
)}
<View style={styles.commentBubble}>
<View style={styles.commentHeader}>
<Text style={styles.commentAuthor}>
{c?.userId === currentUID ? "vous" : c?.userName || ""}
</Text>
<Text style={styles.commentMeta}>
{formatRelativeTime(c?.createdAt)}
</Text>
</View>
<Text style={styles.commentBody}>{c?.text}</Text>
</View>
</View>
))
) : (
<Text style={styles.emptyState}>
Aucun commentaire pour le moment
</Text>
)}
</ScrollView>
<View style={styles.commentInputContainer}>
<TextInput
ref={inputRef}
value={text}
onChangeText={setText}
placeholder={
canComment
? "Commente"
: "Connecte-toi pour laisser un commentaire"
}
placeholderTextColor="#FFFFFF90"
editable={canComment}
style={[styles.commentInput, !canComment && { opacity: 0.6 }]}
/>
</Pressable>
<Pressable
onPress={onSend}
disabled={!canComment || !text.trim()}
style={({ pressed }) => [
styles.sendButton,
(!canComment || !text.trim()) && styles.sendButtonDisabled,
pressed && canComment && styles.sendButtonPressed,
]}
>
<Image
source={icons.send}
style={styles.sendIcon}
resizeMode="contain"
/>
</Pressable>
</View>
</View>
</BlurView>
);
};
const PlaybackItem = ({ item, isActive, userCache, getUserByUid }) => {
const { width: viewportWidth } = useWindowDimensions();
const [layoutWidth, setLayoutWidth] = useState(viewportWidth);
const { width: viewportWidth, height: viewportHeight } =
useWindowDimensions();
const [layoutSize, setLayoutSize] = useState({
width: viewportWidth,
height: viewportHeight,
});
const commentInputRef = useRef(null);
const { currentUID, followUser, unfollowUser } = useUser() || {};
const videoUrl = item?.playbackUrl || null;
@@ -439,12 +454,16 @@ const PlaybackItem = ({ item, isActive, userCache, getUserByUid }) => {
item?.description || item?.title || "Description chanson";
const handleLayout = useCallback((event) => {
const nextWidth = event?.nativeEvent?.layout?.width;
if (typeof nextWidth === "number" && nextWidth > 0) {
setLayoutWidth(nextWidth);
}
const { width = 0, height = 0 } = event?.nativeEvent?.layout || {};
setLayoutSize((prev) => ({
width: width > 0 ? width : prev.width,
height: height > 0 ? height : prev.height,
}));
}, []);
const layoutWidth = layoutSize.width || viewportWidth;
const layoutHeight = layoutSize.height || viewportHeight;
const widePaddingThreshold = 900;
const horizontalPadding = layoutWidth >= widePaddingThreshold ? 64 : 32;
const innerWidth = Math.max(layoutWidth - horizontalPadding * 2, 0);
@@ -454,20 +473,42 @@ const PlaybackItem = ({ item, isActive, userCache, getUserByUid }) => {
const minCommentsWidth = 260;
const maxCommentsWidth = 420;
let sideBySide = innerWidth >= 620;
const baseInnerWidth = innerWidth > 0 ? innerWidth : viewportWidth;
let desiredHeight = Math.max(360, layoutHeight * 0.8);
let desiredWidth = desiredHeight * (9 / 16);
if (desiredWidth > maxVideoWidth) {
desiredWidth = maxVideoWidth;
desiredHeight = desiredWidth * (16 / 9);
}
if (desiredWidth < minVideoWidth) {
desiredWidth = minVideoWidth;
desiredHeight = desiredWidth * (16 / 9);
}
let sideBySide =
innerWidth >= desiredWidth + minCommentsWidth + gapBetweenColumns;
let videoWidth = sideBySide
? Math.min(maxVideoWidth, Math.max(innerWidth * 0.56, minVideoWidth))
: Math.min(innerWidth, maxVideoWidth);
? Math.min(maxVideoWidth, Math.max(desiredWidth, minVideoWidth))
: Math.min(Math.max(baseInnerWidth * 0.88, minVideoWidth), maxVideoWidth);
let videoHeight = videoWidth * (16 / 9);
if (videoHeight > desiredHeight) {
videoHeight = desiredHeight;
videoWidth = videoHeight * (9 / 16);
}
let commentsWidth = sideBySide
? innerWidth - videoWidth - gapBetweenColumns
: innerWidth;
: baseInnerWidth;
if (sideBySide && commentsWidth > maxCommentsWidth) {
videoWidth = Math.min(
maxVideoWidth,
videoWidth + (commentsWidth - maxCommentsWidth)
);
const excess = commentsWidth - maxCommentsWidth;
commentsWidth = maxCommentsWidth;
videoWidth = Math.min(maxVideoWidth, videoWidth + excess);
videoHeight = videoWidth * (16 / 9);
}
if (sideBySide && commentsWidth < minCommentsWidth) {
@@ -475,33 +516,28 @@ const PlaybackItem = ({ item, isActive, userCache, getUserByUid }) => {
if (videoWidth - deficit >= minVideoWidth) {
videoWidth -= deficit;
commentsWidth = minCommentsWidth;
videoHeight = videoWidth * (16 / 9);
} else {
sideBySide = false;
videoWidth = Math.min(innerWidth, maxVideoWidth);
commentsWidth = innerWidth;
videoWidth = Math.min(baseInnerWidth, maxVideoWidth);
commentsWidth = baseInnerWidth;
videoHeight = videoWidth * (16 / 9);
}
}
const baseInnerWidth = innerWidth > 0 ? innerWidth : viewportWidth;
const safeVideoWidth = Math.max(
minVideoWidth,
Math.min(
Number.isFinite(videoWidth) ? videoWidth : baseInnerWidth,
maxVideoWidth
)
);
const videoCardWidth = sideBySide
? safeVideoWidth
: Math.min(Math.max(baseInnerWidth * 0.88, minVideoWidth), maxVideoWidth);
if (!sideBySide) {
videoHeight = Math.min(videoHeight, desiredHeight);
videoWidth = videoHeight * (9 / 16);
commentsWidth = videoWidth;
}
const panelHeight = Math.min(videoHeight, desiredHeight);
const containerHeight = viewportHeight;
const verticalPadding = Math.max((layoutHeight - panelHeight) / 2, 24);
const safeCommentsWidth = sideBySide
? Math.max(
minCommentsWidth,
Math.min(
Number.isFinite(commentsWidth) ? commentsWidth : baseInnerWidth,
maxCommentsWidth
)
)
: 0;
? Math.max(minCommentsWidth, Math.min(commentsWidth, maxCommentsWidth))
: undefined;
return (
<View
@@ -510,8 +546,9 @@ const PlaybackItem = ({ item, isActive, userCache, getUserByUid }) => {
styles.itemContainerBase,
sideBySide ? styles.itemContainerRow : styles.itemContainerCompact,
{
height: responsiveHeight(70),
height: "90%",
paddingHorizontal: sideBySide ? horizontalPadding : 24,
paddingVertical: verticalPadding,
},
]}
>
@@ -519,11 +556,19 @@ const PlaybackItem = ({ item, isActive, userCache, getUserByUid }) => {
style={[
styles.videoColumn,
sideBySide
? { width: safeVideoWidth, marginRight: gapBetweenColumns }
? { width: videoWidth, marginRight: gapBetweenColumns }
: { width: "100%" },
]}
>
<View style={[styles.videoSurface, { width: videoCardWidth }]}>
<View
style={[
styles.videoSurface,
{
// width: videoWidth,
height: panelHeight,
},
]}
>
<View style={styles.videoContainer}>
{!!videoUrl ? (
<VideoView
@@ -689,8 +734,8 @@ const PlaybackItem = ({ item, isActive, userCache, getUserByUid }) => {
style={[
styles.commentsColumn,
sideBySide
? { width: safeCommentsWidth }
: styles.commentsColumnCompact,
? { width: safeCommentsWidth, height: panelHeight }
: [styles.commentsColumnCompact, { height: panelHeight }],
]}
>
<CommentsPanel
@@ -701,6 +746,7 @@ const PlaybackItem = ({ item, isActive, userCache, getUserByUid }) => {
setCommentsCount((c) => Math.max(0, Number(c || 0) + 1))
}
inputRef={commentInputRef}
panelHeight={panelHeight}
/>
</View>
</View>
@@ -726,12 +772,10 @@ const Playbacks = () => {
});
useEffect(() => {
if (Platform.OS === "web") {
setGlobal({ webLayoutMode: "playbacks-wide" });
return () => setGlobal({ webLayoutMode: "default" });
}
return undefined;
}, []);
if (Platform.OS !== "web") return undefined;
setGlobal({ webLayoutMode: isFocused ? "playbacks-wide" : "default" });
return () => setGlobal({ webLayoutMode: "default" });
}, [isFocused]);
const onSnap = useCallback(
(index) => {
@@ -790,11 +834,9 @@ export default Playbacks;
const styles = StyleSheet.create({
screen: {
flex: 1,
// backgroundColor: Palette.darkPurple,
},
itemContainerBase: {
width: "100%",
// backgroundColor: Palette.darkPurple,
paddingVertical: 36,
alignItems: "center",
},
@@ -814,8 +856,8 @@ const styles = StyleSheet.create({
},
videoSurface: {
alignSelf: "center",
width: "100%",
maxWidth: 620,
// width: 100,
// maxWidth: 400,
aspectRatio: 9 / 16,
borderRadius: 32,
overflow: "hidden",
@@ -915,8 +957,16 @@ const styles = StyleSheet.create({
backgroundColor: Palette.glass,
minHeight: 0,
},
commentsInner: {
flex: 1,
justifyContent: "space-between",
},
commentsScroll: {
flex: 1,
},
commentsContent: {
paddingBottom: 24,
flexGrow: 1,
},
sectionTitle: {
fontSize: 18,