Files
musicland/src/screens/Writing/CustomizeSongStructure.js
T
Philip Cesar Garay f007b1f213 Fix Task: #92
2025-09-09 18:16:30 +08:00

147 lines
4.4 KiB
JavaScript

import { View, StyleSheet, Image, Platform, Text } from "react-native";
import React, { useMemo, useState, useCallback, useEffect } from "react";
import Animated, { useAnimatedRef } from "react-native-reanimated";
import { BlurView } from "expo-blur";
import CreateLyricsHeader from "./components/CreateLyricsHeader";
import { Palette, Style } from "../../styles";
import { FONT_FAMILY } from "../../styles/Fonts";
import Sortable from "react-native-sortables";
import { icons } from "../../assets";
// baseStructure: array like ['couplet','refrain',...]
// onChange: callback that receives array like ['couplet','refrain',...]
const CustomizeSongStructure = ({ baseStructure = [], onChange }) => {
const [containerLayout, setContainerLayout] = useState(null);
const scrollRef = useAnimatedRef();
// Build stable keyed data like { 'couplet-1': { label, value }, ... }
const data = useMemo(() => {
const counters = { couplet: 0, refrain: 0 };
const out = {};
(baseStructure || []).forEach((type) => {
const key = (type || "").toLowerCase();
counters[key] = (counters[key] || 0) + 1;
const idx = counters[key];
const id = `${key}-${idx}`;
out[id] = {
id,
label: `${key === "couplet" ? "Couplet" : "Refrain"} ${idx}`,
value: key,
};
});
return out;
}, [baseStructure]);
const items = useMemo(() => Object.values(data), [data]);
// Initialize parent with current order (so it's saved even without drag)
useEffect(() => {
const initialValues = (items || []).map((it) => it?.value).filter(Boolean);
onChange?.(initialValues);
// We only want to run when baseStructure-derived items change
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [items.length]);
const Row = ({ item: rowData }) => (
<View style={styles.itemContainer}>
<BlurView
intensity={Platform.OS !== "ios" ? 10 : 30}
style={styles.rowBlur}
// experimentalBlurMethod={Platform.OS !== "ios" ? "dimezisBlurView" : "none"}
>
<View style={{ ...Style.containerSpaceBetween, alignItems: "center" }}>
<View>
<View>
<View>
<View />
</View>
</View>
<View>
<View />
</View>
</View>
<View style={{ flex: 1, justifyContent: "center" }}>
<View>
<Text style={styles.rowText}>{rowData?.label || ""}</Text>
</View>
</View>
<Sortable.Handle>
<Image source={icons.dragDots} style={styles.handleIcon} />
</Sortable.Handle>
</View>
</BlurView>
</View>
);
return (
<View style={{ flex: 1, gap: 10, marginTop: 16 }}>
<CreateLyricsHeader
title="Personnalise la structure de ta chanson"
subTitle="Réorganise par glisser-déposer"
/>
<View
style={{ flex: 1 }}
onLayout={(e) => setContainerLayout(e.nativeEvent.layout)}
>
<Animated.ScrollView
ref={scrollRef}
contentContainerStyle={{ paddingVertical: 4, paddingBottom: 24 }}
showsVerticalScrollIndicator={false}
>
<Sortable.Grid
columns={1}
rowGap={12}
data={items}
renderItem={({ item }) => <Row item={item} />}
customHandle
// Enable auto-scroll when dragging near edges
scrollableRef={scrollRef}
onDragEnd={({ data: newData }) => {
const values = (newData || [])
.map((it) => it?.value)
.filter(Boolean);
onChange?.(values);
}}
/>
</Animated.ScrollView>
</View>
</View>
);
};
export default CustomizeSongStructure;
const styles = StyleSheet.create({
itemContainer: {
height: 54,
backgroundColor: Palette.glass,
borderRadius: 14,
overflow: "hidden",
shadowColor: "#00000040",
shadowOffset: {
width: 0,
height: 2,
},
shadowOpacity: 0.25,
shadowRadius: 3.84,
elevation: 5,
},
rowBlur: {
flex: 1,
backgroundColor: Palette.glass,
justifyContent: "center",
paddingHorizontal: 12,
height: "100%",
},
rowText: {
fontSize: 16,
color: Palette.white,
fontFamily: FONT_FAMILY.InterRegular,
},
handleIcon: {
width: 22,
height: 22,
tintColor: Palette.white,
},
});