blurview
This commit is contained in:
@@ -1,9 +1,7 @@
|
|||||||
import { BlurView } from "expo-blur";
|
|
||||||
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
import {
|
import {
|
||||||
FlatList,
|
FlatList,
|
||||||
Image,
|
Image,
|
||||||
Platform,
|
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
Text,
|
Text,
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
@@ -21,6 +19,7 @@ import {
|
|||||||
OPTIONAL_STRUCTURE_SEGMENTS,
|
OPTIONAL_STRUCTURE_SEGMENTS,
|
||||||
sanitizeStructureList,
|
sanitizeStructureList,
|
||||||
} from "../../utils/songStructure";
|
} from "../../utils/songStructure";
|
||||||
|
import ItemContainer from "../../components/ItemContainer/ItemContainer";
|
||||||
import CreateLyricsHeader from "./components/CreateLyricsHeader";
|
import CreateLyricsHeader from "./components/CreateLyricsHeader";
|
||||||
|
|
||||||
const randomSuffix = () => Math.random().toString(36).slice(2, 8);
|
const randomSuffix = () => Math.random().toString(36).slice(2, 8);
|
||||||
@@ -138,66 +137,56 @@ const CustomizeSongStructure = ({ baseStructure = [], onChange }) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const Row = ({ item: rowData, onPress }) => {
|
const Row = ({ item: rowData, onPress }) => {
|
||||||
const isButton = typeof onPress === "function";
|
|
||||||
const Wrapper = isButton ? TouchableOpacity : View;
|
|
||||||
const selected = rowData?.selected;
|
|
||||||
const icon = rowData?.icon;
|
|
||||||
const baseType = rowData?.type || rowData?.value;
|
const baseType = rowData?.type || rowData?.value;
|
||||||
const meta = getSegmentMeta(baseType);
|
const meta = getSegmentMeta(baseType);
|
||||||
const canRemove = rowData?.sortable && meta?.optional === true;
|
const canRemove = rowData?.sortable && meta?.optional === true;
|
||||||
|
const selected = rowData?.selected;
|
||||||
const count = rowData?.count ?? 0;
|
const count = rowData?.count ?? 0;
|
||||||
|
const containerStyle = StyleSheet.flatten([
|
||||||
|
styles.rowContainer,
|
||||||
|
]);
|
||||||
|
const blurStyle = StyleSheet.flatten([
|
||||||
|
styles.rowBlur,
|
||||||
|
selected ? styles.selectedBlur : null,
|
||||||
|
]);
|
||||||
|
const badgeStyle = StyleSheet.flatten([
|
||||||
|
styles.countBadge,
|
||||||
|
count > 0 ? styles.countBadgeActive : null,
|
||||||
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Wrapper
|
<CreateLyricsHeader
|
||||||
|
tint="dark"
|
||||||
|
intensity={40}
|
||||||
|
showBorder={false}
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
activeOpacity={0.8}
|
containerStyle={containerStyle}
|
||||||
style={isButton ? styles.touchable : undefined}
|
blurViewStyle={blurStyle}
|
||||||
>
|
>
|
||||||
<View
|
<View style={styles.rowContent}>
|
||||||
style={[
|
<Text style={styles.rowText}>{rowData?.label || ""}</Text>
|
||||||
styles.itemContainer,
|
{rowData?.sortable ? (
|
||||||
selected && styles.selectedItem,
|
<View style={styles.rowActions}>
|
||||||
rowData?.sortable && styles.sortableItem,
|
<Sortable.Handle>
|
||||||
]}
|
<Image source={icons.dragDots} style={styles.handleIcon} />
|
||||||
>
|
</Sortable.Handle>
|
||||||
<BlurView
|
{canRemove && (
|
||||||
intensity={Platform.OS !== "ios" ? 10 : 30}
|
<TouchableOpacity
|
||||||
style={[
|
onPress={() => handleRemoveItem(rowData.id)}
|
||||||
styles.rowBlur,
|
style={styles.removeButton}
|
||||||
selected && styles.selectedBlur,
|
hitSlop={{ top: 8, right: 8, bottom: 8, left: 8 }}
|
||||||
isButton && styles.buttonBlur,
|
>
|
||||||
]}
|
<Image source={icons.close} style={styles.removeIcon} />
|
||||||
>
|
</TouchableOpacity>
|
||||||
<View style={styles.rowContent}>
|
)}
|
||||||
<Text style={styles.rowText}>{rowData?.label || ""}</Text>
|
|
||||||
</View>
|
</View>
|
||||||
{rowData?.sortable ? (
|
) : (
|
||||||
<View style={styles.rowActions}>
|
<View style={badgeStyle}>
|
||||||
<Sortable.Handle>
|
<Text style={styles.countBadgeText}>{count}</Text>
|
||||||
<Image source={icons.dragDots} style={styles.handleIcon} />
|
</View>
|
||||||
</Sortable.Handle>
|
)}
|
||||||
{canRemove && (
|
|
||||||
<TouchableOpacity
|
|
||||||
onPress={() => handleRemoveItem(rowData.id)}
|
|
||||||
style={styles.removeButton}
|
|
||||||
hitSlop={{ top: 8, right: 8, bottom: 8, left: 8 }}
|
|
||||||
>
|
|
||||||
<Image source={icons.close} style={styles.removeIcon} />
|
|
||||||
</TouchableOpacity>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
) : (
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
styles.countBadge,
|
|
||||||
count > 0 ? styles.countBadgeActive : null,
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Text style={styles.countBadgeText}>{count}</Text>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</BlurView>
|
|
||||||
</View>
|
</View>
|
||||||
</Wrapper>
|
</CreateLyricsHeader>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -216,47 +205,60 @@ const CustomizeSongStructure = ({ baseStructure = [], onChange }) => {
|
|||||||
<View style={styles.sectionTag}>
|
<View style={styles.sectionTag}>
|
||||||
<Text style={styles.sectionTagText}>Structure actuelle</Text>
|
<Text style={styles.sectionTagText}>Structure actuelle</Text>
|
||||||
</View>
|
</View>
|
||||||
<Sortable.Grid
|
<ItemContainer
|
||||||
columns={1}
|
height="auto"
|
||||||
rowGap={12}
|
disableKeyboardHeight
|
||||||
data={labeledItems}
|
style={styles.blurSection}
|
||||||
renderItem={({ item }) => <Row item={item} />}
|
>
|
||||||
customHandle
|
<Sortable.Grid
|
||||||
scrollableRef={scrollRef}
|
columns={1}
|
||||||
onDragEnd={({ data: newData }) => {
|
rowGap={12}
|
||||||
const values = extractValues(newData);
|
data={labeledItems}
|
||||||
const sanitized = sanitizeStructureList(values);
|
renderItem={({ item }) => <Row item={item} />}
|
||||||
setSortableItems(buildSortableItems(sanitized));
|
customHandle
|
||||||
}}
|
scrollableRef={scrollRef}
|
||||||
/>
|
onDragEnd={({ data: newData }) => {
|
||||||
|
const values = extractValues(newData);
|
||||||
|
const sanitized = sanitizeStructureList(values);
|
||||||
|
setSortableItems(buildSortableItems(sanitized));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ItemContainer>
|
||||||
<View style={[styles.sectionTag, { marginTop: 24 }]}>
|
<View style={[styles.sectionTag, { marginTop: 24 }]}>
|
||||||
<Text style={styles.sectionTagText}>Éléments à ajouter</Text>
|
<Text style={styles.sectionTagText}>Éléments à ajouter</Text>
|
||||||
</View>
|
</View>
|
||||||
<FlatList
|
<ItemContainer
|
||||||
data={optionalSegments}
|
height="auto"
|
||||||
gap={12}
|
disableKeyboardHeight
|
||||||
renderItem={({ item }) => {
|
style={styles.blurSection}
|
||||||
const count = segmentCounts[item.type] || 0;
|
>
|
||||||
const selected = count > 0;
|
<FlatList
|
||||||
return (
|
data={optionalSegments}
|
||||||
<View style={{ marginTop: 12 }}>
|
gap={12}
|
||||||
<Row
|
renderItem={({ item, index }) => {
|
||||||
item={{
|
const count = segmentCounts[item.type] || 0;
|
||||||
...item,
|
const selected = count > 0;
|
||||||
sortable: false,
|
const wrapperStyle = index === 0 ? null : styles.optionWrapper;
|
||||||
selected,
|
return (
|
||||||
count,
|
<View style={wrapperStyle}>
|
||||||
}}
|
<Row
|
||||||
onPress={() => handleOptionalSegmentPress(item.type)}
|
item={{
|
||||||
/>
|
...item,
|
||||||
</View>
|
sortable: false,
|
||||||
);
|
selected,
|
||||||
}}
|
count,
|
||||||
keyExtractor={(item) => item.type}
|
}}
|
||||||
contentContainerStyle={{ paddingBottom: 24 }}
|
onPress={() => handleOptionalSegmentPress(item.type)}
|
||||||
scrollEnabled={false}
|
/>
|
||||||
nestedScrollEnabled={false}
|
</View>
|
||||||
/>
|
);
|
||||||
|
}}
|
||||||
|
keyExtractor={(item) => item.type}
|
||||||
|
contentContainerStyle={{ paddingBottom: 24 }}
|
||||||
|
scrollEnabled={false}
|
||||||
|
nestedScrollEnabled={false}
|
||||||
|
/>
|
||||||
|
</ItemContainer>
|
||||||
</Animated.ScrollView>
|
</Animated.ScrollView>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
@@ -266,40 +268,19 @@ const CustomizeSongStructure = ({ baseStructure = [], onChange }) => {
|
|||||||
export default CustomizeSongStructure;
|
export default CustomizeSongStructure;
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
touchable: {
|
rowContainer: {
|
||||||
|
borderRadius: 14,
|
||||||
width: "100%",
|
width: "100%",
|
||||||
},
|
},
|
||||||
itemContainer: {
|
blurSection: {
|
||||||
height: 54,
|
marginTop: 12,
|
||||||
backgroundColor: Palette.glass,
|
width: "100%",
|
||||||
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: {
|
rowBlur: {
|
||||||
flex: 1,
|
minHeight: 54,
|
||||||
backgroundColor: Palette.glass,
|
paddingHorizontal: 16,
|
||||||
justifyContent: "center",
|
|
||||||
paddingHorizontal: 12,
|
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
height: "100%",
|
|
||||||
},
|
|
||||||
buttonBlur: {
|
|
||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
},
|
},
|
||||||
selectedBlur: {
|
selectedBlur: {
|
||||||
@@ -307,7 +288,9 @@ const styles = StyleSheet.create({
|
|||||||
},
|
},
|
||||||
rowContent: {
|
rowContent: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
justifyContent: "center",
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "space-between",
|
||||||
},
|
},
|
||||||
rowText: {
|
rowText: {
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
@@ -320,11 +303,6 @@ const styles = StyleSheet.create({
|
|||||||
tintColor: Palette.white,
|
tintColor: Palette.white,
|
||||||
marginRight: 12,
|
marginRight: 12,
|
||||||
},
|
},
|
||||||
optionIcon: {
|
|
||||||
width: 20,
|
|
||||||
height: 20,
|
|
||||||
tintColor: Palette.white,
|
|
||||||
},
|
|
||||||
countBadge: {
|
countBadge: {
|
||||||
minWidth: 28,
|
minWidth: 28,
|
||||||
paddingHorizontal: 8,
|
paddingHorizontal: 8,
|
||||||
@@ -356,6 +334,9 @@ const styles = StyleSheet.create({
|
|||||||
height: 16,
|
height: 16,
|
||||||
tintColor: Palette.white,
|
tintColor: Palette.white,
|
||||||
},
|
},
|
||||||
|
optionWrapper: {
|
||||||
|
marginTop: 12,
|
||||||
|
},
|
||||||
sectionTag: {
|
sectionTag: {
|
||||||
alignSelf: "flex-start",
|
alignSelf: "flex-start",
|
||||||
paddingHorizontal: 12,
|
paddingHorizontal: 12,
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import {
|
|||||||
OPTIONAL_STRUCTURE_SEGMENTS,
|
OPTIONAL_STRUCTURE_SEGMENTS,
|
||||||
sanitizeStructureList,
|
sanitizeStructureList,
|
||||||
} from "../../utils/songStructure";
|
} from "../../utils/songStructure";
|
||||||
|
import ItemContainer from "../../components/ItemContainer/ItemContainer";
|
||||||
import CreateLyricsHeader from "./components/CreateLyricsHeader";
|
import CreateLyricsHeader from "./components/CreateLyricsHeader";
|
||||||
|
|
||||||
const ROW_HEIGHT = 54;
|
const ROW_HEIGHT = 54;
|
||||||
@@ -222,12 +223,22 @@ const CustomizeSongStructure = ({ baseStructure = [], onChange }) => {
|
|||||||
const baseType = item?.type || item?.value;
|
const baseType = item?.type || item?.value;
|
||||||
const meta = getSegmentMeta(baseType);
|
const meta = getSegmentMeta(baseType);
|
||||||
const canRemove = meta?.optional === true;
|
const canRemove = meta?.optional === true;
|
||||||
|
const containerStyle = StyleSheet.flatten([
|
||||||
|
styles.rowContainer,
|
||||||
|
activeItemId === item.id ? styles.activeItem : null,
|
||||||
|
]);
|
||||||
|
const blurStyle = StyleSheet.flatten([
|
||||||
|
styles.itemBlur,
|
||||||
|
activeItemId === item.id ? styles.activeBlur : null,
|
||||||
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<CreateLyricsHeader
|
||||||
style={[
|
tint="dark"
|
||||||
styles.itemContainer,
|
intensity={40}
|
||||||
activeItemId === item.id && styles.activeItem,
|
showBorder={false}
|
||||||
]}
|
containerStyle={containerStyle}
|
||||||
|
blurViewStyle={blurStyle}
|
||||||
>
|
>
|
||||||
<View style={styles.row}>
|
<View style={styles.row}>
|
||||||
<Text style={styles.rowText}>{item.label}</Text>
|
<Text style={styles.rowText}>{item.label}</Text>
|
||||||
@@ -253,7 +264,7 @@ const CustomizeSongStructure = ({ baseStructure = [], onChange }) => {
|
|||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</CreateLyricsHeader>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -271,38 +282,59 @@ const CustomizeSongStructure = ({ baseStructure = [], onChange }) => {
|
|||||||
<View style={styles.sectionTag}>
|
<View style={styles.sectionTag}>
|
||||||
<Text style={styles.sectionTagText}>Structure actuelle</Text>
|
<Text style={styles.sectionTagText}>Structure actuelle</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.listContainer} ref={containerRef}>
|
<ItemContainer
|
||||||
{labeledItems.map((item) => (
|
height="auto"
|
||||||
<StructureRow key={item.id} item={item} />
|
disableKeyboardHeight
|
||||||
))}
|
style={styles.blurSection}
|
||||||
</View>
|
>
|
||||||
|
<View style={styles.listContainer} ref={containerRef}>
|
||||||
|
{labeledItems.map((item) => (
|
||||||
|
<StructureRow key={item.id} item={item} />
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
</ItemContainer>
|
||||||
<View style={[styles.sectionTag, styles.addSectionTag]}>
|
<View style={[styles.sectionTag, styles.addSectionTag]}>
|
||||||
<Text style={styles.sectionTagText}>Éléments à ajouter</Text>
|
<Text style={styles.sectionTagText}>Éléments à ajouter</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.optionsContainer}>
|
<ItemContainer
|
||||||
{optionalSegments.map((segment) => {
|
height="auto"
|
||||||
const count = segmentCounts[segment.type] || 0;
|
disableKeyboardHeight
|
||||||
const selected = count > 0;
|
style={styles.blurSection}
|
||||||
return (
|
>
|
||||||
<TouchableOpacity
|
<View style={styles.optionsContainer}>
|
||||||
key={segment.type}
|
{optionalSegments.map((segment) => {
|
||||||
activeOpacity={0.85}
|
const count = segmentCounts[segment.type] || 0;
|
||||||
onPress={() => handleOptionalSegmentPress(segment.type)}
|
const selected = count > 0;
|
||||||
style={[styles.optionItem, selected && styles.selectedItem]}
|
const blurStyle = StyleSheet.flatten([
|
||||||
>
|
styles.itemBlur,
|
||||||
<Text style={styles.rowText}>{segment.label}</Text>
|
selected ? styles.selectedBlur : null,
|
||||||
<View
|
]);
|
||||||
style={[
|
const badgeStyle = StyleSheet.flatten([
|
||||||
styles.countBadge,
|
styles.countBadge,
|
||||||
selected && styles.countBadgeActive,
|
selected ? styles.countBadgeActive : null,
|
||||||
]}
|
]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CreateLyricsHeader
|
||||||
|
key={segment.type}
|
||||||
|
tint="dark"
|
||||||
|
intensity={40}
|
||||||
|
showBorder={false}
|
||||||
|
onPress={() => handleOptionalSegmentPress(segment.type)}
|
||||||
|
containerStyle={styles.rowContainer}
|
||||||
|
blurViewStyle={blurStyle}
|
||||||
>
|
>
|
||||||
<Text style={styles.countBadgeText}>{count}</Text>
|
<View style={styles.row}>
|
||||||
</View>
|
<Text style={styles.rowText}>{segment.label}</Text>
|
||||||
</TouchableOpacity>
|
<View style={badgeStyle}>
|
||||||
);
|
<Text style={styles.countBadgeText}>{count}</Text>
|
||||||
})}
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
</CreateLyricsHeader>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</View>
|
||||||
|
</ItemContainer>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
@@ -337,24 +369,32 @@ const styles = StyleSheet.create({
|
|||||||
listContainer: {
|
listContainer: {
|
||||||
gap: 12,
|
gap: 12,
|
||||||
},
|
},
|
||||||
itemContainer: {
|
blurSection: {
|
||||||
height: ROW_HEIGHT,
|
marginTop: 12,
|
||||||
|
width: "100%",
|
||||||
|
},
|
||||||
|
rowContainer: {
|
||||||
borderRadius: 14,
|
borderRadius: 14,
|
||||||
backgroundColor: Palette.glass,
|
|
||||||
shadowColor: "#00000040",
|
|
||||||
shadowOffset: { width: 0, height: 2 },
|
|
||||||
shadowOpacity: 0.25,
|
|
||||||
shadowRadius: 3.84,
|
|
||||||
elevation: 5,
|
|
||||||
overflow: "hidden",
|
|
||||||
},
|
},
|
||||||
activeItem: {
|
activeItem: {
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
borderColor: Palette.white,
|
borderColor: Palette.white,
|
||||||
},
|
},
|
||||||
|
itemBlur: {
|
||||||
|
minHeight: ROW_HEIGHT,
|
||||||
|
paddingHorizontal: 16,
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
},
|
||||||
|
activeBlur: {
|
||||||
|
backgroundColor: Palette.transparentWhite,
|
||||||
|
},
|
||||||
|
selectedBlur: {
|
||||||
|
backgroundColor: Palette.transparentWhite,
|
||||||
|
},
|
||||||
row: {
|
row: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
paddingHorizontal: 16,
|
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
@@ -391,26 +431,6 @@ const styles = StyleSheet.create({
|
|||||||
},
|
},
|
||||||
optionsContainer: {
|
optionsContainer: {
|
||||||
gap: 12,
|
gap: 12,
|
||||||
marginTop: 12,
|
|
||||||
},
|
|
||||||
optionItem: {
|
|
||||||
height: ROW_HEIGHT,
|
|
||||||
borderRadius: 14,
|
|
||||||
backgroundColor: Palette.glass,
|
|
||||||
shadowColor: "#00000040",
|
|
||||||
shadowOffset: { width: 0, height: 2 },
|
|
||||||
shadowOpacity: 0.25,
|
|
||||||
shadowRadius: 3.84,
|
|
||||||
elevation: 5,
|
|
||||||
overflow: "hidden",
|
|
||||||
paddingHorizontal: 16,
|
|
||||||
flexDirection: "row",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
},
|
|
||||||
selectedItem: {
|
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: Palette.white,
|
|
||||||
},
|
},
|
||||||
countBadge: {
|
countBadge: {
|
||||||
minWidth: 28,
|
minWidth: 28,
|
||||||
|
|||||||
Reference in New Issue
Block a user