197 lines
5.4 KiB
JavaScript
197 lines
5.4 KiB
JavaScript
import { View, TouchableOpacity, Image } from "react-native";
|
||
import React, { useState, useEffect, useCallback } from "react";
|
||
import { useTailwind } from "tailwind-rn";
|
||
import { Icon } from "@rneui/themed";
|
||
import Toast from "react-native-toast-message";
|
||
import { DraggableGrid } from "react-native-draggable-grid";
|
||
import { useImageViewer } from "../../context/ImageViewProvider";
|
||
import VideoModal from "../VideoModal";
|
||
import * as ImagePicker from "expo-image-picker";
|
||
import * as VideoThumbnails from "expo-video-thumbnails";
|
||
|
||
/*
|
||
props格式:
|
||
setDragging 返回正在拖动的状态,用于该组件嵌套在ScrollView中的时候禁用ScrollView的滚动
|
||
type 选择的媒体类型 "image"|"video"|"mix"
|
||
maxCount 最大选择数量
|
||
setAssets 向父组件返回媒体
|
||
*/
|
||
|
||
export default function MediaPicker({
|
||
setDragging = () => null,
|
||
maxCount,
|
||
type = "image",
|
||
setAssets,
|
||
}) {
|
||
const tailwind = useTailwind();
|
||
const [newAssets, setNewAssets] = useState([]);
|
||
const [_assets, set_Assets] = useState([]);
|
||
|
||
const { showImageViewer } = useImageViewer();
|
||
|
||
const [showVideo, setShowVideo] = useState(false);
|
||
const [videoUrl, setVideoUrl] = useState("");
|
||
|
||
//为视频生成封面
|
||
const generateThumbnail = useCallback(async (uri) => {
|
||
try {
|
||
const videoCover = await VideoThumbnails.getThumbnailAsync(uri);
|
||
return videoCover;
|
||
} catch (e) {
|
||
console.warn(e);
|
||
}
|
||
}, []);
|
||
|
||
//选择媒体
|
||
const pickMedia = async () => {
|
||
let mediaType;
|
||
if (type === "image") {
|
||
mediaType = ImagePicker.MediaTypeOptions.Images;
|
||
} else if (type === "video") {
|
||
mediaType = ImagePicker.MediaTypeOptions.Videos;
|
||
} else {
|
||
mediaType = ImagePicker.MediaTypeOptions.All;
|
||
}
|
||
let result = await ImagePicker.launchImageLibraryAsync({
|
||
mediaTypes: mediaType,
|
||
allowsMultipleSelection: true,
|
||
selectionLimit: maxCount - _assets.length,
|
||
quality: 1,
|
||
});
|
||
|
||
if (!result.canceled) {
|
||
for (let i = 0; i < result.assets.length; i++) {
|
||
if (result.assets[i].duration > 0) {
|
||
const videoCover = await generateThumbnail(result.assets[i].uri);
|
||
result.assets[i].cover = videoCover.uri;
|
||
}
|
||
}
|
||
setNewAssets(result.assets);
|
||
}
|
||
};
|
||
|
||
//当newAssets改变的时候添加新数据到assets
|
||
useEffect(() => {
|
||
const temAssets = newAssets.map((item) => ({
|
||
...item,
|
||
key: Date.now().toString(36) + Math.random().toString(36),
|
||
}));
|
||
set_Assets([..._assets, ...temAssets]);
|
||
setAssets([..._assets, ...temAssets]);
|
||
}, [newAssets]);
|
||
|
||
//加号选择器
|
||
const Picker = () => {
|
||
return (
|
||
<TouchableOpacity
|
||
onPress={() => {
|
||
//数量检查
|
||
maxCount - _assets.length !== 0
|
||
? pickMedia()
|
||
: Toast.show({
|
||
type: "error",
|
||
text1: `已达到最大选择数量`,
|
||
topOffset: 60,
|
||
});
|
||
}}
|
||
style={{
|
||
aspectRatio: 1,
|
||
padding: 2,
|
||
...tailwind("w-1/4"),
|
||
}}
|
||
>
|
||
<View
|
||
style={tailwind(
|
||
"border border-dashed border-gray-400 rounded flex-1 justify-center items-center"
|
||
)}
|
||
>
|
||
<Icon type="ionicon" name="add" color="#9ca3af" size={40} />
|
||
</View>
|
||
</TouchableOpacity>
|
||
);
|
||
};
|
||
|
||
const renderItem = (item) => {
|
||
const handleDelete = () => {
|
||
const updatedAssets = _assets.filter((_item) => _item.key !== item.key);
|
||
set_Assets(updatedAssets);
|
||
setAssets(updatedAssets);
|
||
};
|
||
|
||
return (
|
||
<View
|
||
activeOpacity={1}
|
||
style={{
|
||
aspectRatio: 1,
|
||
padding: 2,
|
||
...tailwind("w-full"),
|
||
}}
|
||
key={item.key}
|
||
>
|
||
<Image
|
||
src={item.cover ? item.cover : item.uri}
|
||
style={tailwind("w-full h-full rounded")}
|
||
/>
|
||
<View style={{ zIndex: 999, ...tailwind("absolute top-2 right-2") }}>
|
||
<Icon
|
||
type="ionicon"
|
||
name="close"
|
||
color="white"
|
||
size={18}
|
||
onPress={handleDelete}
|
||
style={tailwind("bg-black rounded-full opacity-70")}
|
||
/>
|
||
</View>
|
||
{item.duration > 0 && (
|
||
<View
|
||
style={tailwind(
|
||
"absolute flex w-full h-full items-center justify-center"
|
||
)}
|
||
>
|
||
<Icon
|
||
type="ionicon"
|
||
name="play"
|
||
color="white"
|
||
size={14}
|
||
style={tailwind("bg-black p-2 rounded-full opacity-70")}
|
||
/>
|
||
</View>
|
||
)}
|
||
</View>
|
||
);
|
||
};
|
||
|
||
return (
|
||
<View style={tailwind("flex flex-col w-full")}>
|
||
<DraggableGrid
|
||
numColumns={4}
|
||
renderItem={renderItem}
|
||
data={_assets}
|
||
onDragStart={() => setDragging(true)}
|
||
onDragRelease={(data) => {
|
||
set_Assets(data);
|
||
setAssets(data);
|
||
setDragging(false);
|
||
}}
|
||
onItemPress={(item) => {
|
||
if (!item.duration) {
|
||
showImageViewer({
|
||
imageUrls: [{ url: item.uri }],
|
||
index: 0,
|
||
});
|
||
return;
|
||
}
|
||
setVideoUrl(item.uri);
|
||
setShowVideo(true);
|
||
}}
|
||
/>
|
||
<Picker />
|
||
<VideoModal
|
||
visible={showVideo}
|
||
setVisible={setShowVideo}
|
||
url={videoUrl}
|
||
/>
|
||
</View>
|
||
);
|
||
}
|