feat: 上传进度条组件
This commit is contained in:
parent
e2c5493c8c
commit
d35ee35bad
@ -23,7 +23,6 @@ export default function AskScreen() {
|
||||
const insets = useSafeAreaInsets();
|
||||
useEffect(() => {
|
||||
checkAuthStatus(router);
|
||||
router.replace('/login');
|
||||
}, []);
|
||||
// 在组件内部添加 ref
|
||||
const scrollViewRef = useRef<ScrollView>(null);
|
||||
|
||||
@ -1,14 +1,16 @@
|
||||
import ChatSvg from "@/assets/icons/svg/chat.svg";
|
||||
import AutoUploadScreen from "@/components/file-upload/autoUploadScreen";
|
||||
import AskNavbar from "@/components/layout/ask";
|
||||
import { getUploadTasks, UploadTask } from "@/lib/db";
|
||||
import { fetchApi } from "@/lib/server-api-util";
|
||||
import { Chat } from "@/types/ask";
|
||||
import { router } from "expo-router";
|
||||
import React, { useEffect } from 'react';
|
||||
import { router, useFocusEffect } from "expo-router";
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { FlatList, Platform, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
const MemoList = () => {
|
||||
const insets = useSafeAreaInsets();
|
||||
|
||||
const [uploadTasks, setUploadTasks] = useState<UploadTask[]>([]); // 新增上传任务状态
|
||||
// 历史消息
|
||||
const [historyList, setHistoryList] = React.useState<Chat[]>([]);
|
||||
|
||||
@ -39,8 +41,37 @@ const MemoList = () => {
|
||||
getHistoryList()
|
||||
}, [])
|
||||
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
// 设置定时器,每秒查询一次上传进度
|
||||
const intervalId = setInterval(async () => {
|
||||
const tasks = await getUploadTasks();
|
||||
setUploadTasks(tasks);
|
||||
}, 1000);
|
||||
|
||||
return () => clearInterval(intervalId); // 清理定时器
|
||||
}, [])
|
||||
);
|
||||
|
||||
return (
|
||||
<View style={[styles.container, { paddingTop: insets.top }]}>
|
||||
{/* 上传进度展示区域 */}
|
||||
|
||||
<View className="w-full h-80">
|
||||
{uploadTasks.length >= 0 && (
|
||||
<View style={{ padding: 10, backgroundColor: '#f0f0f0', borderBottomWidth: 1, borderBottomColor: '#ccc' }}>
|
||||
<Text style={{ fontWeight: 'bold', marginBottom: 5 }}>上传任务:</Text>
|
||||
{uploadTasks.map((task) => (
|
||||
<Text key={task.uri}>
|
||||
{task.filename}: {task.status} ({task.progress}%)
|
||||
</Text>
|
||||
))}
|
||||
</View>
|
||||
)}
|
||||
<AutoUploadScreen />
|
||||
</View>
|
||||
|
||||
{/* 顶部标题和上传按钮 */}
|
||||
<View style={styles.header}>
|
||||
<Text style={styles.title}>Memo List</Text>
|
||||
|
||||
@ -3,13 +3,12 @@ import Done from '@/components/user-message.tsx/done';
|
||||
import Look from '@/components/user-message.tsx/look';
|
||||
import UserName from '@/components/user-message.tsx/userName';
|
||||
import { checkAuthStatus } from '@/lib/auth';
|
||||
import { getUploadTasks, UploadTask } from '@/lib/db';
|
||||
import { FileUploadItem } from '@/lib/background-uploader/types';
|
||||
import { fetchApi } from '@/lib/server-api-util';
|
||||
import { FileUploadItem } from '@/types/upload';
|
||||
import { User } from '@/types/user';
|
||||
import { useLocalSearchParams, useRouter } from 'expo-router';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { KeyboardAvoidingView, Platform, ScrollView, StatusBar, Text, View } from 'react-native';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { KeyboardAvoidingView, Platform, ScrollView, StatusBar, View } from 'react-native';
|
||||
export type Steps = "userName" | "look" | "choice" | "done";
|
||||
export default function UserMessage() {
|
||||
const router = useRouter();
|
||||
@ -20,7 +19,6 @@ export default function UserMessage() {
|
||||
const [fileData, setFileData] = useState<FileUploadItem[]>([])
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [userInfo, setUserInfo] = useState<User | null>(null);
|
||||
const [uploadTasks, setUploadTasks] = useState<UploadTask[]>([]); // 新增上传任务状态
|
||||
const statusBarHeight = StatusBar?.currentHeight ?? 0;
|
||||
|
||||
// 获取路由参数
|
||||
@ -29,14 +27,6 @@ export default function UserMessage() {
|
||||
|
||||
useEffect(() => {
|
||||
checkAuthStatus(router);
|
||||
|
||||
// 设置定时器,每秒查询一次上传进度
|
||||
const intervalId = setInterval(async () => {
|
||||
const tasks = await getUploadTasks();
|
||||
setUploadTasks(tasks);
|
||||
}, 1000);
|
||||
|
||||
return () => clearInterval(intervalId); // 清理定时器
|
||||
}, []);
|
||||
|
||||
// 获取用户信息
|
||||
@ -80,17 +70,6 @@ export default function UserMessage() {
|
||||
keyboardShouldPersistTaps="handled"
|
||||
bounces={false}
|
||||
>
|
||||
{/* 上传进度展示区域 */}
|
||||
{uploadTasks.length > 0 && (
|
||||
<View style={{ padding: 10, backgroundColor: '#f0f0f0', borderBottomWidth: 1, borderBottomColor: '#ccc' }}>
|
||||
<Text style={{ fontWeight: 'bold', marginBottom: 5 }}>上传任务:</Text>
|
||||
{uploadTasks.map((task) => (
|
||||
<Text key={task.uri}>
|
||||
{task.filename}: {task.status} ({task.progress}%)
|
||||
</Text>
|
||||
))}
|
||||
</View>
|
||||
)}
|
||||
<View className="h-full" key={steps}>
|
||||
{(() => {
|
||||
const components = {
|
||||
|
||||
@ -1,12 +1,20 @@
|
||||
import { registerBackgroundUploadTask } from '@/lib/background-uploader/automatic';
|
||||
import { triggerManualUpload } from '@/lib/background-uploader/manual';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { ActivityIndicator, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
||||
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
||||
import UploaderProgressBar from './uploader-progress-bar';
|
||||
|
||||
export default function AutoUploadScreen() {
|
||||
const [timeRange, setTimeRange] = useState('day');
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isRegistered, setIsRegistered] = useState(false);
|
||||
const [uploadProgress, setUploadProgress] = useState({
|
||||
totalCount: 0,
|
||||
uploadedCount: 0,
|
||||
currentFileUrl: '',
|
||||
uploadedSize: 0,
|
||||
totalSize: 0,
|
||||
});
|
||||
|
||||
// 注册后台任务
|
||||
useEffect(() => {
|
||||
@ -22,7 +30,19 @@ export default function AutoUploadScreen() {
|
||||
const handleManualUpload = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
await triggerManualUpload(getDateRange(timeRange)[0], getDateRange(timeRange)[1]);
|
||||
await triggerManualUpload(
|
||||
getDateRange(timeRange)[0],
|
||||
getDateRange(timeRange)[1],
|
||||
(progress) => {
|
||||
setUploadProgress({
|
||||
totalCount: progress.totalCount,
|
||||
uploadedCount: progress.uploadedCount,
|
||||
currentFileUrl: progress.currentAsset.uri,
|
||||
uploadedSize: progress.uploadedBytes,
|
||||
totalSize: progress.totalBytes,
|
||||
});
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Upload error:', error);
|
||||
} finally {
|
||||
@ -127,10 +147,13 @@ export default function AutoUploadScreen() {
|
||||
</View>
|
||||
|
||||
{isLoading && (
|
||||
<View style={styles.loadingContainer}>
|
||||
<ActivityIndicator size="large" color="#0000ff" />
|
||||
<Text style={styles.loadingText}>正在上传,请稍候...</Text>
|
||||
</View>
|
||||
<UploaderProgressBar
|
||||
imageUrl={uploadProgress.currentFileUrl}
|
||||
uploadedSize={uploadProgress.uploadedSize}
|
||||
totalSize={uploadProgress.totalSize}
|
||||
uploadedCount={uploadProgress.uploadedCount}
|
||||
totalCount={uploadProgress.totalCount}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
|
||||
@ -158,7 +158,19 @@ export const ImagesUploader: React.FC<ImagesuploaderProps> = ({
|
||||
try {
|
||||
// 统一通过 lib 的 uploadFileWithProgress 实现上传
|
||||
const uploadUrlData = await getUploadUrl(task.file, { ...task.metadata, GPSVersionID: undefined });
|
||||
await uploadFileWithProgress(task.file, uploadUrlData.upload_url, updateProgress, 30000);
|
||||
const taskIndex = uploadTasks.indexOf(task);
|
||||
const totalTasks = uploadTasks.length;
|
||||
const baseProgress = (taskIndex / totalTasks) * 100;
|
||||
|
||||
await uploadFileWithProgress(
|
||||
task.file,
|
||||
uploadUrlData.upload_url,
|
||||
(progress) => {
|
||||
const taskProgress = progress.total > 0 ? (progress.loaded / progress.total) * (100 / totalTasks) : 0;
|
||||
updateProgress(baseProgress + taskProgress);
|
||||
},
|
||||
30000
|
||||
);
|
||||
const result = await confirmUpload(uploadUrlData.file_id);
|
||||
uploadResultsList.push({ ...result, file_id: uploadUrlData.file_id, upload_url: uploadUrlData.upload_url });
|
||||
} catch (error) {
|
||||
|
||||
119
components/file-upload/uploader-progress-bar.tsx
Normal file
119
components/file-upload/uploader-progress-bar.tsx
Normal file
@ -0,0 +1,119 @@
|
||||
import React from 'react';
|
||||
import { Image, StyleSheet, Text, View } from 'react-native';
|
||||
import * as Progress from 'react-native-progress';
|
||||
|
||||
// Helper to format bytes into a readable string (e.g., KB, MB)
|
||||
const formatBytes = (bytes: number, decimals = 1) => {
|
||||
if (bytes === 0) return '0 Bytes';
|
||||
const k = 1024;
|
||||
const dm = decimals < 0 ? 0 : decimals;
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;
|
||||
};
|
||||
|
||||
interface UploaderProgressBarProps {
|
||||
imageUrl: string;
|
||||
uploadedSize: number; // in bytes
|
||||
totalSize: number; // in bytes
|
||||
uploadedCount: number;
|
||||
totalCount: number;
|
||||
}
|
||||
|
||||
const UploaderProgressBar: React.FC<UploaderProgressBarProps> = ({
|
||||
imageUrl,
|
||||
uploadedSize,
|
||||
totalSize,
|
||||
uploadedCount,
|
||||
totalCount,
|
||||
}) => {
|
||||
const progress = totalSize > 0 ? uploadedSize / totalSize : 0;
|
||||
// The image shows 1.1M/6.3M, so we format the bytes
|
||||
const formattedUploadedSize = formatBytes(uploadedSize, 1).replace(' ', '');
|
||||
const formattedTotalSize = formatBytes(totalSize, 1).replace(' ', '');
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View style={styles.imageContainer}>
|
||||
<Image source={{ uri: imageUrl }} style={styles.thumbnail} />
|
||||
</View>
|
||||
<View style={styles.progressSection}>
|
||||
<View style={styles.progressInfo}>
|
||||
<Text style={styles.progressText}>{`${formattedUploadedSize}/${formattedTotalSize}`}</Text>
|
||||
<Text style={styles.statusText}>Uploading...</Text>
|
||||
</View>
|
||||
<Progress.Bar
|
||||
progress={progress}
|
||||
width={null} // Fills the container
|
||||
height={4}
|
||||
color={'#4A4A4A'}
|
||||
unfilledColor={'rgba(255, 255, 255, 0.5)'}
|
||||
borderWidth={0}
|
||||
style={styles.progressBar}
|
||||
/>
|
||||
</View>
|
||||
<Text style={styles.countText}>{`${uploadedCount}/${totalCount}`}</Text>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#F5B941', // From image
|
||||
borderRadius: 25,
|
||||
paddingHorizontal: 8,
|
||||
paddingVertical: 8,
|
||||
marginHorizontal: 15,
|
||||
height: 50,
|
||||
},
|
||||
imageContainer: {
|
||||
width: 40,
|
||||
height: 40,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
// This container helps with the skewed frame effect
|
||||
transform: [{ rotate: '-5deg' }],
|
||||
marginRight: 8,
|
||||
},
|
||||
thumbnail: {
|
||||
width: 36,
|
||||
height: 36,
|
||||
borderRadius: 8,
|
||||
borderWidth: 2,
|
||||
borderColor: 'white',
|
||||
transform: [{ rotate: '5deg' }], // Counter-rotate to keep image straight
|
||||
},
|
||||
progressSection: {
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
marginRight: 15,
|
||||
},
|
||||
progressInfo: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'baseline',
|
||||
marginBottom: 4,
|
||||
},
|
||||
progressText: {
|
||||
color: '#4A4A4A',
|
||||
fontWeight: 'bold',
|
||||
fontSize: 12,
|
||||
marginRight: 8,
|
||||
},
|
||||
statusText: {
|
||||
color: '#4A4A4A',
|
||||
fontSize: 12,
|
||||
},
|
||||
progressBar: {
|
||||
width: '100%',
|
||||
},
|
||||
countText: {
|
||||
color: '#4A4A4A',
|
||||
fontWeight: 'bold',
|
||||
fontSize: 16,
|
||||
},
|
||||
});
|
||||
|
||||
export default UploaderProgressBar;
|
||||
@ -5,9 +5,7 @@ import { ThemedText } from '@/components/ThemedText';
|
||||
import { FileUploadItem } from '@/types/upload';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ActivityIndicator, Image, TouchableOpacity, View } from 'react-native';
|
||||
import AutoUploadScreen from '../file-upload/autoUploadScreen';
|
||||
import FilesUploader from '../file-upload/files-uploader';
|
||||
import MediaStatsScreen from '../file-upload/getTotal';
|
||||
|
||||
interface Props {
|
||||
setSteps?: (steps: Steps) => void;
|
||||
@ -64,8 +62,8 @@ export default function Look(props: Props) {
|
||||
</View>
|
||||
}
|
||||
/>
|
||||
<AutoUploadScreen />
|
||||
<MediaStatsScreen />
|
||||
{/* <AutoUploadScreen /> */}
|
||||
{/* <MediaStatsScreen /> */}
|
||||
</View>
|
||||
|
||||
<View className="w-full">
|
||||
|
||||
@ -12,7 +12,7 @@ export async function identityCheck(token: string) {
|
||||
},
|
||||
});
|
||||
const data = await res.json();
|
||||
return data.code != 0;
|
||||
return data.code == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -10,7 +10,17 @@ const CONCURRENCY_LIMIT = 10; // 同时最多上传10个文件
|
||||
const limit = pLimit(CONCURRENCY_LIMIT);
|
||||
|
||||
// 手动触发上传
|
||||
export const triggerManualUpload = async (startDate: Date, endDate: Date) => {
|
||||
export const triggerManualUpload = async (
|
||||
startDate: Date,
|
||||
endDate: Date,
|
||||
onProgress?: (progress: {
|
||||
totalCount: number;
|
||||
uploadedCount: number;
|
||||
totalBytes: number; // Overall total size
|
||||
uploadedBytes: number; // Overall uploaded size
|
||||
currentAsset: ExtendedAsset; // To show a thumbnail
|
||||
}) => void
|
||||
) => {
|
||||
try {
|
||||
const media = await getMediaByDateRange(startDate, endDate);
|
||||
if (media.length === 0) {
|
||||
@ -18,20 +28,41 @@ export const triggerManualUpload = async (startDate: Date, endDate: Date) => {
|
||||
return [];
|
||||
}
|
||||
|
||||
const uploadPromises = media.map((asset: ExtendedAsset) =>
|
||||
limit(async () => {
|
||||
const existingTask = await getUploadTaskStatus(asset.uri);
|
||||
if (!existingTask) {
|
||||
await insertUploadTask(asset.uri, asset.filename);
|
||||
} else if (existingTask.status === 'success' || existingTask.status === 'skipped') {
|
||||
console.log(`File ${asset.uri} already ${existingTask.status}, skipping processing.`);
|
||||
return null; // Skip processing if already successful or skipped
|
||||
}
|
||||
return processAndUploadMedia(asset);
|
||||
})
|
||||
);
|
||||
const progressMap = new Map<string, { loaded: number; total: number }>();
|
||||
|
||||
const results = await Promise.all(uploadPromises);
|
||||
const results = [];
|
||||
let uploadedCount = 0;
|
||||
|
||||
for (const asset of media) {
|
||||
const existingTask = await getUploadTaskStatus(asset.uri);
|
||||
if (!existingTask) {
|
||||
await insertUploadTask(asset.uri, asset.filename);
|
||||
} else if (existingTask.status === 'success' || existingTask.status === 'skipped') {
|
||||
console.log(`File ${asset.uri} already ${existingTask.status}, skipping processing.`);
|
||||
uploadedCount++; // Also count skipped files as 'processed'
|
||||
continue;
|
||||
}
|
||||
|
||||
const result = await limit(() => processAndUploadMedia(asset, (fileProgress) => {
|
||||
progressMap.set(asset.uri, fileProgress);
|
||||
const uploadedBytes = Array.from(progressMap.values()).reduce((sum, p) => sum + p.loaded, 0);
|
||||
const totalBytes = Array.from(progressMap.values()).reduce((sum, p) => sum + p.total, 0);
|
||||
onProgress?.({
|
||||
totalCount: media.length,
|
||||
uploadedCount,
|
||||
totalBytes,
|
||||
uploadedBytes,
|
||||
currentAsset: asset,
|
||||
});
|
||||
}));
|
||||
|
||||
if (result) {
|
||||
results.push(result);
|
||||
if(result.originalSuccess) {
|
||||
uploadedCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 过滤掉因为已上传而返回 null 的结果
|
||||
const finalResults = results.filter(result => result !== null);
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
import * as MediaLibrary from 'expo-media-library';
|
||||
import { MediaTypeValue } from 'expo-media-library';
|
||||
import { ExtendedAsset } from './types'; // Assuming ExtendedAsset is defined in types.ts
|
||||
|
||||
// Helper to fetch assets with pagination and EXIF info
|
||||
const fetchAssetsWithExif = async (
|
||||
mediaType: MediaLibrary.MediaType[],
|
||||
mediaType: MediaTypeValue[],
|
||||
createdAfter?: number,
|
||||
createdBefore?: number,
|
||||
descending: boolean = true // Default to descending
|
||||
@ -16,11 +17,10 @@ const fetchAssetsWithExif = async (
|
||||
const media = await MediaLibrary.getAssetsAsync({
|
||||
mediaType,
|
||||
first: 500, // Fetch in batches of 500
|
||||
sortBy: [MediaLibrary.SortBy.creationTime],
|
||||
sortBy: [MediaLibrary.SortBy.creationTime, descending],
|
||||
createdAfter,
|
||||
createdBefore,
|
||||
after,
|
||||
descending,
|
||||
});
|
||||
|
||||
allAssets = allAssets.concat(media.assets);
|
||||
@ -35,6 +35,7 @@ const fetchAssetsWithExif = async (
|
||||
const assetInfo = await MediaLibrary.getAssetInfoAsync(asset.id);
|
||||
return {
|
||||
...asset,
|
||||
|
||||
exif: assetInfo.exif || null,
|
||||
location: assetInfo.location || null
|
||||
};
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import * as MediaLibrary from 'expo-media-library';
|
||||
|
||||
export type ExtendedAsset = MediaLibrary.Asset & {
|
||||
exif?: Record<string, any>;
|
||||
size?: number;
|
||||
exif?: Record<string, any> | null;
|
||||
};
|
||||
|
||||
// 上传任务类型
|
||||
|
||||
@ -30,7 +30,10 @@ export const uploadFile = async (file: File, uploadUrl: string): Promise<void> =
|
||||
|
||||
|
||||
// 处理单个媒体文件上传的核心逻辑
|
||||
export const processAndUploadMedia = async (asset: ExtendedAsset) => {
|
||||
export const processAndUploadMedia = async (
|
||||
asset: ExtendedAsset,
|
||||
onProgress?: (progress: { loaded: number; total: number }) => void
|
||||
) => {
|
||||
try {
|
||||
// 1. 文件去重检查 (从数据库获取状态)
|
||||
const existingTask = await getUploadTaskStatus(asset.uri);
|
||||
@ -101,7 +104,9 @@ export const processAndUploadMedia = async (asset: ExtendedAsset) => {
|
||||
});
|
||||
|
||||
await uploadFileWithProgress(fileToUpload, upload_url, (progress) => {
|
||||
updateUploadTaskProgress(asset.uri, Math.round(progress * 0.5)); // 原始文件占总进度的50%
|
||||
if (onProgress) onProgress(progress);
|
||||
const percentage = progress.total > 0 ? (progress.loaded / progress.total) * 100 : 0;
|
||||
updateUploadTaskProgress(asset.uri, Math.round(percentage * 0.5)); // 原始文件占总进度的50%
|
||||
});
|
||||
await confirmUpload(file_id);
|
||||
|
||||
@ -127,7 +132,10 @@ export const processAndUploadMedia = async (asset: ExtendedAsset) => {
|
||||
});
|
||||
|
||||
await uploadFileWithProgress(compressedFile, upload_url, (progress) => {
|
||||
updateUploadTaskProgress(asset.uri, 50 + Math.round(progress * 0.5)); // 压缩文件占总进度的后50%
|
||||
// For compressed files, we can't easily report byte progress relative to the whole process,
|
||||
// as we don't know the compressed size in advance. We'll just update the DB progress.
|
||||
const percentage = progress.total > 0 ? (progress.loaded / progress.total) * 100 : 0;
|
||||
updateUploadTaskProgress(asset.uri, 50 + Math.round(percentage * 0.5)); // 压缩文件占总进度的后50%
|
||||
});
|
||||
await confirmUpload(file_id);
|
||||
return { success: true, file_id };
|
||||
@ -187,7 +195,7 @@ export const processAndUploadMedia = async (asset: ExtendedAsset) => {
|
||||
export const uploadFileWithProgress = async (
|
||||
file: File,
|
||||
uploadUrl: string,
|
||||
onProgress?: (progress: number) => void,
|
||||
onProgress?: (progress: { loaded: number; total: number }) => void,
|
||||
timeout: number = 30000
|
||||
): Promise<void> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
@ -201,8 +209,7 @@ export const uploadFileWithProgress = async (
|
||||
if (onProgress) {
|
||||
xhr.upload.onprogress = (event) => {
|
||||
if (event.lengthComputable) {
|
||||
const progress = Math.round((event.loaded / event.total) * 100);
|
||||
onProgress(progress);
|
||||
onProgress({ loaded: event.loaded, total: event.total });
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@ -78,7 +78,7 @@ export async function updateUploadTaskProgress(uri: string, progress: number): P
|
||||
|
||||
// 获取所有上传任务
|
||||
export async function getUploadTasks(): Promise<UploadTask[]> {
|
||||
console.log('Fetching all upload tasks...');
|
||||
console.log('Fetching all upload tasks... time:', new Date().toLocaleString());
|
||||
const results = db.getAllSync<UploadTask>(
|
||||
'SELECT uri, filename, status, progress, file_id FROM upload_tasks;'
|
||||
);
|
||||
|
||||
173
package-lock.json
generated
173
package-lock.json
generated
@ -13,6 +13,7 @@
|
||||
"@react-navigation/elements": "^2.3.8",
|
||||
"@react-navigation/native": "^7.1.6",
|
||||
"@reduxjs/toolkit": "^2.8.2",
|
||||
"@types/p-limit": "^2.2.0",
|
||||
"@types/react-redux": "^7.1.34",
|
||||
"expo": "~53.0.12",
|
||||
"expo-audio": "~0.4.7",
|
||||
@ -49,6 +50,7 @@
|
||||
"i18next-http-backend": "^3.0.2",
|
||||
"lottie-react-native": "7.2.2",
|
||||
"nativewind": "^4.1.23",
|
||||
"p-limit": "^6.2.0",
|
||||
"react": "19.0.0",
|
||||
"react-dom": "19.0.0",
|
||||
"react-i18next": "^15.5.3",
|
||||
@ -2521,6 +2523,21 @@
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/@expo/fingerprint/node_modules/p-limit": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "http://192.168.31.115:8081/repository/npm/p-limit/-/p-limit-3.1.0.tgz",
|
||||
"integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"yocto-queue": "^0.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/@expo/fingerprint/node_modules/semver": {
|
||||
"version": "7.7.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
|
||||
@ -2533,6 +2550,18 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@expo/fingerprint/node_modules/yocto-queue": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "http://192.168.31.115:8081/repository/npm/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
||||
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/@expo/image-utils": {
|
||||
"version": "0.7.6",
|
||||
"resolved": "https://registry.npmjs.org/@expo/image-utils/-/image-utils-0.7.6.tgz",
|
||||
@ -5924,6 +5953,16 @@
|
||||
"undici-types": "~7.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/p-limit": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "http://192.168.31.115:8081/repository/npm/@types/p-limit/-/p-limit-2.2.0.tgz",
|
||||
"integrity": "sha512-fGFbybl1r0oE9mqgfc2EHHUin9ZL5rbQIexWI6jYRU1ADVn4I3LHzT+g/kpPpZsfp8PB94CQ655pfAjNF8LP6A==",
|
||||
"deprecated": "This is a stub types definition. p-limit provides its own type definitions, so you do not need this installed.",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"p-limit": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/ramda": {
|
||||
"version": "0.27.66",
|
||||
"resolved": "https://registry.npmjs.org/@types/ramda/-/ramda-0.27.66.tgz",
|
||||
@ -12182,6 +12221,22 @@
|
||||
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-changed-files/node_modules/p-limit": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "http://192.168.31.115:8081/repository/npm/p-limit/-/p-limit-3.1.0.tgz",
|
||||
"integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"yocto-queue": "^0.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-changed-files/node_modules/picomatch": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "http://192.168.31.115:8081/repository/npm/picomatch/-/picomatch-4.0.3.tgz",
|
||||
@ -12195,6 +12250,19 @@
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-changed-files/node_modules/yocto-queue": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "http://192.168.31.115:8081/repository/npm/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
||||
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-circus": {
|
||||
"version": "30.0.4",
|
||||
"resolved": "http://192.168.31.115:8081/repository/npm/jest-circus/-/jest-circus-30.0.4.tgz",
|
||||
@ -12393,6 +12461,22 @@
|
||||
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-circus/node_modules/p-limit": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "http://192.168.31.115:8081/repository/npm/p-limit/-/p-limit-3.1.0.tgz",
|
||||
"integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"yocto-queue": "^0.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-circus/node_modules/picomatch": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "http://192.168.31.115:8081/repository/npm/picomatch/-/picomatch-4.0.3.tgz",
|
||||
@ -12428,6 +12512,19 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/jest-circus/node_modules/yocto-queue": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "http://192.168.31.115:8081/repository/npm/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
||||
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-cli": {
|
||||
"version": "30.0.4",
|
||||
"resolved": "http://192.168.31.115:8081/repository/npm/jest-cli/-/jest-cli-30.0.4.tgz",
|
||||
@ -14246,6 +14343,22 @@
|
||||
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-runner/node_modules/p-limit": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "http://192.168.31.115:8081/repository/npm/p-limit/-/p-limit-3.1.0.tgz",
|
||||
"integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"yocto-queue": "^0.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-runner/node_modules/picomatch": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "http://192.168.31.115:8081/repository/npm/picomatch/-/picomatch-4.0.3.tgz",
|
||||
@ -14345,6 +14458,19 @@
|
||||
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-runner/node_modules/yocto-queue": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "http://192.168.31.115:8081/repository/npm/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
||||
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-runtime": {
|
||||
"version": "30.0.4",
|
||||
"resolved": "http://192.168.31.115:8081/repository/npm/jest-runtime/-/jest-runtime-30.0.4.tgz",
|
||||
@ -17046,15 +17172,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/p-limit": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
|
||||
"integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
|
||||
"version": "6.2.0",
|
||||
"resolved": "http://192.168.31.115:8081/repository/npm/p-limit/-/p-limit-6.2.0.tgz",
|
||||
"integrity": "sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"yocto-queue": "^0.1.0"
|
||||
"yocto-queue": "^1.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
@ -17075,6 +17201,33 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/p-locate/node_modules/p-limit": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "http://192.168.31.115:8081/repository/npm/p-limit/-/p-limit-3.1.0.tgz",
|
||||
"integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"yocto-queue": "^0.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/p-locate/node_modules/yocto-queue": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "http://192.168.31.115:8081/repository/npm/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
||||
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/p-try": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
|
||||
@ -18088,7 +18241,7 @@
|
||||
},
|
||||
"node_modules/react-native-progress": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-progress/-/react-native-progress-5.0.1.tgz",
|
||||
"resolved": "http://192.168.31.115:8081/repository/npm/react-native-progress/-/react-native-progress-5.0.1.tgz",
|
||||
"integrity": "sha512-TYfJ4auAe5vubDma2yfFvt7ktSI+UCfysqJnkdHEcLXqAitRFOozgF/cLgN5VNi/iLdaf3ga1ETi2RF4jVZ/+g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@ -21317,12 +21470,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/yocto-queue": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
||||
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
|
||||
"version": "1.2.1",
|
||||
"resolved": "http://192.168.31.115:8081/repository/npm/yocto-queue/-/yocto-queue-1.2.1.tgz",
|
||||
"integrity": "sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
"node": ">=12.20"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
|
||||
0
scripts/android_local_build.sh
Normal file
0
scripts/android_local_build.sh
Normal file
Loading…
x
Reference in New Issue
Block a user