fix list selection and other tickets
This commit is contained in:
@@ -0,0 +1,262 @@
|
||||
import React, { useMemo, useState } from "react";
|
||||
import { FlatList, SectionList, Text, View, Platform } from "react-native";
|
||||
import { BlurView } from "expo-blur";
|
||||
import CreateLyricsHeader from "../../screens/Writing/components/CreateLyricsHeader";
|
||||
import { Palette } from "../../styles";
|
||||
import { FONT_FAMILY } from "../../styles/Fonts";
|
||||
|
||||
// Reusable selection list supporting single, multiple and sectioned data.
|
||||
// Variants supported:
|
||||
// - "simple": options are strings
|
||||
// - "titleDescription": options are { title, description }
|
||||
// - "emotion": options are { title, description, color } (uses gradient colors)
|
||||
// - "sectioned": options are sections [{ title, data: [] }] (single per section)
|
||||
const ListSelection = ({
|
||||
options = [],
|
||||
variant = "simple",
|
||||
selected: selectedProp,
|
||||
setSelected: setSelectedProp,
|
||||
multiple = false,
|
||||
maxSelection,
|
||||
getItemValue,
|
||||
selectedValueExtractor,
|
||||
formatSelectedValue,
|
||||
// styles
|
||||
contentContainerStyle,
|
||||
itemContainerStyle,
|
||||
itemOuterStyle,
|
||||
itemTextStyle,
|
||||
highlightColor = "#F94697",
|
||||
}) => {
|
||||
// Internal fallback state when not provided by parent
|
||||
const [internalSelected, setInternalSelected] = useState(
|
||||
variant === "sectioned" ? {} : multiple ? [] : null,
|
||||
);
|
||||
const selected = selectedProp !== undefined ? selectedProp : internalSelected;
|
||||
const setSelected =
|
||||
setSelectedProp !== undefined ? setSelectedProp : setInternalSelected;
|
||||
|
||||
const valueOf = useMemo(() => {
|
||||
if (typeof getItemValue === "function") return getItemValue;
|
||||
if (variant === "simple") return (item) => item;
|
||||
return (item) => item?.title ?? item; // default to title when present
|
||||
}, [getItemValue, variant]);
|
||||
|
||||
const formatValue = (item) =>
|
||||
typeof formatSelectedValue === "function"
|
||||
? formatSelectedValue(item)
|
||||
: valueOf(item);
|
||||
|
||||
const extractSelectedComparable = (s) => {
|
||||
if (typeof selectedValueExtractor === "function")
|
||||
return selectedValueExtractor(s);
|
||||
if (s && typeof s === "object" && "title" in s) return s.title;
|
||||
return s;
|
||||
};
|
||||
|
||||
const isSelected = (val, category) => {
|
||||
if (variant === "sectioned") {
|
||||
return selected?.[category] === val;
|
||||
}
|
||||
if (multiple) {
|
||||
const list = Array.isArray(selected) ? selected : [];
|
||||
return list.some((v) => v === val);
|
||||
}
|
||||
return extractSelectedComparable(selected) === val;
|
||||
};
|
||||
|
||||
const toggleSelect = (item, category) => {
|
||||
const val = valueOf(item);
|
||||
if (variant === "sectioned") {
|
||||
const current = selected && typeof selected === "object" ? selected : {};
|
||||
const next = { ...current };
|
||||
if (current?.[category] === val) next[category] = null;
|
||||
else next[category] = formatValue(item);
|
||||
setSelected(next);
|
||||
return;
|
||||
}
|
||||
|
||||
if (multiple) {
|
||||
const list = Array.isArray(selected) ? selected : [];
|
||||
const exists = list.some((v) => v === val);
|
||||
if (exists) {
|
||||
setSelected(list.filter((v) => v !== val));
|
||||
} else {
|
||||
if (!maxSelection || list.length < maxSelection) {
|
||||
setSelected([...list, formatValue(item)]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (selected === val) setSelected(null);
|
||||
else setSelected(formatValue(item));
|
||||
}
|
||||
};
|
||||
|
||||
const renderSimpleContent = (label) => (
|
||||
<View style={{ minHeight: 40, justifyContent: "center" }}>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 16,
|
||||
color: Palette.white,
|
||||
fontFamily: FONT_FAMILY.InterRegular,
|
||||
textAlign: "left",
|
||||
...itemTextStyle,
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
const renderTitleDescriptionContent = (item) => (
|
||||
<View style={{ paddingVertical: 6 }}>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 16,
|
||||
color: Palette.white,
|
||||
fontFamily: FONT_FAMILY.InterRegular,
|
||||
...itemTextStyle,
|
||||
}}
|
||||
>
|
||||
<Text style={{ fontFamily: FONT_FAMILY.InterBold }}>{item?.title}</Text>{" "}
|
||||
: {item?.description}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
if (variant === "sectioned") {
|
||||
// Sectioned list: options as [{ title, data: [] }]
|
||||
return (
|
||||
<SectionList
|
||||
sections={options}
|
||||
showsVerticalScrollIndicator={false}
|
||||
contentContainerStyle={contentContainerStyle}
|
||||
renderItem={({ item, section }) => {
|
||||
const cat = section?.title;
|
||||
const val = valueOf(item);
|
||||
const sel = isSelected(val, cat);
|
||||
return (
|
||||
<View style={[{ position: "relative" }, itemOuterStyle]}>
|
||||
<CreateLyricsHeader
|
||||
onPress={() => toggleSelect(item, cat)}
|
||||
tint={sel ? "default" : "dark"}
|
||||
colors={[Palette.tran, Palette.tran]}
|
||||
containerStyle={{
|
||||
borderWidth: 0,
|
||||
borderColor: "transparent",
|
||||
borderRadius: 14,
|
||||
...itemContainerStyle,
|
||||
}}
|
||||
>
|
||||
{renderSimpleContent(item)}
|
||||
</CreateLyricsHeader>
|
||||
{sel && (
|
||||
<View
|
||||
pointerEvents="none"
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
borderWidth: 2,
|
||||
borderRadius: 14,
|
||||
borderColor: highlightColor,
|
||||
zIndex: 2,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}}
|
||||
renderSectionHeader={({ section: { title } }) => (
|
||||
<View style={{ alignSelf: "flex-start", marginLeft: 10, marginBottom: 6 }}>
|
||||
<BlurView
|
||||
intensity={40}
|
||||
tint="dark"
|
||||
experimentalBlurMethod={Platform.OS !== "ios" ? "dimezisBlurView" : "none"}
|
||||
style={{
|
||||
borderRadius: 18,
|
||||
overflow: "hidden",
|
||||
paddingHorizontal: 8,
|
||||
paddingVertical: 4,
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 16,
|
||||
color: Palette.white,
|
||||
fontFamily: FONT_FAMILY.InterBold,
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
</Text>
|
||||
</BlurView>
|
||||
</View>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<FlatList
|
||||
data={options}
|
||||
showsVerticalScrollIndicator={false}
|
||||
contentContainerStyle={contentContainerStyle}
|
||||
renderItem={({ item }) => {
|
||||
const val = valueOf(item);
|
||||
const sel = isSelected(val);
|
||||
const useEmotionColors = variant === "emotion";
|
||||
// For emotion variant, hide the left color when selected
|
||||
const borderColors = useEmotionColors
|
||||
? sel
|
||||
? [Palette.tran, Palette.tran]
|
||||
: item?.color
|
||||
: [Palette.tran, Palette.tran];
|
||||
const tint = useEmotionColors ? "dark" : sel ? "default" : "dark";
|
||||
|
||||
return (
|
||||
<View style={[{ position: "relative" }, itemOuterStyle]}>
|
||||
<CreateLyricsHeader
|
||||
colors={borderColors}
|
||||
tint={tint}
|
||||
onPress={() => toggleSelect(item)}
|
||||
gradientProps={
|
||||
useEmotionColors && !sel ? { locations: [0.24, 1] } : undefined
|
||||
}
|
||||
containerStyle={{
|
||||
borderWidth: 0,
|
||||
borderColor: "transparent",
|
||||
borderRadius: 14,
|
||||
...itemContainerStyle,
|
||||
}}
|
||||
>
|
||||
{variant === "simple"
|
||||
? renderSimpleContent(item)
|
||||
: renderTitleDescriptionContent(item)}
|
||||
</CreateLyricsHeader>
|
||||
{sel && (
|
||||
<View
|
||||
pointerEvents="none"
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
borderWidth: 2,
|
||||
borderRadius: 14,
|
||||
borderColor: highlightColor,
|
||||
zIndex: 2,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}}
|
||||
keyExtractor={(item, idx) => `${valueOf(item)}-${idx}`}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default ListSelection;
|
||||
@@ -31,9 +31,13 @@ import { FONT_FAMILY } from "../../styles/Fonts";
|
||||
import { size } from "../../styles/Style";
|
||||
|
||||
let externalOpen;
|
||||
let externalClose;
|
||||
export const openComments = (projectId) => {
|
||||
if (typeof externalOpen === "function") externalOpen(projectId);
|
||||
};
|
||||
export const closeComments = () => {
|
||||
if (typeof externalClose === "function") externalClose();
|
||||
};
|
||||
|
||||
const formatRelativeTime = (date) => {
|
||||
try {
|
||||
@@ -67,8 +71,14 @@ const CommentsBottomSheet = () => {
|
||||
setProjectId(pid || null);
|
||||
requestAnimationFrame(() => modalRef.current?.present());
|
||||
};
|
||||
externalClose = () => {
|
||||
try {
|
||||
modalRef.current?.dismiss?.();
|
||||
} catch {}
|
||||
};
|
||||
return () => {
|
||||
externalOpen = undefined;
|
||||
externalClose = undefined;
|
||||
};
|
||||
}, []);
|
||||
|
||||
@@ -141,10 +151,10 @@ const CommentsBottomSheet = () => {
|
||||
{...props}
|
||||
appearsOnIndex={0}
|
||||
disappearsOnIndex={-1}
|
||||
pressBehavior={typing ? "none" : "close"}
|
||||
pressBehavior="close"
|
||||
/>
|
||||
),
|
||||
[typing]
|
||||
[]
|
||||
);
|
||||
|
||||
const handleScroll = useCallback(
|
||||
@@ -170,14 +180,19 @@ const CommentsBottomSheet = () => {
|
||||
snapPoints={snapPoints}
|
||||
keyboardBehavior="interactive" // l’input est dans le contenu → mode interactif OK
|
||||
keyboardBlurBehavior="restore"
|
||||
enablePanDownToClose={!typing} // évite un dismiss pendant la frappe
|
||||
enableContentPanningGesture={!typing}
|
||||
enablePanDownToClose
|
||||
enableContentPanningGesture
|
||||
stackBehavior="push"
|
||||
topInset={insets.top}
|
||||
bottomInset={insets.bottom}
|
||||
backdropComponent={renderBackdrop}
|
||||
handleIndicatorStyle={{ backgroundColor: Palette.white }}
|
||||
backgroundStyle={{ backgroundColor: "transparent" }}
|
||||
onDismiss={() => {
|
||||
setProjectId(null);
|
||||
setTyping(false);
|
||||
setText("");
|
||||
}}
|
||||
>
|
||||
<BlurView
|
||||
intensity={20}
|
||||
@@ -192,6 +207,17 @@ const CommentsBottomSheet = () => {
|
||||
}
|
||||
>
|
||||
<View style={{ paddingTop: 10, paddingBottom: 8 }}>
|
||||
<Pressable
|
||||
onPress={() => modalRef.current?.dismiss?.()}
|
||||
style={{ position: "absolute", right: 10, top: 8, padding: 6 }}
|
||||
hitSlop={8}
|
||||
>
|
||||
<Image
|
||||
source={icons.close}
|
||||
style={{ width: 22, height: 22, tintColor: Palette.white }}
|
||||
resizeMode="contain"
|
||||
/>
|
||||
</Pressable>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 22,
|
||||
|
||||
@@ -95,6 +95,7 @@ export const BottomTabScreen = () => {
|
||||
<BottomTab.Screen
|
||||
name={Routes.Profile}
|
||||
component={Profile}
|
||||
initialParams={{ noBack: true }}
|
||||
options={{
|
||||
tabBarLabel: "Profil",
|
||||
headerShown: false,
|
||||
|
||||
@@ -8,7 +8,7 @@ import React, {
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { Text, View } from "react-native";
|
||||
import { Alert, Text, View } from "react-native";
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
import Svg, { Circle } from "react-native-svg";
|
||||
import GradientButton from "../../components/GradientButton";
|
||||
@@ -355,6 +355,25 @@ const RecordPlayback = ({ route }) => {
|
||||
clearInterval(checkSongEndRef.current);
|
||||
checkSongEndRef.current = null;
|
||||
}
|
||||
// Inform the user when using a simulator where recording isn't supported
|
||||
const msg = String(e?.message || e || "");
|
||||
if (/not supported on the simulator/i.test(msg)) {
|
||||
Alert.alert(
|
||||
"Indisponible sur simulateur",
|
||||
"L’enregistrement vidéo n’est pas disponible sur le simulateur. Merci d’utiliser un appareil réel.",
|
||||
[
|
||||
{
|
||||
text: "OK",
|
||||
onPress: () => {
|
||||
try {
|
||||
goBack();
|
||||
} catch (_) {}
|
||||
},
|
||||
},
|
||||
],
|
||||
{ cancelable: false }
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ const Profile = () => {
|
||||
setFollowers(
|
||||
Array.isArray(currentUserData?.followedBy)
|
||||
? currentUserData.followedBy.length
|
||||
: 0
|
||||
: 0,
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -230,7 +230,7 @@ const Profile = () => {
|
||||
<Page
|
||||
backgroundImg={background.profileBG}
|
||||
headerType="NAVIGATE"
|
||||
hideBackButton={isSelf}
|
||||
hideBackButton={params?.noBack}
|
||||
contentContainerStyle={{
|
||||
paddingBottom: gutters * 2,
|
||||
}}
|
||||
|
||||
@@ -1,26 +1,14 @@
|
||||
import { View, Text, FlatList } from "react-native";
|
||||
import { View } from "react-native";
|
||||
import React, { useState } from "react";
|
||||
import CreateLyricsHeader from "../Writing/components/CreateLyricsHeader";
|
||||
import { FONT_FAMILY } from "../../styles/Fonts";
|
||||
import { Palette } from "../../styles";
|
||||
import { CHOOSE_GENRE } from "../../data/data";
|
||||
import ItemContainer from "../../components/ItemContainer/ItemContainer";
|
||||
import _ from "lodash";
|
||||
import ListSelection from "../../components/ListSelection/ListSelection";
|
||||
|
||||
const ChooseGenre = ({ selected = [], setSelected }) => {
|
||||
const [containerLayout, setContainerLayout] = useState(null);
|
||||
|
||||
const onPressSelect = (item) => {
|
||||
if (!setSelected) return;
|
||||
const value = item?.title;
|
||||
const list = _.isArray(selected) ? selected : [];
|
||||
const exists = _.includes(list, value);
|
||||
if (exists) {
|
||||
setSelected(_.filter(list, (v) => !_.isEqual(v, value)));
|
||||
} else if (_.size(list) < 2) {
|
||||
setSelected([...list, value]);
|
||||
}
|
||||
};
|
||||
// Selection handled by ListSelection
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1, gap: 10, paddingTop: 16 }}>
|
||||
@@ -33,45 +21,21 @@ const ChooseGenre = ({ selected = [], setSelected }) => {
|
||||
onLayout={(event) => setContainerLayout(event.nativeEvent.layout)}
|
||||
>
|
||||
<ItemContainer height={containerLayout?.height}>
|
||||
<FlatList
|
||||
data={CHOOSE_GENRE}
|
||||
<ListSelection
|
||||
options={CHOOSE_GENRE}
|
||||
variant="titleDescription"
|
||||
selected={selected}
|
||||
setSelected={setSelected}
|
||||
multiple
|
||||
maxSelection={2}
|
||||
getItemValue={(item) => item?.title}
|
||||
contentContainerStyle={{
|
||||
gap: 16,
|
||||
flexGrow: 1,
|
||||
zIndex: 2,
|
||||
paddingTop: 5,
|
||||
}}
|
||||
renderItem={({ item, index }) => {
|
||||
const list = Array.isArray(selected) ? selected : [];
|
||||
const selectedItem = _.includes(list, item.title);
|
||||
|
||||
return (
|
||||
<View style={{ paddingHorizontal: 5 }}>
|
||||
<CreateLyricsHeader
|
||||
colors={[Palette.tran, Palette.tran]}
|
||||
tint={selectedItem ? "default" : "dark"}
|
||||
onPress={() => onPressSelect(item)}
|
||||
containerStyle={{
|
||||
borderWidth: selectedItem ? 2 : 0,
|
||||
borderColor: selectedItem ? "#F94697" : "transparent",
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 16,
|
||||
color: Palette.white,
|
||||
fontFamily: FONT_FAMILY.InterRegular,
|
||||
}}
|
||||
>
|
||||
<Text style={{ fontFamily: FONT_FAMILY.InterBold }}>
|
||||
{item.title}
|
||||
</Text>{" "}
|
||||
: {item.description}
|
||||
</Text>
|
||||
</CreateLyricsHeader>
|
||||
</View>
|
||||
);
|
||||
}}
|
||||
itemOuterStyle={{ paddingHorizontal: 5 }}
|
||||
/>
|
||||
</ItemContainer>
|
||||
</View>
|
||||
|
||||
@@ -1,25 +1,16 @@
|
||||
import { View, Text, FlatList, StyleSheet } from "react-native";
|
||||
import { View, StyleSheet } from "react-native";
|
||||
import React, { useState } from "react";
|
||||
import CreateLyricsHeader from "../Writing/components/CreateLyricsHeader";
|
||||
import ItemContainer from "../../components/ItemContainer/ItemContainer";
|
||||
import { Palette } from "../../styles";
|
||||
import { FONT_FAMILY } from "../../styles/Fonts";
|
||||
import { BlurView } from "expo-blur";
|
||||
import { INSTRUMENTS } from "../../data/data";
|
||||
import ListSelection from "../../components/ListSelection/ListSelection";
|
||||
|
||||
const ChooseInstruments = ({ selected = [], setSelected }) => {
|
||||
const [containerLayout, setContainerLayout] = useState(null);
|
||||
|
||||
const onPressSelect = (item) => {
|
||||
if (!setSelected) return;
|
||||
const list = Array.isArray(selected) ? selected : [];
|
||||
const exists = list.includes(item);
|
||||
if (exists) {
|
||||
setSelected(list.filter((v) => v !== item));
|
||||
} else if (list.length < 5) {
|
||||
setSelected([...list, item]);
|
||||
}
|
||||
};
|
||||
// Selection handled by ListSelection
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1, gap: 10, paddingTop: 16 }}>
|
||||
@@ -32,36 +23,16 @@ const ChooseInstruments = ({ selected = [], setSelected }) => {
|
||||
onLayout={(event) => setContainerLayout(event.nativeEvent.layout)}
|
||||
>
|
||||
<ItemContainer height={containerLayout?.height}>
|
||||
<FlatList
|
||||
data={INSTRUMENTS}
|
||||
showsVerticalScrollIndicator={false}
|
||||
<ListSelection
|
||||
options={INSTRUMENTS}
|
||||
variant="simple"
|
||||
selected={selected}
|
||||
setSelected={setSelected}
|
||||
multiple
|
||||
maxSelection={5}
|
||||
contentContainerStyle={styles.contentContainer}
|
||||
renderItem={({ item }) => {
|
||||
const list = Array.isArray(selected) ? selected : [];
|
||||
const selectedItem = list.includes(item);
|
||||
|
||||
return (
|
||||
<CreateLyricsHeader
|
||||
onPress={() => onPressSelect(item)}
|
||||
tint={selectedItem ? "default" : "dark"}
|
||||
colors={[Palette.tran, Palette.tran]}
|
||||
containerStyle={{
|
||||
...styles.itemContainer,
|
||||
borderWidth: selectedItem ? 2 : 0,
|
||||
borderColor: selectedItem ? "#F94697" : "transparent",
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
height: 40,
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<Text style={styles.itemText}>{item}</Text>
|
||||
</View>
|
||||
</CreateLyricsHeader>
|
||||
);
|
||||
}}
|
||||
itemContainerStyle={styles.itemContainer}
|
||||
itemTextStyle={styles.itemText}
|
||||
/>
|
||||
</ItemContainer>
|
||||
</View>
|
||||
|
||||
@@ -1,55 +1,27 @@
|
||||
import { View, Text, FlatList, StyleSheet } from "react-native";
|
||||
import React, { useState } from "react";
|
||||
import { View, StyleSheet } from "react-native";
|
||||
import React from "react";
|
||||
import CreateLyricsHeader from "../Writing/components/CreateLyricsHeader";
|
||||
import ItemContainer from "../../components/ItemContainer/ItemContainer";
|
||||
import { BlurView } from "expo-blur";
|
||||
import { Palette } from "../../styles";
|
||||
import { FONT_FAMILY } from "../../styles/Fonts";
|
||||
import { RHYTHM } from "../../data/data";
|
||||
import ListSelection from "../../components/ListSelection/ListSelection";
|
||||
|
||||
const ChooseRhythm = ({ selected, setSelected }) => {
|
||||
|
||||
const onPressSelect = (item) => {
|
||||
if (!setSelected) return;
|
||||
if (selected === item) setSelected(null);
|
||||
else setSelected(item);
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1, gap: 10, paddingTop: 16 }}>
|
||||
<CreateLyricsHeader title="Choisis un rythme" />
|
||||
<View style={{ flex: 1 }}>
|
||||
<ItemContainer height={340}>
|
||||
<FlatList
|
||||
data={RHYTHM}
|
||||
showsVerticalScrollIndicator={false}
|
||||
<ListSelection
|
||||
options={RHYTHM}
|
||||
variant="simple"
|
||||
selected={selected}
|
||||
setSelected={setSelected}
|
||||
contentContainerStyle={styles.contentContainer}
|
||||
renderItem={({ item }) => {
|
||||
const selectedItem = selected === item;
|
||||
|
||||
return (
|
||||
<CreateLyricsHeader
|
||||
onPress={() => onPressSelect(item)}
|
||||
tint={selectedItem ? "default" : "dark"}
|
||||
// Keep transparent colors; highlight selection with border only
|
||||
colors={[Palette.tran, Palette.tran]}
|
||||
containerStyle={{
|
||||
...styles.itemContainer,
|
||||
borderWidth: selectedItem ? 2 : 0,
|
||||
borderColor: selectedItem ? "#F94697" : "transparent",
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
height: 40,
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<Text style={styles.itemText}>{item}</Text>
|
||||
</View>
|
||||
</CreateLyricsHeader>
|
||||
);
|
||||
}}
|
||||
itemContainerStyle={styles.itemContainer}
|
||||
itemTextStyle={styles.itemText}
|
||||
/>
|
||||
</ItemContainer>
|
||||
</View>
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { View, Text, FlatList, StyleSheet, SectionList } from "react-native";
|
||||
import { View, StyleSheet } from "react-native";
|
||||
import React, { useState } from "react";
|
||||
import CreateLyricsHeader from "../Writing/components/CreateLyricsHeader";
|
||||
import ItemContainer from "../../components/ItemContainer/ItemContainer";
|
||||
import { Palette, Style } from "../../styles";
|
||||
import { FONT_FAMILY } from "../../styles/Fonts";
|
||||
import { BlurView } from "expo-blur";
|
||||
import { Palette } from "../../styles";
|
||||
import { VOICE } from "../../data/data";
|
||||
import ListSelection from "../../components/ListSelection/ListSelection";
|
||||
|
||||
const CustomizeVoice = ({ selected = {}, setSelected }) => {
|
||||
const [containerLayout, setContainerLayout] = useState(null);
|
||||
@@ -33,49 +32,13 @@ const CustomizeVoice = ({ selected = {}, setSelected }) => {
|
||||
>
|
||||
<ItemContainer height={containerLayout?.height}>
|
||||
<View style={{ flex: 1, gap: 10 }}>
|
||||
<SectionList
|
||||
sections={VOICE}
|
||||
showsVerticalScrollIndicator={false}
|
||||
<ListSelection
|
||||
options={VOICE}
|
||||
variant="sectioned"
|
||||
selected={selected}
|
||||
setSelected={setSelected}
|
||||
contentContainerStyle={styles.contentContainer}
|
||||
renderItem={({ item, section }) => {
|
||||
const cat = section?.title;
|
||||
const selectedItem = selected?.[cat] === item;
|
||||
|
||||
return (
|
||||
<CreateLyricsHeader
|
||||
onPress={() => onPressSelect(cat, item)}
|
||||
tint={selectedItem ? "default" : "dark"}
|
||||
// Ne pas changer le blur; garder fond transparent comme ChooseInstruments
|
||||
colors={[Palette.tran, Palette.tran]}
|
||||
containerStyle={{
|
||||
...styles.itemContainer,
|
||||
borderWidth: selectedItem ? 2 : 0,
|
||||
borderColor: selectedItem ? "#F94697" : "transparent",
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
height: 40,
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<Text style={styles.itemText}>{item}</Text>
|
||||
</View>
|
||||
</CreateLyricsHeader>
|
||||
);
|
||||
}}
|
||||
renderSectionHeader={({ section: { title } }) => (
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 16,
|
||||
color: Palette.white,
|
||||
fontFamily: FONT_FAMILY.InterBold,
|
||||
left: 10,
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
</Text>
|
||||
)}
|
||||
itemContainerStyle={styles.itemContainer}
|
||||
/>
|
||||
</View>
|
||||
</ItemContainer>
|
||||
@@ -109,9 +72,5 @@ const styles = StyleSheet.create({
|
||||
|
||||
elevation: 5,
|
||||
},
|
||||
itemText: {
|
||||
fontSize: 16,
|
||||
color: Palette.white,
|
||||
fontFamily: FONT_FAMILY.InterRegular,
|
||||
},
|
||||
// itemText was used in legacy implementation; kept here for consistency if needed later
|
||||
});
|
||||
|
||||
@@ -159,6 +159,45 @@ const CreateLyricsWithAi = () => {
|
||||
customStructure,
|
||||
]);
|
||||
|
||||
const isNextDisabled = React.useMemo(() => {
|
||||
switch (selectedIndex) {
|
||||
case 0: {
|
||||
const hasObjective = typeof objective === "string" && objective.length > 0;
|
||||
const hasOther = typeof otherObjective === "string" && otherObjective.trim().length > 0;
|
||||
return !(hasObjective || hasOther);
|
||||
}
|
||||
case 1: {
|
||||
const hasContext = typeof context === "string" && context.trim().length > 0;
|
||||
return !hasContext;
|
||||
}
|
||||
case 2: {
|
||||
const hasEmotion =
|
||||
(emotion && typeof emotion === "object" && !!emotion.title) ||
|
||||
(typeof emotion === "string" && emotion.trim().length > 0);
|
||||
return !hasEmotion;
|
||||
}
|
||||
case 3: {
|
||||
const hasStyle = typeof style === "string" && style.length > 0;
|
||||
const hasOther = typeof otherStyle === "string" && otherStyle.trim().length > 0;
|
||||
return !(hasStyle || hasOther);
|
||||
}
|
||||
case 4: {
|
||||
const hasAudience = typeof audience === "string" && audience.trim().length > 0;
|
||||
return !hasAudience;
|
||||
}
|
||||
case 5: {
|
||||
const hasStructure = typeof structure === "string" && structure.length > 0;
|
||||
return !hasStructure;
|
||||
}
|
||||
case 7: {
|
||||
const hasRhymes = typeof rhymes === "string" && rhymes.length > 0;
|
||||
return !hasRhymes;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}, [selectedIndex, objective, otherObjective, context, emotion, style, otherStyle, audience, structure, rhymes]);
|
||||
|
||||
const onPressNext = async () => {
|
||||
// Si l'utilisateur a déjà des paroles et vient de finir CustomizeSongStructure (index 6),
|
||||
// sauter Rhymes et CreatingLyrics et aller directement sur Lyrics
|
||||
@@ -362,6 +401,7 @@ const CreateLyricsWithAi = () => {
|
||||
{selectedIndex !== 8 && (
|
||||
<GradientButton
|
||||
title={selectedIndex === 7 ? "Générer" : "Suivant"}
|
||||
disabled={isNextDisabled}
|
||||
onPress={onPressNext}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
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";
|
||||
@@ -11,6 +12,7 @@ import { icons } from "../../assets";
|
||||
// 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(() => {
|
||||
@@ -78,17 +80,25 @@ const CustomizeSongStructure = ({ baseStructure = [], onChange }) => {
|
||||
subTitle="Réorganise par glisser-déposer"
|
||||
/>
|
||||
<View style={{ flex: 1 }} onLayout={(e) => setContainerLayout(e.nativeEvent.layout)}>
|
||||
<Sortable.Grid
|
||||
columns={1}
|
||||
rowGap={12}
|
||||
data={items}
|
||||
renderItem={({ item }) => <Row item={item} />}
|
||||
customHandle
|
||||
onDragEnd={({ data: newData }) => {
|
||||
const values = (newData || []).map((it) => it?.value).filter(Boolean);
|
||||
onChange?.(values);
|
||||
}}
|
||||
/>
|
||||
<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>
|
||||
);
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { View, Text, FlatList } from "react-native";
|
||||
import { View } from "react-native";
|
||||
import React, { useState } from "react";
|
||||
import CreateLyricsHeader from "./components/CreateLyricsHeader";
|
||||
import { Palette } from "../../styles";
|
||||
import { FONT_FAMILY } from "../../styles/Fonts";
|
||||
import { EMOTION_CONVEY } from "../../data/data";
|
||||
import ItemContainer from "../../components/ItemContainer/ItemContainer";
|
||||
import ListSelection from "../../components/ListSelection/ListSelection";
|
||||
|
||||
const EmotionConvey = ({
|
||||
selected: selectedProp,
|
||||
@@ -15,14 +14,7 @@ const EmotionConvey = ({
|
||||
const selected = selectedProp ?? internalSelected;
|
||||
const setSelected = setSelectedProp ?? setInternalSelected;
|
||||
|
||||
const onPressSelect = (item) => {
|
||||
// item is full object { title, description, color }
|
||||
if (selected?.title === item.title) {
|
||||
setSelected(null);
|
||||
} else {
|
||||
setSelected({ title: item.title, description: item.description });
|
||||
}
|
||||
};
|
||||
// Selection handled by ListSelection
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1, gap: 10, marginTop: 16 }}>
|
||||
@@ -36,45 +28,25 @@ const EmotionConvey = ({
|
||||
onLayout={(event) => setContainerLayout(event.nativeEvent.layout)}
|
||||
>
|
||||
<ItemContainer height={containerLayout?.height}>
|
||||
<FlatList
|
||||
data={EMOTION_CONVEY}
|
||||
<ListSelection
|
||||
options={EMOTION_CONVEY}
|
||||
variant="emotion"
|
||||
selected={selected}
|
||||
setSelected={setSelected}
|
||||
// Ensure selection remains an object {title, description}
|
||||
formatSelectedValue={(item) => ({
|
||||
title: item.title,
|
||||
description: item.description,
|
||||
})}
|
||||
// selected is object; compare via selected.title
|
||||
selectedValueExtractor={(s) => (s && s.title) || s}
|
||||
contentContainerStyle={{
|
||||
gap: 16,
|
||||
flexGrow: 1,
|
||||
zIndex: 2,
|
||||
paddingTop: 5,
|
||||
}}
|
||||
renderItem={({ item, index }) => {
|
||||
const selectedItem = selected?.title === item.title;
|
||||
|
||||
return (
|
||||
<View style={{ paddingHorizontal: 5 }}>
|
||||
<CreateLyricsHeader
|
||||
colors={item.color}
|
||||
tint={"dark"}
|
||||
onPress={() => onPressSelect(item)}
|
||||
containerStyle={{
|
||||
borderWidth: selectedItem ? 2 : 0,
|
||||
borderColor: selectedItem ? "#F94697" : "transparent",
|
||||
borderRadius: 14,
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 16,
|
||||
color: Palette.white,
|
||||
fontFamily: FONT_FAMILY.InterRegular,
|
||||
}}
|
||||
>
|
||||
<Text style={{ fontFamily: FONT_FAMILY.InterBold }}>
|
||||
{item.title}
|
||||
</Text>{" "}
|
||||
: {item.description}
|
||||
</Text>
|
||||
</CreateLyricsHeader>
|
||||
</View>
|
||||
);
|
||||
}}
|
||||
itemOuterStyle={{ paddingHorizontal: 5 }}
|
||||
/>
|
||||
</ItemContainer>
|
||||
</View>
|
||||
|
||||
@@ -1,21 +1,12 @@
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
FlatList,
|
||||
StyleSheet,
|
||||
Pressable,
|
||||
KeyboardAvoidingView,
|
||||
Platform,
|
||||
} from "react-native";
|
||||
import { View, StyleSheet } from "react-native";
|
||||
import React, { useState } from "react";
|
||||
import CreateLyricsHeader from "./components/CreateLyricsHeader";
|
||||
import { Palette, Style } from "../../styles";
|
||||
import { Palette } from "../../styles";
|
||||
import { GOALS } from "../../data/data";
|
||||
import { FONT_FAMILY } from "../../styles/Fonts";
|
||||
import CustomInput from "./components/CustomInput";
|
||||
import ItemContainer from "../../components/ItemContainer/ItemContainer";
|
||||
import { useKeyboard } from "@react-native-community/hooks";
|
||||
import { responsiveHeight } from "react-native-responsive-dimensions";
|
||||
import ListSelection from "../../components/ListSelection/ListSelection";
|
||||
|
||||
const Goals = ({
|
||||
selected: selectedProp,
|
||||
@@ -28,48 +19,21 @@ const Goals = ({
|
||||
const selected = selectedProp ?? internalSelected;
|
||||
const setSelected = setSelectedProp ?? setInternalSelected;
|
||||
|
||||
const onPressSelect = (item) => {
|
||||
if (selected === item) {
|
||||
setSelected(null);
|
||||
} else {
|
||||
setSelected(item);
|
||||
}
|
||||
};
|
||||
// Selection handled by ListSelection
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1, gap: 16, marginTop: 16 }}>
|
||||
<View style={{ gap: 10 }}>
|
||||
<CreateLyricsHeader title="Quels sont tes objectifs ?" />
|
||||
<CreateLyricsHeader title="Quels est ton objectif ?" />
|
||||
<ItemContainer>
|
||||
<FlatList
|
||||
data={GOALS}
|
||||
showsVerticalScrollIndicator={false}
|
||||
<ListSelection
|
||||
options={GOALS}
|
||||
variant="simple"
|
||||
selected={selected}
|
||||
setSelected={setSelected}
|
||||
contentContainerStyle={styles.contentContainer}
|
||||
renderItem={({ item }) => {
|
||||
const selectedItem = selected === item;
|
||||
|
||||
return (
|
||||
<CreateLyricsHeader
|
||||
onPress={() => onPressSelect(item)}
|
||||
tint={selectedItem ? "default" : "dark"}
|
||||
colors={[Palette.tran, Palette.tran]}
|
||||
containerStyle={{
|
||||
...styles.itemContainer,
|
||||
borderWidth: selectedItem ? 2 : 0,
|
||||
borderColor: selectedItem ? "#F94697" : "transparent",
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
height: 54,
|
||||
...Style.containerCenter,
|
||||
}}
|
||||
>
|
||||
<Text style={styles.itemText}>{item}</Text>
|
||||
</View>
|
||||
</CreateLyricsHeader>
|
||||
);
|
||||
}}
|
||||
itemContainerStyle={styles.itemContainer}
|
||||
itemTextStyle={styles.itemText}
|
||||
/>
|
||||
</ItemContainer>
|
||||
</View>
|
||||
|
||||
@@ -1,59 +1,31 @@
|
||||
import { View, Text, StyleSheet } from "react-native";
|
||||
import { View, StyleSheet } from "react-native";
|
||||
import React, { useState } from "react";
|
||||
import CreateLyricsHeader from "./components/CreateLyricsHeader";
|
||||
import { BlurView } from "expo-blur";
|
||||
import { Palette, Style } from "../../styles";
|
||||
import { Palette } from "../../styles";
|
||||
import { FONT_FAMILY } from "../../styles/Fonts";
|
||||
import ItemContainer from "../../components/ItemContainer/ItemContainer";
|
||||
import ListSelection from "../../components/ListSelection/ListSelection";
|
||||
|
||||
const Rhymes = ({ selected: selectedProp, setSelected: setSelectedProp }) => {
|
||||
const [internalSelected, setInternalSelected] = useState(null);
|
||||
const selected = selectedProp ?? internalSelected;
|
||||
const setSelected = setSelectedProp ?? setInternalSelected;
|
||||
|
||||
const onPressSelect = (item) => {
|
||||
if (selected === item) {
|
||||
setSelected(null);
|
||||
} else {
|
||||
setSelected(item);
|
||||
}
|
||||
};
|
||||
// Selection handled by ListSelection
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1, gap: 10, marginTop: 16 }}>
|
||||
<CreateLyricsHeader title="Avec ou sans rimes" />
|
||||
<ItemContainer height={200}>
|
||||
<View style={{ gap: 10 }}>
|
||||
{["Avec rimes", "Sans rimes", "Mélange des deux"].map(
|
||||
(item, index) => {
|
||||
const selectedItem = selected === item;
|
||||
|
||||
return (
|
||||
<CreateLyricsHeader
|
||||
key={index}
|
||||
onPress={() => onPressSelect(item)}
|
||||
tint={selectedItem ? "default" : "dark"}
|
||||
colors={[Palette.tran, Palette.tran]}
|
||||
containerStyle={{
|
||||
...styles.itemContainer,
|
||||
borderWidth: selectedItem ? 2 : 0,
|
||||
borderColor: selectedItem ? "#F94697" : "transparent",
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
height: 54,
|
||||
...Style.containerCenter,
|
||||
paddingHorizontal: 14,
|
||||
}}
|
||||
>
|
||||
<Text style={styles.dropzone}>{item}</Text>
|
||||
</View>
|
||||
</CreateLyricsHeader>
|
||||
);
|
||||
}
|
||||
)}
|
||||
</View>
|
||||
<ListSelection
|
||||
options={["Avec rimes", "Sans rimes", "Mélange des deux"]}
|
||||
variant="simple"
|
||||
selected={selected}
|
||||
setSelected={setSelected}
|
||||
contentContainerStyle={{ gap: 10 }}
|
||||
itemContainerStyle={styles.itemContainer}
|
||||
itemTextStyle={styles.dropzone}
|
||||
/>
|
||||
</ItemContainer>
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { View, Text, StyleSheet, FlatList } from "react-native";
|
||||
import { View, StyleSheet } from "react-native";
|
||||
import React, { useState } from "react";
|
||||
import CreateLyricsHeader from "./components/CreateLyricsHeader";
|
||||
import { SONG_STRUCTURE } from "../../data/data";
|
||||
import { Palette } from "../../styles";
|
||||
import { FONT_FAMILY } from "../../styles/Fonts";
|
||||
import ItemContainer from "../../components/ItemContainer/ItemContainer";
|
||||
import ListSelection from "../../components/ListSelection/ListSelection";
|
||||
|
||||
const SongStructure = ({
|
||||
selected: selectedProp,
|
||||
@@ -14,13 +15,7 @@ const SongStructure = ({
|
||||
const selected = selectedProp ?? internalSelected;
|
||||
const setSelected = setSelectedProp ?? setInternalSelected;
|
||||
|
||||
const onPressSelect = (item) => {
|
||||
if (selected === item) {
|
||||
setSelected(null);
|
||||
} else {
|
||||
setSelected(item);
|
||||
}
|
||||
};
|
||||
// Selection handled by ListSelection
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1, gap: 10, marginTop: 16 }}>
|
||||
@@ -29,36 +24,14 @@ const SongStructure = ({
|
||||
subTitle="Sélectionne une structure."
|
||||
/>
|
||||
<ItemContainer>
|
||||
<FlatList
|
||||
data={SONG_STRUCTURE}
|
||||
showsVerticalScrollIndicator={false}
|
||||
<ListSelection
|
||||
options={SONG_STRUCTURE}
|
||||
variant="simple"
|
||||
selected={selected}
|
||||
setSelected={setSelected}
|
||||
contentContainerStyle={styles.contentContainer}
|
||||
renderItem={({ item }) => {
|
||||
const selectedItem = selected === item;
|
||||
|
||||
return (
|
||||
<CreateLyricsHeader
|
||||
onPress={() => onPressSelect(item)}
|
||||
tint={selectedItem ? "default" : "dark"}
|
||||
colors={[Palette.tran, Palette.tran]}
|
||||
containerStyle={{
|
||||
...styles.itemContainer,
|
||||
borderWidth: selectedItem ? 2 : 0,
|
||||
borderColor: selectedItem ? "#F94697" : "transparent",
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
height: 40,
|
||||
paddingHorizontal: 14,
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<Text style={styles.itemText}>{item}</Text>
|
||||
</View>
|
||||
</CreateLyricsHeader>
|
||||
);
|
||||
}}
|
||||
itemContainerStyle={styles.itemContainer}
|
||||
itemTextStyle={styles.itemText}
|
||||
/>
|
||||
</ItemContainer>
|
||||
</View>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { View, Text, FlatList, Pressable, StyleSheet } from "react-native";
|
||||
import { View, StyleSheet } from "react-native";
|
||||
import React, { useState } from "react";
|
||||
import CreateLyricsHeader from "./components/CreateLyricsHeader";
|
||||
import { BlurView } from "expo-blur";
|
||||
import { GOALS, SONG_STYLE } from "../../data/data";
|
||||
import { Palette, Style } from "../../styles";
|
||||
import { SONG_STYLE } from "../../data/data";
|
||||
import { Palette } from "../../styles";
|
||||
import CustomInput from "./components/CustomInput";
|
||||
import { FONT_FAMILY } from "../../styles/Fonts";
|
||||
import ItemContainer from "../../components/ItemContainer/ItemContainer";
|
||||
import ListSelection from "../../components/ListSelection/ListSelection";
|
||||
|
||||
const SongStyle = ({
|
||||
selected: selectedProp,
|
||||
@@ -18,13 +18,7 @@ const SongStyle = ({
|
||||
const selected = selectedProp ?? internalSelected;
|
||||
const setSelected = setSelectedProp ?? setInternalSelected;
|
||||
|
||||
const onPressSelect = (item) => {
|
||||
if (selected === item) {
|
||||
setSelected(null);
|
||||
} else {
|
||||
setSelected(item);
|
||||
}
|
||||
};
|
||||
// Selection handled by ListSelection
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1, gap: 16, marginTop: 16 }}>
|
||||
@@ -34,41 +28,13 @@ const SongStyle = ({
|
||||
subTitle="Choisis l'ambiance émotions que tu veux faire passer."
|
||||
/>
|
||||
<ItemContainer>
|
||||
<FlatList
|
||||
data={SONG_STYLE}
|
||||
showsVerticalScrollIndicator={false}
|
||||
<ListSelection
|
||||
options={SONG_STYLE}
|
||||
variant="titleDescription"
|
||||
selected={selected}
|
||||
setSelected={setSelected}
|
||||
contentContainerStyle={styles.contentContainer}
|
||||
renderItem={({ item }) => {
|
||||
const selectedItem = selected === item.title;
|
||||
|
||||
return (
|
||||
<CreateLyricsHeader
|
||||
colors={[Palette.tran, Palette.tran]}
|
||||
tint={selectedItem ? "default" : "dark"}
|
||||
onPress={() => onPressSelect(item.title)}
|
||||
containerStyle={{
|
||||
...styles.itemContainer,
|
||||
borderWidth: selectedItem ? 2 : 0,
|
||||
borderColor: selectedItem ? "#F94697" : "transparent",
|
||||
}}
|
||||
>
|
||||
<View style={{ paddingVertical: 6 }}>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 16,
|
||||
color: Palette.white,
|
||||
fontFamily: FONT_FAMILY.InterRegular,
|
||||
}}
|
||||
>
|
||||
<Text style={{ fontFamily: FONT_FAMILY.InterBold }}>
|
||||
{item.title}
|
||||
</Text>{" "}
|
||||
: {item.description}
|
||||
</Text>
|
||||
</View>
|
||||
</CreateLyricsHeader>
|
||||
);
|
||||
}}
|
||||
itemContainerStyle={styles.itemContainer}
|
||||
/>
|
||||
</ItemContainer>
|
||||
</View>
|
||||
|
||||
Reference in New Issue
Block a user