308 lines
8.5 KiB
React
308 lines
8.5 KiB
React
|
import {
|
|||
|
View,
|
|||
|
Text,
|
|||
|
TouchableOpacity,
|
|||
|
Image,
|
|||
|
Modal,
|
|||
|
Platform,
|
|||
|
} from "react-native";
|
|||
|
import React, { useState, useEffect } from "react";
|
|||
|
import { useTailwind } from "tailwind-rn";
|
|||
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|||
|
import { ImagePicker } from "expo-image-multiple-picker";
|
|||
|
import { Icon, Badge } from "@rneui/themed";
|
|||
|
import * as MediaLibrary from "expo-media-library";
|
|||
|
import * as Linking from "expo-linking";
|
|||
|
import MyModal from "../MyModal";
|
|||
|
|
|||
|
/*
|
|||
|
props格式:
|
|||
|
visible 控制modal可见性
|
|||
|
setVisible 控制modal可见性
|
|||
|
type 选择的媒体类型 "image"|"video"|"mix"
|
|||
|
maxCount 最大选择数量
|
|||
|
setAssets 向父组件返回媒体
|
|||
|
*/
|
|||
|
|
|||
|
export default function MediaPickerModal({
|
|||
|
visible,
|
|||
|
setVisible,
|
|||
|
type,
|
|||
|
maxCount,
|
|||
|
setAssets,
|
|||
|
}) {
|
|||
|
const tailwind = useTailwind();
|
|||
|
const insets = useSafeAreaInsets();
|
|||
|
const [album, setAlbum] = useState({ title: "全部" });
|
|||
|
|
|||
|
//前往打开权限弹窗
|
|||
|
const [overlayVisible, setOverlayVisible] = useState(false);
|
|||
|
//保存当前权限状态
|
|||
|
const [permissionStatus, setPermissionStatus] = useState();
|
|||
|
|
|||
|
//检查并获取权限
|
|||
|
useEffect(() => {
|
|||
|
if (visible) {
|
|||
|
async function requestMediaLibraryPermissions() {
|
|||
|
//权限检查、获取
|
|||
|
// 第一步:检查是否已有权限
|
|||
|
const { status } = await MediaLibrary.getPermissionsAsync();
|
|||
|
if (status === "granted") {
|
|||
|
// 如果已有权限,直接执行操作
|
|||
|
setPermissionStatus("granted");
|
|||
|
return;
|
|||
|
}
|
|||
|
// 第二步:如果没有权限,请求用户授权
|
|||
|
const permission = await MediaLibrary.requestPermissionsAsync();
|
|||
|
if (permission.status === "denied") {
|
|||
|
// 用户拒绝了权限请求,打开应用设置页面
|
|||
|
setPermissionStatus("denied");
|
|||
|
setVisible(false);
|
|||
|
setOverlayVisible(true);
|
|||
|
return;
|
|||
|
}
|
|||
|
if (permission.status === "granted") {
|
|||
|
// 用户同意了权限请求,执行操作
|
|||
|
setPermissionStatus("granted");
|
|||
|
}
|
|||
|
}
|
|||
|
requestMediaLibraryPermissions();
|
|||
|
}
|
|||
|
}, [visible]);
|
|||
|
|
|||
|
//将ios中assets的路径从ph://改为file://
|
|||
|
const changeToLocalUri = async (assets) => {
|
|||
|
const editedAssets = await Promise.all(
|
|||
|
assets.map(async (item) => {
|
|||
|
const info = await MediaLibrary.getAssetInfoAsync(item.id);
|
|||
|
const uri = info.localUri;
|
|||
|
if (Platform.OS === "ios") {
|
|||
|
return { ...item, old_uri: item.uri, uri };
|
|||
|
}
|
|||
|
return { ...item, old_uri: item.uri };
|
|||
|
})
|
|||
|
);
|
|||
|
return editedAssets;
|
|||
|
};
|
|||
|
|
|||
|
//让视频秒数格式化
|
|||
|
function formatDuration(duration) {
|
|||
|
let minutes = Math.floor(duration / 60);
|
|||
|
let seconds = Math.round(duration % 60);
|
|||
|
if (seconds < 10) {
|
|||
|
seconds = "0" + seconds;
|
|||
|
}
|
|||
|
return `${minutes}:${seconds}`;
|
|||
|
}
|
|||
|
|
|||
|
//header组件
|
|||
|
const ImagePickerHeader = (props) => {
|
|||
|
return (
|
|||
|
<View
|
|||
|
style={{
|
|||
|
paddingTop: insets.top,
|
|||
|
height: 100,
|
|||
|
...tailwind("px-4 bg-white flex-row justify-between items-center"),
|
|||
|
}}
|
|||
|
>
|
|||
|
{props.view == "album" && (
|
|||
|
<>
|
|||
|
<View style={tailwind("w-10")}></View>
|
|||
|
<Text style={tailwind("text-lg text-center")}>选择相册</Text>
|
|||
|
<TouchableOpacity
|
|||
|
onPress={() => {
|
|||
|
setVisible(false);
|
|||
|
}}
|
|||
|
style={tailwind("flex-row items-center")}
|
|||
|
>
|
|||
|
<Text
|
|||
|
style={{
|
|||
|
color: "#4387d6",
|
|||
|
...tailwind("text-lg font-semibold ml-1"),
|
|||
|
}}
|
|||
|
>
|
|||
|
取消
|
|||
|
</Text>
|
|||
|
</TouchableOpacity>
|
|||
|
</>
|
|||
|
)}
|
|||
|
{props.view == "gallery" && (
|
|||
|
<>
|
|||
|
<TouchableOpacity onPress={props.goToAlbum}>
|
|||
|
<Icon type="ionicon" name="chevron-back" size={32} />
|
|||
|
</TouchableOpacity>
|
|||
|
<Text
|
|||
|
style={tailwind("text-lg text-center w-1/2")}
|
|||
|
numberOfLines={1}
|
|||
|
ellipsizeMode="tail"
|
|||
|
>
|
|||
|
{props.album.title}
|
|||
|
</Text>
|
|||
|
|
|||
|
{props.imagesPicked > 0 ? (
|
|||
|
<TouchableOpacity
|
|||
|
onPress={props.save}
|
|||
|
style={tailwind("flex-row items-center")}
|
|||
|
>
|
|||
|
<Badge value={props.imagesPicked} status="primary" />
|
|||
|
<Text
|
|||
|
style={{
|
|||
|
color: "#4387d6",
|
|||
|
...tailwind("text-lg font-semibold ml-1"),
|
|||
|
}}
|
|||
|
>
|
|||
|
完成
|
|||
|
</Text>
|
|||
|
</TouchableOpacity>
|
|||
|
) : (
|
|||
|
<TouchableOpacity
|
|||
|
onPress={() => {
|
|||
|
setVisible(false);
|
|||
|
}}
|
|||
|
style={tailwind("flex-row items-center")}
|
|||
|
>
|
|||
|
<Text
|
|||
|
style={{
|
|||
|
color: "#4387d6",
|
|||
|
...tailwind("text-lg font-semibold ml-1"),
|
|||
|
}}
|
|||
|
>
|
|||
|
取消
|
|||
|
</Text>
|
|||
|
</TouchableOpacity>
|
|||
|
)}
|
|||
|
</>
|
|||
|
)}
|
|||
|
</View>
|
|||
|
);
|
|||
|
};
|
|||
|
|
|||
|
//check组件
|
|||
|
const ImagePickerCheck = () => {
|
|||
|
return (
|
|||
|
<View
|
|||
|
style={{
|
|||
|
width: "100%",
|
|||
|
height: "100%",
|
|||
|
alignItems: "center",
|
|||
|
justifyContent: "center",
|
|||
|
backgroundColor: "rgba(0,0,0,0.6)",
|
|||
|
}}
|
|||
|
>
|
|||
|
<Icon type="ionicon" name="checkmark" color="white" size={32} />
|
|||
|
</View>
|
|||
|
);
|
|||
|
};
|
|||
|
|
|||
|
//album组件
|
|||
|
const ImagePickerAlbum = (props) => {
|
|||
|
return (
|
|||
|
<TouchableOpacity
|
|||
|
onPress={() => props.goToGallery(props.album)}
|
|||
|
style={{ flex: 1, height: 200 }}
|
|||
|
>
|
|||
|
<Image
|
|||
|
source={{ uri: props.thumb.uri }}
|
|||
|
style={{ width: "100%", height: "100%" }}
|
|||
|
blurRadius={10}
|
|||
|
></Image>
|
|||
|
<View
|
|||
|
style={{
|
|||
|
position: "absolute",
|
|||
|
width: "100%",
|
|||
|
height: "100%",
|
|||
|
backgroundColor: "rgba(0,0,0,0.2)",
|
|||
|
justifyContent: "flex-end",
|
|||
|
}}
|
|||
|
>
|
|||
|
<View style={{ padding: 5, flexDirection: "row" }}>
|
|||
|
<Icon type="ionicon" name="folder-open" color="white" size={16} />
|
|||
|
<Text
|
|||
|
style={{
|
|||
|
color: "white",
|
|||
|
fontSize: 16,
|
|||
|
marginLeft: 5,
|
|||
|
}}
|
|||
|
>
|
|||
|
{props.album.title}
|
|||
|
</Text>
|
|||
|
</View>
|
|||
|
</View>
|
|||
|
</TouchableOpacity>
|
|||
|
);
|
|||
|
};
|
|||
|
|
|||
|
//video组件
|
|||
|
const ImagePickerVideo = (props) => {
|
|||
|
return (
|
|||
|
<View style={tailwind("flex-1 justify-end")}>
|
|||
|
<View
|
|||
|
style={tailwind(
|
|||
|
"flex-row justify-between items-center bg-black px-2"
|
|||
|
)}
|
|||
|
>
|
|||
|
<Icon type="ionicon" name="videocam" color="white" size={16} />
|
|||
|
<Text numberOfLines={1} style={tailwind("text-xs text-white")}>
|
|||
|
{formatDuration(props.duration)}
|
|||
|
</Text>
|
|||
|
</View>
|
|||
|
</View>
|
|||
|
);
|
|||
|
};
|
|||
|
|
|||
|
return (
|
|||
|
<>
|
|||
|
<Modal
|
|||
|
visible={visible && permissionStatus == "granted"}
|
|||
|
transparent={true}
|
|||
|
statusBarTranslucent
|
|||
|
animationType="slide"
|
|||
|
>
|
|||
|
<View
|
|||
|
style={{
|
|||
|
...tailwind("bg-white flex-1"),
|
|||
|
}}
|
|||
|
>
|
|||
|
<ImagePicker
|
|||
|
theme={{
|
|||
|
header: ImagePickerHeader,
|
|||
|
check: ImagePickerCheck,
|
|||
|
album: ImagePickerAlbum,
|
|||
|
video: ImagePickerVideo,
|
|||
|
}}
|
|||
|
onSave={async (assets) => {
|
|||
|
const editedAssets = await changeToLocalUri(assets);
|
|||
|
setAssets(editedAssets);
|
|||
|
setVisible(false);
|
|||
|
}}
|
|||
|
onCancel={() => {
|
|||
|
setVisible(false);
|
|||
|
}}
|
|||
|
galleryColumns={3}
|
|||
|
albumColumns={2}
|
|||
|
multiple
|
|||
|
onSelectAlbum={(album) => setAlbum(album)}
|
|||
|
selectedAlbum={album}
|
|||
|
limit={maxCount}
|
|||
|
video={type === "video" || type === "mix"}
|
|||
|
image={type === "image" || type === "mix"}
|
|||
|
/>
|
|||
|
</View>
|
|||
|
</Modal>
|
|||
|
<MyModal
|
|||
|
visible={overlayVisible}
|
|||
|
setVisible={setOverlayVisible}
|
|||
|
title="您还未打开媒体权限"
|
|||
|
content="是否前往设置开启权限?"
|
|||
|
cancel={() => {
|
|||
|
setOverlayVisible(false);
|
|||
|
}}
|
|||
|
confirm={() => {
|
|||
|
Linking.openSettings();
|
|||
|
setOverlayVisible(false);
|
|||
|
}}
|
|||
|
/>
|
|||
|
</>
|
|||
|
);
|
|||
|
}
|