feat: fixes and formatter

This commit is contained in:
2026-01-12 16:01:32 +01:00
parent 85c6084351
commit 11e632acff
353 changed files with 23315 additions and 27361 deletions
@@ -1,11 +1,5 @@
import { BlurView } from "expo-blur";
import React, {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from "react";
import { BlurView } from 'expo-blur'
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import {
Animated,
ImageBackground,
@@ -13,42 +7,42 @@ import {
StyleSheet,
View,
useWindowDimensions,
} from "react-native";
import { ai, cardsImg } from "../../assets";
import { getCreationStageStates } from "../../utils/projectStages";
import AnimatedPaginationDot from "../AnimatedPaginationDot/AnimatedPaginationDot";
import PersonaCard from "../cards/PersonaCard/PersonaCard";
import { LinearGradient } from "../LinearGradient/LinearGradient";
} from 'react-native'
import { ai, cardsImg } from '../../assets'
import { getCreationStageStates } from '../../utils/projectStages'
import AnimatedPaginationDot from '../AnimatedPaginationDot/AnimatedPaginationDot'
import PersonaCard from '../cards/PersonaCard/PersonaCard'
import { LinearGradient } from '../LinearGradient/LinearGradient'
const STAGE_CARD_CONTENT = [
{
key: "songwriter",
title: "Céline",
description: "Lets write lyrics together !",
key: 'songwriter',
title: 'Céline',
description: 'Lets write lyrics together !',
image: ai.leftIcon,
},
{
key: "beatmaker",
title: "Theo",
key: 'beatmaker',
title: 'Theo',
description: "Come back, when you'll have lyrics!",
image: ai.rightIcon,
},
{
key: "director",
title: "Theo",
key: 'director',
title: 'Theo',
description: "Theo t'accompagne pour créer ton playback.",
image: ai.rightIcon,
},
{
key: "publisher",
title: "Publication",
description: "Ta vidéo est prête ? Direction YouTube !",
key: 'publisher',
title: 'Publication',
description: 'Ta vidéo est prête ? Direction YouTube !',
image: cardsImg.production,
},
];
]
const WEB_SCROLL_INACTIVE_DELTA = 0.05;
const HOME_BACKGROUND_COLOR = "#425B87"; // Derived from the bottom color of the home hero image
const WEB_SCROLL_INACTIVE_DELTA = 0.05
const HOME_BACKGROUND_COLOR = '#425B87' // Derived from the bottom color of the home hero image
const FeatureCarousel = ({
style,
selectedProject,
@@ -60,257 +54,245 @@ const FeatureCarousel = ({
}) => {
const stageStates = useMemo(() => {
if (stageStatesProp) {
return stageStatesProp;
return stageStatesProp
}
return getCreationStageStates(selectedProject);
}, [stageStatesProp, selectedProject]);
return getCreationStageStates(selectedProject)
}, [stageStatesProp, selectedProject])
const stageStatesByKey = useMemo(() => {
if (!Array.isArray(stageStates)) {
return {};
return {}
}
return stageStates.reduce((acc, stage) => {
if (stage?.key) {
acc[stage.key] = stage;
acc[stage.key] = stage
}
return acc;
}, {});
}, [stageStates]);
return acc
}, {})
}, [stageStates])
const carouselItems = useMemo(
() =>
STAGE_CARD_CONTENT.map((item) => {
const state = stageStatesByKey[item.key];
const state = stageStatesByKey[item.key]
return {
...item,
isLocked: state?.isLocked ?? true,
description: state?.description ?? item.description,
};
}
}),
[stageStatesByKey]
);
)
const { height: windowHeight } = useWindowDimensions();
const isWeb = Platform.OS === "web";
const { height: windowHeight } = useWindowDimensions()
const isWeb = Platform.OS === 'web'
const [viewportHeight, setViewportHeight] = useState(() =>
Math.max(windowHeight, 1)
);
const [viewportHeight, setViewportHeight] = useState(() => Math.max(windowHeight, 1))
const updateSnapHeight = useCallback((height) => {
if (!height || Number.isNaN(height)) {
return;
return
}
setViewportHeight((prev) => {
if (prev == null || Math.abs(prev - height) > 0.5) {
return height;
return height
}
return prev;
});
}, []);
return prev
})
}, [])
useEffect(() => {
updateSnapHeight(Math.max(windowHeight, 1));
}, [updateSnapHeight, windowHeight]);
updateSnapHeight(Math.max(windowHeight, 1))
}, [updateSnapHeight, windowHeight])
const itemHeight = Math.max(viewportHeight, 1);
const itemHeight = Math.max(viewportHeight, 1)
const scrollViewRef = useRef(null);
const alignTimeoutRef = useRef(null);
const activeIndexRef = useRef(
typeof activeIndex === "number" ? activeIndex : 0
);
const onActiveIndexChangeRef = useRef(onActiveIndexChange);
const scrollY = useRef(new Animated.Value(0)).current;
const scrollViewRef = useRef(null)
const alignTimeoutRef = useRef(null)
const activeIndexRef = useRef(typeof activeIndex === 'number' ? activeIndex : 0)
const onActiveIndexChangeRef = useRef(onActiveIndexChange)
const scrollY = useRef(new Animated.Value(0)).current
useEffect(() => {
onActiveIndexChangeRef.current = onActiveIndexChange;
}, [onActiveIndexChange]);
onActiveIndexChangeRef.current = onActiveIndexChange
}, [onActiveIndexChange])
useEffect(() => {
if (typeof activeIndex === "number") {
activeIndexRef.current = activeIndex;
if (typeof activeIndex === 'number') {
activeIndexRef.current = activeIndex
}
}, [activeIndex]);
}, [activeIndex])
const clampIndex = useCallback(
(index) => {
if (!carouselItems.length) {
return 0;
return 0
}
if (index < 0) {
return 0;
return 0
}
if (index >= carouselItems.length) {
return carouselItems.length - 1;
return carouselItems.length - 1
}
return index;
return index
},
[carouselItems.length]
);
)
const clearPendingAlignment = useCallback(() => {
if (alignTimeoutRef.current != null) {
globalThis.clearTimeout(alignTimeoutRef.current);
alignTimeoutRef.current = null;
globalThis.clearTimeout(alignTimeoutRef.current)
alignTimeoutRef.current = null
}
}, []);
}, [])
useEffect(() => () => clearPendingAlignment(), [clearPendingAlignment]);
useEffect(() => () => clearPendingAlignment(), [clearPendingAlignment])
const getScrollNode = useCallback(() => {
const node = scrollViewRef.current;
const node = scrollViewRef.current
if (!node) {
return null;
return null
}
if (typeof node.scrollTo === "function") {
return node;
if (typeof node.scrollTo === 'function') {
return node
}
if (typeof node.getNode === "function") {
return node.getNode();
if (typeof node.getNode === 'function') {
return node.getNode()
}
return null;
}, []);
return null
}, [])
const scrollToIndex = useCallback(
(index, animated = true, heightOverride) => {
const target = getScrollNode();
const target = getScrollNode()
if (!target) {
return;
return
}
const clamped = clampIndex(index);
const height =
heightOverride && heightOverride > 0 ? heightOverride : itemHeight;
const clamped = clampIndex(index)
const height = heightOverride && heightOverride > 0 ? heightOverride : itemHeight
if (!height) {
return;
return
}
updateSnapHeight(height);
const offset = clamped * height;
updateSnapHeight(height)
const offset = clamped * height
try {
if (typeof target.scrollTo === "function") {
target.scrollTo({ y: offset, animated });
} else if (typeof target.scrollToOffset === "function") {
target.scrollToOffset({ offset, animated });
if (typeof target.scrollTo === 'function') {
target.scrollTo({ y: offset, animated })
} else if (typeof target.scrollToOffset === 'function') {
target.scrollToOffset({ offset, animated })
}
} catch (_error) {
// ScrollView not ready yet, ignore.
}
activeIndexRef.current = clamped;
activeIndexRef.current = clamped
},
[clampIndex, getScrollNode, itemHeight, updateSnapHeight]
);
)
useEffect(() => {
if (
scrollViewRef.current == null ||
typeof activeIndex !== "number" ||
typeof activeIndex !== 'number' ||
activeIndex < 0 ||
activeIndex >= carouselItems.length ||
(isWeb && !itemHeight)
) {
return;
return
}
scrollToIndex(activeIndex);
}, [activeIndex, carouselItems.length, isWeb, itemHeight, scrollToIndex]);
scrollToIndex(activeIndex)
}, [activeIndex, carouselItems.length, isWeb, itemHeight, scrollToIndex])
useEffect(() => {
if (scrollViewRef.current == null || (isWeb && !itemHeight)) {
return;
return
}
scrollToIndex(activeIndexRef.current, false);
}, [carouselItems.length, isWeb, itemHeight, scrollToIndex]);
scrollToIndex(activeIndexRef.current, false)
}, [carouselItems.length, isWeb, itemHeight, scrollToIndex])
useEffect(() => {
if (!isWeb || !isFocused) {
return;
return
}
if (!itemHeight) {
return;
return
}
clearPendingAlignment();
scrollToIndex(activeIndexRef.current, false);
}, [
clearPendingAlignment,
isFocused,
isWeb,
itemHeight,
scrollToIndex,
]);
clearPendingAlignment()
scrollToIndex(activeIndexRef.current, false)
}, [clearPendingAlignment, isFocused, isWeb, itemHeight, scrollToIndex])
const alignToOffset = useCallback(
(offset, layoutHeight, options = {}) => {
const { forceSnap = false } = options || {};
const height =
layoutHeight && layoutHeight > 0 ? layoutHeight : itemHeight;
const { forceSnap = false } = options || {}
const height = layoutHeight && layoutHeight > 0 ? layoutHeight : itemHeight
if (!height) {
return;
return
}
updateSnapHeight(height);
updateSnapHeight(height)
const currentIndex = activeIndexRef.current;
const rawIndex = height ? offset / height : currentIndex;
const currentIndex = activeIndexRef.current
const rawIndex = height ? offset / height : currentIndex
let nextIndex = currentIndex;
let nextIndex = currentIndex
if (isWeb) {
const delta = rawIndex - currentIndex;
const delta = rawIndex - currentIndex
if (Math.abs(delta) > WEB_SCROLL_INACTIVE_DELTA) {
if (Math.abs(delta) <= 1) {
nextIndex = clampIndex(currentIndex + (delta > 0 ? 1 : -1));
nextIndex = clampIndex(currentIndex + (delta > 0 ? 1 : -1))
} else {
nextIndex = clampIndex(currentIndex + Math.round(delta));
nextIndex = clampIndex(currentIndex + Math.round(delta))
}
}
} else {
nextIndex = clampIndex(Math.round(rawIndex));
nextIndex = clampIndex(Math.round(rawIndex))
}
const hasChanged = nextIndex !== activeIndexRef.current;
const hasChanged = nextIndex !== activeIndexRef.current
if (hasChanged) {
activeIndexRef.current = nextIndex;
const callback = onActiveIndexChangeRef.current;
activeIndexRef.current = nextIndex
const callback = onActiveIndexChangeRef.current
if (callback) {
callback(nextIndex);
callback(nextIndex)
}
}
if (forceSnap || hasChanged) {
scrollToIndex(nextIndex, true, height);
scrollToIndex(nextIndex, true, height)
}
},
[clampIndex, isWeb, itemHeight, scrollToIndex, updateSnapHeight]
);
)
const handleScrollEnd = useCallback(
(event) => {
clearPendingAlignment();
const offsetY = event?.nativeEvent?.contentOffset?.y ?? 0;
const layoutHeight = event?.nativeEvent?.layoutMeasurement?.height;
alignToOffset(offsetY, layoutHeight, { forceSnap: true });
clearPendingAlignment()
const offsetY = event?.nativeEvent?.contentOffset?.y ?? 0
const layoutHeight = event?.nativeEvent?.layoutMeasurement?.height
alignToOffset(offsetY, layoutHeight, { forceSnap: true })
},
[alignToOffset, clearPendingAlignment]
);
)
const handleScroll = useCallback(
(event) => {
if (!isWeb) {
return;
return
}
const offsetY = event?.nativeEvent?.contentOffset?.y ?? 0;
const layoutHeight = event?.nativeEvent?.layoutMeasurement?.height;
clearPendingAlignment();
const offsetY = event?.nativeEvent?.contentOffset?.y ?? 0
const layoutHeight = event?.nativeEvent?.layoutMeasurement?.height
clearPendingAlignment()
alignTimeoutRef.current = globalThis.setTimeout(() => {
alignToOffset(offsetY, layoutHeight, { forceSnap: false });
alignTimeoutRef.current = null;
}, 80);
alignToOffset(offsetY, layoutHeight, { forceSnap: false })
alignTimeoutRef.current = null
}, 80)
},
[alignToOffset, clearPendingAlignment, isWeb]
);
)
const animatedScrollHandler = useMemo(
() =>
@@ -319,19 +301,19 @@ const FeatureCarousel = ({
listener: isWeb ? handleScroll : undefined,
}),
[handleScroll, isWeb, scrollY]
);
)
const handleLayout = useCallback(
(event) => {
const layoutHeight = event?.nativeEvent?.layout?.height ?? 0;
const layoutHeight = event?.nativeEvent?.layout?.height ?? 0
if (layoutHeight > 0) {
updateSnapHeight(layoutHeight);
updateSnapHeight(layoutHeight)
}
},
[updateSnapHeight]
);
)
const hasBackgroundImage = !!backgroundImage;
const hasBackgroundImage = !!backgroundImage
const renderContent = useMemo(
() =>
@@ -345,43 +327,35 @@ const FeatureCarousel = ({
]}
>
<View style={styles.cardWrapper}>
<PersonaCard
item={item}
index={index}
isLock={item.isLocked}
height={itemHeight}
/>
<PersonaCard item={item} index={index} isLock={item.isLocked} height={itemHeight} />
</View>
</View>
)),
[carouselItems, hasBackgroundImage, itemHeight]
);
)
const snapOffsets = useMemo(() => {
if (!itemHeight || !isWeb) {
return undefined;
return undefined
}
return carouselItems.map((_, index) => index * itemHeight);
}, [carouselItems, isWeb, itemHeight]);
return carouselItems.map((_, index) => index * itemHeight)
}, [carouselItems, isWeb, itemHeight])
const blurIntensity = isWeb ? 80 : 30;
const blurIntensity = isWeb ? 80 : 30
const dotsWrapperStyle = useMemo(
() => [
styles.dotsWrapperBase,
isWeb ? styles.dotsWrapperWeb : styles.dotsWrapperNative,
],
() => [styles.dotsWrapperBase, isWeb ? styles.dotsWrapperWeb : styles.dotsWrapperNative],
[isWeb]
);
)
const containerProps = hasBackgroundImage
? {
source: backgroundImage,
resizeMode: "cover",
resizeMode: 'cover',
imageStyle: styles.backgroundImage,
}
: {};
: {}
const ContainerComponent = hasBackgroundImage ? ImageBackground : View;
const ContainerComponent = hasBackgroundImage ? ImageBackground : View
return (
<ContainerComponent
@@ -396,11 +370,7 @@ const FeatureCarousel = ({
>
{hasBackgroundImage && (
<LinearGradient
colors={[
"rgba(66, 91, 135, 0)",
"rgba(66, 91, 135, 0.4)",
HOME_BACKGROUND_COLOR,
]}
colors={['rgba(66, 91, 135, 0)', 'rgba(66, 91, 135, 0.4)', HOME_BACKGROUND_COLOR]}
locations={[0, 0.75, 1]}
style={styles.backgroundGradient}
pointerEvents="none"
@@ -416,7 +386,7 @@ const FeatureCarousel = ({
snapToAlignment="start"
snapToInterval={!isWeb && itemHeight ? itemHeight : undefined}
snapToOffsets={snapOffsets}
decelerationRate={!isWeb ? "fast" : "normal"}
decelerationRate={!isWeb ? 'fast' : 'normal'}
style={styles.list}
contentContainerStyle={styles.scrollContent}
onScroll={animatedScrollHandler}
@@ -427,7 +397,7 @@ const FeatureCarousel = ({
</Animated.ScrollView>
<BlurView
intensity={blurIntensity}
tint={Platform.OS === "web" ? undefined : "dark"}
tint={Platform.OS === 'web' ? undefined : 'dark'}
style={dotsWrapperStyle}
pointerEvents="none"
>
@@ -444,16 +414,16 @@ const FeatureCarousel = ({
/>
</BlurView>
</ContainerComponent>
);
};
)
}
export default FeatureCarousel;
export default FeatureCarousel
const styles = StyleSheet.create({
container: {
flex: 1,
width: "100%",
flexDirection: "row",
width: '100%',
flexDirection: 'row',
},
containerWeb: {
// paddingRight: 56,
@@ -462,7 +432,7 @@ const styles = StyleSheet.create({
backgroundColor: HOME_BACKGROUND_COLOR,
},
containerTransparent: {
backgroundColor: "transparent",
backgroundColor: 'transparent',
},
list: {
flex: 1,
@@ -477,45 +447,45 @@ const styles = StyleSheet.create({
...StyleSheet.absoluteFillObject,
},
slide: {
width: "100%",
justifyContent: "flex-start",
width: '100%',
justifyContent: 'flex-start',
},
slideColored: {
backgroundColor: HOME_BACKGROUND_COLOR,
},
slideTransparent: {
backgroundColor: "transparent",
backgroundColor: 'transparent',
},
cardWrapper: {
flex: 1,
width: "60%",
justifyContent: "center",
alignSelf: "center",
width: '60%',
justifyContent: 'center',
alignSelf: 'center',
zIndex: 1,
},
dotsWrapperBase: {
justifyContent: "center",
alignItems: "center",
justifyContent: 'center',
alignItems: 'center',
borderRadius: 16,
paddingVertical: 12,
paddingHorizontal: 8,
overflow: "hidden",
backgroundColor: "rgba(18, 18, 18, 0.2)",
overflow: 'hidden',
backgroundColor: 'rgba(18, 18, 18, 0.2)',
},
dotsWrapperWeb: {
position: "absolute",
position: 'absolute',
right: 12,
top: 0,
bottom: 0,
maxHeight: "75%",
alignSelf: "center",
maxHeight: '75%',
alignSelf: 'center',
},
dotsWrapperNative: {
marginLeft: 12,
alignSelf: "center",
alignSelf: 'center',
},
dotsContainer: {
flexDirection: "column",
flexDirection: 'column',
},
dot: {
width: 8,
@@ -523,6 +493,6 @@ const styles = StyleSheet.create({
marginHorizontal: 0,
marginVertical: 6,
borderRadius: 999,
backgroundColor: "rgba(255,255,255,0.4)",
backgroundColor: 'rgba(255,255,255,0.4)',
},
});
})