memowake-front/components/ask/selectModel.tsx
2025-07-31 14:26:54 +08:00

289 lines
12 KiB
TypeScript

import CancelSvg from '@/assets/icons/svg/cancel.svg';
import DownloadSvg from '@/assets/icons/svg/download.svg';
import FolderSvg from "@/assets/icons/svg/folder.svg";
import ReturnArrow from "@/assets/icons/svg/returnArrow.svg";
import YesSvg from "@/assets/icons/svg/yes.svg";
import { TFunction } from "i18next";
import React from "react";
import { FlatList, Image, Modal, StyleSheet, TouchableOpacity, View } from "react-native";
import ContextMenu from "../gusture/contextMenu";
import { ThemedText } from "../ThemedText";
import { mergeArrays, saveMediaToGallery } from "./utils";
interface SelectModelProps {
modalDetailsVisible: { visible: boolean, content: any };
setModalDetailsVisible: React.Dispatch<React.SetStateAction<{ visible: boolean, content: any }>>;
insets: { top: number };
setSelectedImages: React.Dispatch<React.SetStateAction<string[]>>;
selectedImages: string[];
t: TFunction;
cancel: boolean;
setCancel: React.Dispatch<React.SetStateAction<boolean>>;
}
const SelectModel = ({ modalDetailsVisible, setModalDetailsVisible, insets, setSelectedImages, selectedImages, t, cancel, setCancel }: SelectModelProps) => {
return (
<Modal
animationType="fade"
visible={modalDetailsVisible.visible}
transparent={false}
statusBarTranslucent={true}
onRequestClose={() => {
setModalDetailsVisible({ visible: false, content: [] });
}}
>
<View style={[detailsStyles.container, { paddingTop: insets?.top }]}>
<View style={detailsStyles.header}>
<TouchableOpacity onPress={() => setModalDetailsVisible({ visible: false, content: [] })}>
<ReturnArrow />
</TouchableOpacity>
<ThemedText style={detailsStyles.headerText}>{t('ask.selectPhoto', { ns: 'ask' })}</ThemedText>
<FolderSvg />
</View>
<View style={{ flex: 1 }}>
<FlatList
data={mergeArrays(modalDetailsVisible?.content?.image_material_infos || [], modalDetailsVisible?.content?.video_material_infos || [])}
numColumns={3}
keyExtractor={(item) => item.id}
showsVerticalScrollIndicator={false}
contentContainerStyle={detailsStyles.gridContainer}
initialNumToRender={12}
maxToRenderPerBatch={12}
updateCellsBatchingPeriod={50}
windowSize={10}
removeClippedSubviews={true}
renderItem={({ item }) => {
const itemId = item?.id || item?.video?.id;
const isSelected = selectedImages.includes(itemId);
const toggleSelection = () => {
setSelectedImages(prev =>
isSelected
? prev.filter(id => id !== itemId)
: [...prev, itemId]
);
};
return (
<View style={detailsStyles.gridItemContainer} key={itemId}>
<View style={detailsStyles.gridItem}>
{isSelected && (
<ThemedText style={detailsStyles.imageNumber}>
{selectedImages.indexOf(itemId) + 1}
</ThemedText>
)}
<ContextMenu
items={[
{
svg: <DownloadSvg width={20} height={20} />,
label: t("ask:ask.save"),
onPress: () => {
const imageUrl = item?.file_info?.url || item.video?.file_info?.url;
if (imageUrl) {
saveMediaToGallery(imageUrl, t);
}
},
textStyle: { color: '#4C320C' }
},
{
svg: <CancelSvg width={20} height={20} color='red' />,
label: t("ask:ask.cancel"),
onPress: () => setCancel(true),
textStyle: { color: 'red' }
}
]}
cancel={cancel}
menuStyle={{
backgroundColor: 'white',
borderRadius: 8,
padding: 8,
minWidth: 150,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.25,
shadowRadius: 4,
elevation: 5,
}}
>
<Image
source={{ uri: item?.preview_file_info?.url || item.video?.preview_file_info?.url }}
style={detailsStyles.image}
onError={(error) => console.log('Image load error:', error.nativeEvent.error)}
onLoad={() => console.log('Image loaded successfully')}
loadingIndicatorSource={require('@/assets/images/png/placeholder.png')}
/>
</ContextMenu>
<TouchableOpacity
style={[
detailsStyles.circleMarker,
isSelected && detailsStyles.circleMarkerSelected
]}
onPress={() => {
setSelectedImages(prev =>
isSelected
? prev.filter(id => id !== itemId)
: [...prev, itemId]
);
}}
activeOpacity={0.8}
>
{isSelected && <YesSvg width={16} height={16} />}
</TouchableOpacity>
</View>
</View>
);
}}
/>
</View>
<View style={detailsStyles.footer}>
<TouchableOpacity
style={detailsStyles.continueButton}
onPress={async () => {
// 如果用户没有选择 则为选择全部
if (selectedImages?.length < 0) {
setSelectedImages(mergeArrays(modalDetailsVisible?.content?.image_material_infos || [], modalDetailsVisible?.content?.video_material_infos || [])?.map((item) => {
return item.id || item.video?.id
}))
}
setModalDetailsVisible({ visible: false, content: [] })
}}
activeOpacity={0.8}
>
<ThemedText style={detailsStyles.continueButtonText}>
{t('ask.continueAsking', { ns: 'ask' })}
</ThemedText>
</TouchableOpacity>
</View>
</View>
</Modal >
)
}
const detailsStyles = StyleSheet.create({
gridContainer: {
flex: 1,
paddingHorizontal: 8,
paddingTop: 8,
},
gridItemContainer: {
width: '33.33%',
aspectRatio: 1,
padding: 1,
},
flatListContent: {
paddingBottom: 100,
paddingHorizontal: 8,
paddingTop: 8,
},
headerText: {
fontSize: 20,
fontWeight: 'bold',
color: "#4C320C"
},
container: {
flex: 1,
padding: 0,
margin: 0,
backgroundColor: '#fff',
width: '100%',
height: '100%',
position: 'relative',
},
imageNumber: {
fontSize: 16,
fontWeight: 'bold',
color: '#fff',
position: 'absolute',
top: 10,
left: 10,
zIndex: 10,
},
imageNumberText: {
fontSize: 16,
fontWeight: 'bold',
color: '#fff',
},
numberText: {
position: 'absolute',
top: 10,
left: 10,
width: 28,
height: 28,
borderRadius: 14,
backgroundColor: 'rgba(0, 122, 255, 0.9)',
justifyContent: 'center',
alignItems: 'center',
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.25,
shadowRadius: 3.84,
elevation: 5,
},
header: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
padding: 16,
borderBottomWidth: 1,
borderBottomColor: '#eee',
},
gridItem: {
flex: 1,
backgroundColor: '#f5f5f5',
borderRadius: 8,
overflow: 'hidden',
position: 'relative',
},
image: {
width: '100%',
height: '100%',
resizeMode: 'cover',
},
circleMarker: {
position: 'absolute',
top: 10,
right: 10,
width: 28,
height: 28,
borderRadius: 14,
justifyContent: 'center',
alignItems: 'center',
borderWidth: 2,
borderColor: '#fff',
},
circleMarkerSelected: {
backgroundColor: '#FFB645',
},
markerText: {
fontSize: 16,
fontWeight: 'bold',
color: '#000',
},
footer: {
position: 'absolute',
bottom: 20,
left: 0,
right: 0,
paddingHorizontal: 16,
zIndex: 10,
paddingVertical: 10,
},
continueButton: {
backgroundColor: '#E2793F',
borderRadius: 32,
padding: 16,
alignItems: 'center',
width: '100%',
zIndex: 10,
},
continueButtonText: {
color: '#fff',
fontSize: 18,
fontWeight: 'bold',
}
});
export default SelectModel