feat: 图片视频长按保存
This commit is contained in:
parent
0644d636d7
commit
a250529de3
2
app.json
2
app.json
@ -12,7 +12,7 @@
|
||||
"supportsTablet": true,
|
||||
"infoPlist": {
|
||||
"NSPhotoLibraryUsageDescription": "允许访问照片库,以便模型使用您照片库中的素材进行视频创作”(例如:上传您参加音乐节的现场图,生成一个音乐节体验Vlog",
|
||||
"NSPhotoLibraryAddUsageDescription": "需要保存图片到相册",
|
||||
"NSPhotoLibraryAddUsageDescription": "App需要访问相册来保存图片",
|
||||
"NSLocationWhenInUseUsageDescription": "允许获取位置信息,以便模型使用您的位置信息进行个性化创作”(例如:上传您去欧洲旅游的位置信息,结合在当地拍摄的照片,生成一个欧洲旅行攻略Vlog)",
|
||||
"ITSAppUsesNonExemptEncryption": false,
|
||||
"UIBackgroundModes": [
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
import CancelSvg from '@/assets/icons/svg/cancel.svg';
|
||||
import ChatSvg from "@/assets/icons/svg/chat.svg";
|
||||
import DownloadSvg from '@/assets/icons/svg/download.svg';
|
||||
import MoreSvg from "@/assets/icons/svg/more.svg";
|
||||
import { Message, Video } from "@/types/ask";
|
||||
import { MaterialItem } from "@/types/personal-info";
|
||||
@ -12,11 +14,12 @@ import {
|
||||
TouchableOpacity,
|
||||
View
|
||||
} from 'react-native';
|
||||
import ContextMenu from "../gusture/contextMenu";
|
||||
import { ThemedText } from "../ThemedText";
|
||||
import SelectModel from "./selectModel";
|
||||
import SingleContentModel from "./singleContentModel";
|
||||
import TypewriterText from "./typewriterText";
|
||||
import { mergeArrays } from "./utils";
|
||||
import { mergeArrays, saveMediaToGallery } from "./utils";
|
||||
|
||||
interface RenderMessageProps {
|
||||
insets: { top: number };
|
||||
@ -69,16 +72,50 @@ const MessageItem = ({ t, insets, item, sessionId, setModalVisible, modalVisible
|
||||
marginBottom: 8,
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
source={{ uri: image?.preview_file_info?.url || image.video?.preview_file_info?.url }}
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
borderRadius: 12,
|
||||
<ContextMenu
|
||||
items={[
|
||||
{
|
||||
svg: <DownloadSvg width={20} height={20} />,
|
||||
label: "保存",
|
||||
onPress: () => {
|
||||
const imageUrl = image?.preview_file_info?.url || image.video?.preview_file_info?.url;
|
||||
if (imageUrl) {
|
||||
saveMediaToGallery(imageUrl);
|
||||
}
|
||||
},
|
||||
textStyle: { color: '#4C320C' }
|
||||
},
|
||||
{
|
||||
svg: <CancelSvg width={20} height={20} color='red' />,
|
||||
label: "取消",
|
||||
onPress: () => console.log('取消'),
|
||||
textStyle: { color: 'red' }
|
||||
}
|
||||
]}
|
||||
menuStyle={{
|
||||
backgroundColor: 'white',
|
||||
borderRadius: 8,
|
||||
padding: 8,
|
||||
minWidth: 150,
|
||||
shadowColor: '#000',
|
||||
shadowOffset: { width: 0, height: 2 },
|
||||
shadowOpacity: 0.25,
|
||||
shadowRadius: 4,
|
||||
elevation: 5,
|
||||
}}
|
||||
resizeMode="cover"
|
||||
loadingIndicatorSource={require('@/assets/images/png/placeholder.png')}
|
||||
/>
|
||||
>
|
||||
<Image
|
||||
source={{ uri: image?.preview_file_info?.url || image.video?.preview_file_info?.url }}
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
borderRadius: 12,
|
||||
}}
|
||||
resizeMode="cover"
|
||||
loadingIndicatorSource={require('@/assets/images/png/placeholder.png')}
|
||||
/>
|
||||
</ContextMenu>
|
||||
|
||||
</Pressable>
|
||||
))}
|
||||
</View>
|
||||
|
||||
@ -1,16 +1,14 @@
|
||||
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 { Gesture } from 'react-native-gesture-handler';
|
||||
import ContextMenu from "../gusture/contextMenu";
|
||||
import { ThemedText } from "../ThemedText";
|
||||
import { mergeArrays } from "./utils";
|
||||
|
||||
import CancelSvg from '@/assets/icons/svg/cancel.svg';
|
||||
import DownloadSvg from '@/assets/icons/svg/download.svg';
|
||||
import { mergeArrays, saveMediaToGallery } from "./utils";
|
||||
|
||||
interface SelectModelProps {
|
||||
modalDetailsVisible: { visible: boolean, content: any };
|
||||
@ -22,11 +20,6 @@ interface SelectModelProps {
|
||||
}
|
||||
const SelectModel = ({ modalDetailsVisible, setModalDetailsVisible, insets, setSelectedImages, selectedImages, t }: SelectModelProps) => {
|
||||
|
||||
const longPressGesture = Gesture.LongPress().onEnd((e, success) => {
|
||||
if (success) {
|
||||
console.log(`Long pressed for ${e.duration} ms!`);
|
||||
}
|
||||
});
|
||||
return (
|
||||
<Modal
|
||||
animationType="fade"
|
||||
@ -83,7 +76,12 @@ const SelectModel = ({ modalDetailsVisible, setModalDetailsVisible, insets, setS
|
||||
{
|
||||
svg: <DownloadSvg width={20} height={20} />,
|
||||
label: "保存",
|
||||
onPress: () => console.log('保存图片'),
|
||||
onPress: () => {
|
||||
const imageUrl = item?.file_info?.url || item.video?.file_info?.url;
|
||||
if (imageUrl) {
|
||||
saveMediaToGallery(imageUrl);
|
||||
}
|
||||
},
|
||||
textStyle: { color: '#4C320C' }
|
||||
},
|
||||
{
|
||||
@ -156,7 +154,7 @@ const SelectModel = ({ modalDetailsVisible, setModalDetailsVisible, insets, setS
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
</Modal>
|
||||
</Modal >
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
import { fetchApi } from "@/lib/server-api-util";
|
||||
import { Message } from "@/types/ask";
|
||||
import * as FileSystem from 'expo-file-system';
|
||||
import * as MediaLibrary from 'expo-media-library';
|
||||
import { useCallback } from "react";
|
||||
import { Alert } from 'react-native';
|
||||
|
||||
// 实现一个函数,从两个数组中轮流插入新数组
|
||||
export const mergeArrays = (arr1: any[], arr2: any[]) => {
|
||||
@ -49,4 +52,77 @@ export const getConversation = async ({
|
||||
// console.error('Error in getConversation:', error);
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
// 图片 视频 保存到本地
|
||||
export const saveMediaToGallery = async (mediaUrl: string) => {
|
||||
// 声明 fileUri 变量以便在 finally 块中使用
|
||||
let fileUri: string | null = null;
|
||||
|
||||
try {
|
||||
// 首先请求权限
|
||||
const { status } = await MediaLibrary.requestPermissionsAsync();
|
||||
|
||||
if (status !== 'granted') {
|
||||
Alert.alert('需要相册权限', '请允许应用访问相册以保存媒体文件');
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取文件扩展名
|
||||
const fileExtension = mediaUrl.split('.').pop()?.toLowerCase() || 'mp4';
|
||||
const isVideo = ['mp4', 'mov', 'avi', 'mkv'].includes(fileExtension);
|
||||
const fileName = `temp_${Date.now()}.${fileExtension}`;
|
||||
fileUri = `${FileSystem.documentDirectory}${fileName}`;
|
||||
|
||||
// 下载文件
|
||||
console.log('开始下载文件:', mediaUrl);
|
||||
const downloadResumable = FileSystem.createDownloadResumable(
|
||||
mediaUrl,
|
||||
fileUri,
|
||||
{},
|
||||
(downloadProgress) => {
|
||||
const progress = downloadProgress.totalBytesWritten / (downloadProgress.totalBytesExpectedToWrite || 1);
|
||||
console.log(`下载进度: ${Math.round(progress * 100)}%`);
|
||||
}
|
||||
);
|
||||
|
||||
const downloadResult = await downloadResumable.downloadAsync();
|
||||
|
||||
if (!downloadResult) {
|
||||
throw new Error('下载失败: 下载被取消或发生错误');
|
||||
}
|
||||
|
||||
const { uri } = downloadResult;
|
||||
console.log('文件下载完成,准备保存到相册:', uri);
|
||||
|
||||
// 保存到相册
|
||||
const asset = await MediaLibrary.createAssetAsync(uri);
|
||||
await MediaLibrary.createAlbumAsync(
|
||||
'Memowake',
|
||||
asset,
|
||||
false
|
||||
);
|
||||
|
||||
Alert.alert(
|
||||
'保存成功',
|
||||
isVideo ? '视频已保存到相册' : '图片已保存到相册'
|
||||
);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('保存失败:', error);
|
||||
Alert.alert(
|
||||
'保存失败',
|
||||
error instanceof Error ? error.message : '保存媒体文件时出错,请重试'
|
||||
);
|
||||
return false;
|
||||
} finally {
|
||||
// 清理临时文件
|
||||
try {
|
||||
if (fileUri) {
|
||||
await FileSystem.deleteAsync(fileUri, { idempotent: true }).catch(console.warn);
|
||||
}
|
||||
} catch (cleanupError) {
|
||||
console.warn('清理临时文件时出错:', cleanupError);
|
||||
}
|
||||
}
|
||||
};
|
||||
Loading…
x
Reference in New Issue
Block a user