import { BlurView } from "expo-blur"; import React, { useEffect, useMemo, useState } from "react"; import { FlatList, Image, Platform, StyleSheet, Text, TouchableOpacity, View, } from "react-native"; import Animated, { useAnimatedRef } from "react-native-reanimated"; import Sortable from "react-native-sortables"; import { icons } from "../../assets"; import { Palette } from "../../styles"; import { FONT_FAMILY } from "../../styles/Fonts"; import { formatStructureLabel, getSegmentMeta, OPTIONAL_STRUCTURE_SEGMENTS, sanitizeStructureList, } from "../../utils/songStructure"; import CreateLyricsHeader from "./components/CreateLyricsHeader"; const randomSuffix = () => Math.random().toString(36).slice(2, 8); const createItemId = (type) => `${type}-${Date.now()}-${randomSuffix()}`; const createSortableItem = (type, index = 0) => ({ id: `${type}-${index}-${randomSuffix()}`, type, value: type, }); const buildInitialItems = (structure = []) => sanitizeStructureList(structure).map((type, index) => createSortableItem(type, index) ); const CustomizeSongStructure = ({ baseStructure = [], onChange }) => { const scrollRef = useAnimatedRef(); const sanitizedBase = useMemo( () => sanitizeStructureList(baseStructure), [baseStructure] ); const [sortableItems, setSortableItems] = useState(() => buildInitialItems(sanitizedBase) ); useEffect(() => { setSortableItems((prev) => { const prevTypes = prev.map((item) => item.type); if ( prevTypes.length === sanitizedBase.length && prevTypes.every((type, index) => type === sanitizedBase[index]) ) { return prev; } return buildInitialItems(sanitizedBase); }); }, [sanitizedBase]); useEffect(() => { const values = sortableItems.map((item) => item.type).filter(Boolean); onChange?.(values); }, [sortableItems, onChange]); const labeledItems = useMemo(() => { const counts = {}; return sortableItems.map((item) => { const baseType = item.type || item.value; const nextCount = (counts[baseType] || 0) + 1; counts[baseType] = nextCount; return { ...item, type: baseType, value: baseType, label: formatStructureLabel(baseType, nextCount), sortable: true, }; }); }, [sortableItems]); const optionalSegments = useMemo( () => OPTIONAL_STRUCTURE_SEGMENTS.map((type) => { const meta = getSegmentMeta(type); return { type, label: meta?.label || formatStructureLabel(type, 1) }; }), [] ); const selectedTypes = useMemo(() => { const set = new Set(); sortableItems.forEach((item) => { if (item?.type) set.add(item.type); else if (item?.value) set.add(item.value); }); return set; }, [sortableItems]); const toggleOptionalSegment = (type) => { setSortableItems((prev) => { const existsIndex = prev.findIndex((item) => item.type === type); if (existsIndex !== -1) { return prev.filter((item) => item.type !== type); } const meta = getSegmentMeta(type); let next = [...prev]; if (meta?.exclusiveGroup) { next = next.filter((item) => { const itemMeta = getSegmentMeta(item.type || item.value); return itemMeta?.exclusiveGroup !== meta.exclusiveGroup; }); } next.push(createSortableItem(type, next.length)); return next; }); }; const Row = ({ item: rowData, onPress }) => { const isButton = typeof onPress === "function"; const Wrapper = isButton ? TouchableOpacity : View; const selected = rowData?.selected; const icon = rowData?.icon; return ( {rowData?.label || ""} {rowData?.sortable ? ( ) : ( )} ); }; return ( <> } customHandle scrollableRef={scrollRef} onDragEnd={({ data: newData }) => { const next = (newData || []).map((it, index) => { const nextType = it?.type || it?.value; return { id: it?.id || createItemId(nextType || `section-${index}`), type: nextType, value: nextType, }; }); setSortableItems(next.filter((item) => item.type)); }} /> { const selected = selectedTypes.has(item.type); return ( toggleOptionalSegment(item.type)} /> ); }} keyExtractor={(item) => item.type} contentContainerStyle={{ paddingBottom: 24 }} scrollEnabled={false} nestedScrollEnabled={false} /> ); }; export default CustomizeSongStructure; const styles = StyleSheet.create({ touchable: { width: "100%", }, 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, }, sortableItem: { marginBottom: 0, }, selectedItem: { borderWidth: 1, borderColor: Palette.white, }, rowBlur: { flex: 1, backgroundColor: Palette.glass, justifyContent: "center", paddingHorizontal: 12, flexDirection: "row", alignItems: "center", height: "100%", }, buttonBlur: { justifyContent: "space-between", }, selectedBlur: { backgroundColor: Palette.transparentWhite, }, rowContent: { flex: 1, justifyContent: "center", }, rowText: { fontSize: 16, color: Palette.white, fontFamily: FONT_FAMILY.InterRegular, }, handleIcon: { width: 22, height: 22, tintColor: Palette.white, }, optionIcon: { width: 20, height: 20, tintColor: Palette.white, }, });