last fixes, home and videos

This commit is contained in:
Thomas Demirdjian
2025-11-10 14:24:16 +01:00
parent d7d9211fbe
commit dd4cf9b4d4
40 changed files with 984 additions and 647 deletions
+121
View File
@@ -0,0 +1,121 @@
import React from "react";
import { StyleSheet, Text, View } from "react-native";
import CoinIcon from "./CoinIcon";
import { FONT_FAMILY } from "../styles/Fonts";
const defaultFormatOptions = {
minimumFractionDigits: 0,
maximumFractionDigits: 0,
};
const formatAmount = (value, options = defaultFormatOptions) => {
if (typeof value !== "number" || !Number.isFinite(value)) {
return null;
}
try {
return new Intl.NumberFormat("fr-FR", {
...defaultFormatOptions,
...options,
}).format(value);
} catch (_error) {
return `${value}`;
}
};
const extractNumericValue = (value) => {
if (typeof value === "number" && Number.isFinite(value)) {
return value;
}
if (typeof value === "string") {
const parsed = Number(value);
if (Number.isFinite(parsed)) {
return parsed;
}
}
return null;
};
const CreditAmount = ({
value,
style,
textStyle,
iconSize = 18,
iconStyle,
iconPosition = "right",
gap = 6,
showPlus = false,
formatterOptions,
accessibilityLabel,
}) => {
const numericValue = React.useMemo(
() => extractNumericValue(value),
[value],
);
const resolvedValue =
numericValue !== null
? numericValue
: value ?? 0;
const formattedValue =
numericValue !== null
? formatAmount(resolvedValue, formatterOptions) ?? `${resolvedValue}`
: typeof resolvedValue === "string"
? resolvedValue
: `${resolvedValue}`;
const prefix =
numericValue !== null && showPlus && numericValue > 0 ? "+" : "";
const a11yLabel =
accessibilityLabel || `${prefix}${formattedValue} pièces`;
const containerStyles = Array.isArray(style)
? [styles.container, { gap }, ...style]
: [styles.container, { gap }, style];
const textStyles = Array.isArray(textStyle)
? [styles.value, ...textStyle]
: [styles.value, textStyle];
const iconStyles = Array.isArray(iconStyle)
? [styles.icon, ...iconStyle]
: [styles.icon, iconStyle];
return (
<View
style={containerStyles}
accessibilityRole="text"
accessibilityLabel={a11yLabel}
>
{iconPosition === "left" ? (
<CoinIcon size={iconSize} style={iconStyles} />
) : null}
<Text style={textStyles}>{`${prefix}${formattedValue}`}</Text>
{iconPosition === "right" ? (
<CoinIcon size={iconSize} style={iconStyles} />
) : null}
</View>
);
};
const styles = StyleSheet.create({
container: {
flexDirection: "row",
alignItems: "center",
},
value: {
fontFamily: FONT_FAMILY.InterSemiBold,
fontSize: 16,
color: "#fff",
},
icon: {
width: 18,
height: 18,
},
});
export default CreditAmount;
+12 -9
View File
@@ -11,6 +11,7 @@ const MusicLandHeader = ({
showSkip = false,
progress = 1,
logo = null,
style,
}) => {
const isWeb = Platform.OS === "web";
const backButtonStyle = isWeb
@@ -34,7 +35,7 @@ const MusicLandHeader = ({
};
return (
<View style={{ alignItems: "center", gap: 16 }}>
<View style={{ alignItems: "center", gap: 16, ...style }}>
<View style={{ width: "100%", ...Style.containerRow, gap: 16 }}>
<Pressable style={backButtonStyle} onPress={onPressBack}>
<Image
@@ -72,14 +73,16 @@ const MusicLandHeader = ({
</Pressable>
)}
</View>
<Image
source={logo}
style={{
alignSelf: "center",
height: isWeb ? 150 : 100,
resizeMode: "contain",
}}
/>
{logo && (
<Image
source={logo}
style={{
alignSelf: "center",
height: isWeb ? 150 : 100,
resizeMode: "contain",
}}
/>
)}
</View>
);
};
+40 -9
View File
@@ -1,4 +1,4 @@
import { useEffect, useState } from "react";
import { useEffect, useRef, useState } from "react";
import { StyleSheet, Text, View } from "react-native";
import { Gesture, GestureDetector } from "react-native-gesture-handler";
import Animated, {
@@ -24,6 +24,7 @@ export default ({
const offset = useSharedValue(0);
const boxWidth = useSharedValue(INITIAL_BOX_SIZE);
const [layout, setLayout] = useState(null);
const seekingRef = useRef(false);
const SLIDER_WIDTH = layout?.width;
const MAX_VALUE = SLIDER_WIDTH - INITIAL_BOX_SIZE;
@@ -31,12 +32,34 @@ export default ({
const pan = Gesture.Pan()
.enabled(seekEnabled)
.onBegin(() => {
if (seekEnabled && typeof onSeekStart === "function") {
// Notify JS thread that user started seeking (e.g., pause audio)
if (
seekEnabled &&
typeof onSeekStart === "function" &&
!seekingRef.current
) {
seekingRef.current = true;
runOnJS(onSeekStart)();
}
})
.onStart(() => {
if (
seekEnabled &&
typeof onSeekStart === "function" &&
!seekingRef.current
) {
seekingRef.current = true;
runOnJS(onSeekStart)();
}
})
.onChange((event) => {
if (
seekEnabled &&
typeof onSeekStart === "function" &&
!seekingRef.current
) {
seekingRef.current = true;
runOnJS(onSeekStart)();
}
offset.value =
Math.abs(offset.value) <= MAX_VALUE
? offset.value + event.changeX <= 0
@@ -50,14 +73,22 @@ export default ({
boxWidth.value = newWidth;
})
.onEnd(() => {
if (!seekEnabled || !onSeek || !MAX_VALUE) return;
const ratio =
MAX_VALUE > 0 ? Math.min(1, Math.max(0, offset.value / MAX_VALUE)) : 0;
// Reanimated -> JS thread bridge
runOnJS(onSeek)(ratio);
if (seekEnabled && typeof onSeek === "function" && MAX_VALUE) {
const ratio =
MAX_VALUE > 0
? Math.min(1, Math.max(0, offset.value / MAX_VALUE))
: 0;
// Reanimated -> JS thread bridge
runOnJS(onSeek)(ratio);
}
if (seekingRef.current && typeof onSeekEnd === "function") {
seekingRef.current = false;
runOnJS(onSeekEnd)();
}
})
.onFinalize(() => {
if (seekEnabled && typeof onSeekEnd === "function") {
if (seekingRef.current && typeof onSeekEnd === "function") {
seekingRef.current = false;
runOnJS(onSeekEnd)();
}
});
+7 -11
View File
@@ -1,7 +1,6 @@
import React from "react";
import {
ActivityIndicator,
Image,
Linking,
Modal,
Pressable,
@@ -11,7 +10,6 @@ import {
View,
} from "react-native";
import { BlurView } from "expo-blur";
import { icons } from "../../assets";
import BorderGradientButton from "../BorderGradientButton";
import GradientButton from "../GradientButton";
import CreateLyricsHeader from "../../screens/Writing/components/CreateLyricsHeader";
@@ -19,6 +17,7 @@ import { Palette, gutters } from "../../styles";
import { FONT_FAMILY } from "../../styles/Fonts";
import { getFunctionsClient } from "../../config/firebase";
import { isWeb } from "../../hooks/useLayoutType";
import CreditAmount from "../CreditAmount";
const WEB_MODAL_MAX_WIDTH = 1000;
const FUNCTIONS_REGION = "europe-west1";
@@ -73,10 +72,12 @@ function CoinPackCard({ pack, selected, onSelect }) {
>
<BlurView intensity={20} tint="dark" style={styles.cardBlur}>
<View style={styles.cardHeader}>
<View style={styles.coinRow}>
<Image source={icons.coin} style={styles.coinIcon} />
<Text style={styles.coinAmount}>{pack?.coinAmount} pièces</Text>
</View>
<CreditAmount
value={pack?.coinAmount}
style={styles.coinRow}
textStyle={styles.coinAmount}
iconSize={26}
/>
{pack?.name ? <Text style={styles.packName}>{pack.name}</Text> : null}
</View>
{pack?.description ? (
@@ -395,11 +396,6 @@ const styles = StyleSheet.create({
alignItems: "center",
gap: 10,
},
coinIcon: {
width: 26,
height: 26,
resizeMode: "contain",
},
coinAmount: {
fontFamily: FONT_FAMILY.InterBold,
fontSize: 20,