333 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import * as MediaLibrary from 'expo-media-library';
import React, { useState } from 'react';
import { ActivityIndicator, Alert, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
interface MediaStats {
total: number;
photos: number;
videos: number;
audios: number;
others: number;
byMonth: Record<string, number>;
}
type TimeRange = 'day' | 'week' | 'month' | 'all';
const MediaStatsScreen = () => {
const [isLoading, setIsLoading] = useState(false);
const [stats, setStats] = useState<MediaStats | null>(null);
const [timeRange, setTimeRange] = useState<TimeRange>('week'); // 默认显示一周
const getDateRange = (range: TimeRange) => {
const now = new Date();
const start = new Date(now);
switch (range) {
case 'day':
start.setDate(now.getDate() - 1);
break;
case 'week':
start.setDate(now.getDate() - 7);
break;
case 'month':
start.setMonth(now.getMonth() - 1);
break;
case 'all':
default:
return null; // 返回 null 表示不限制时间范围
}
return { start, end: now };
};
const getMediaStatistics = async () => {
setIsLoading(true);
try {
// 1. 请求媒体库权限
const { status } = await MediaLibrary.requestPermissionsAsync();
if (status !== 'granted') {
Alert.alert('权限被拒绝', '需要访问媒体库权限来获取统计信息');
return;
}
// 2. 设置时间范围
const dateRange = getDateRange(timeRange);
const createdAfter = dateRange ? Math.floor(dateRange.start.getTime() / 1000) : 0;
const endTime = dateRange?.end ? Math.floor(dateRange.end.getTime() / 1000) : undefined;
// 3. 分页获取媒体资源每次10条
let hasNextPage = true;
let after = undefined;
let allAssets: MediaLibrary.Asset[] = [];
const pageSize = 10; // 每次获取10条
while (hasNextPage) {
const media = await MediaLibrary.getAssetsAsync({
first: pageSize,
after,
mediaType: ['photo', 'video', 'audio', 'unknown'],
sortBy: 'creationTime', // 按创建时间降序,最新的在前面
createdAfter: Date.now() - 24 * 30 * 12 * 60 * 60 * 1000, // 时间戳(毫秒)
createdBefore: Date.now(), // 时间戳(毫秒)
});
// 如果没有数据,直接退出
if (media.assets.length === 0) {
break;
}
// 检查每条记录是否在时间范围内
for (const asset of media.assets) {
const assetTime = asset.creationTime ? new Date(asset.creationTime).getTime() / 1000 : 0;
// 如果设置了结束时间,并且当前记录的时间早于开始时间,则停止
if (endTime && assetTime > endTime) {
continue; // 跳过这条记录
}
// 如果设置了开始时间,并且当前记录的时间早于开始时间,则停止
if (createdAfter && assetTime < createdAfter) {
hasNextPage = false;
break;
}
allAssets.push(asset);
}
// 更新游标和是否还有下一页
hasNextPage = media.hasNextPage && media.assets.length === pageSize;
after = media.endCursor;
// 如果没有更多数据或者已经获取了足够的数据
if (!hasNextPage || allAssets.length >= 1000) {
break;
}
}
console.log(`总共获取到 ${allAssets.length} 个媒体文件`);
// 4. 统计不同类型媒体的数量
const stats: MediaStats = {
total: allAssets.length,
photos: 0,
videos: 0,
audios: 0,
others: 0,
byMonth: {},
};
allAssets.forEach(asset => {
// 统计类型
if (asset.mediaType === 'photo') stats.photos++;
else if (asset.mediaType === 'video') stats.videos++;
else if (asset.mediaType === 'audio') stats.audios++;
else stats.others++;
// 按月份统计
if (asset.creationTime) {
const date = new Date(asset.creationTime);
const monthKey = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`;
stats.byMonth[monthKey] = (stats.byMonth[monthKey] || 0) + 1;
}
});
setStats(stats);
} catch (error) {
console.error('获取媒体库统计信息失败:', error);
Alert.alert('错误', '获取媒体库统计信息失败');
} finally {
setIsLoading(false);
}
};
// 时间范围选择器
const TimeRangeSelector = () => (
<View style={styles.timeRangeContainer}>
{(['day', 'week', 'month', 'all'] as TimeRange[]).map((range) => (
<TouchableOpacity
key={range}
style={[
styles.timeRangeButton,
timeRange === range && styles.timeRangeButtonActive
]}
onPress={() => {
setTimeRange(range);
}}
disabled={isLoading}
>
<Text style={[
styles.timeRangeButtonText,
timeRange === range && styles.timeRangeButtonTextActive
]}>
{{
day: '今天',
week: '本周',
month: '本月',
all: '全部'
}[range]}
</Text>
</TouchableOpacity>
))}
</View>
);
return (
<View style={styles.container}>
<View style={styles.header}>
<Text style={styles.title}></Text>
</View>
<TimeRangeSelector />
<TouchableOpacity
style={[styles.button, isLoading && styles.buttonDisabled]}
onPress={getMediaStatistics}
disabled={isLoading}
>
{isLoading ? (
<ActivityIndicator color="#fff" />
) : (
<Text style={styles.buttonText}></Text>
)}
</TouchableOpacity>
{stats && (
<View style={styles.statsContainer}>
<View style={styles.statsRow}>
<StatItem label="总文件数" value={stats.total.toString()} />
<StatItem label="照片" value={stats.photos.toString()} />
</View>
<View style={styles.statsRow}>
<StatItem label="视频" value={stats.videos.toString()} />
<StatItem label="音频" value={stats.audios.toString()} />
<StatItem label="其他" value={stats.others.toString()} />
</View>
<View style={styles.monthlyContainer}>
<Text style={styles.sectionTitle}></Text>
{Object.entries(stats.byMonth)
.sort(([a], [b]) => b.localeCompare(a))
.map(([month, count]) => (
<View key={month} style={styles.monthlyItem}>
<Text style={styles.monthText}>{month}</Text>
<Text style={styles.countText}>{count} </Text>
</View>
))}
</View>
</View>
)}
</View>
);
};
const StatItem = ({ label, value }: { label: string; value: string }) => (
<View style={styles.statItem}>
<Text style={styles.statValue}>{value}</Text>
<Text style={styles.statLabel}>{label}</Text>
</View>
);
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
padding: 16,
},
header: {
marginBottom: 16,
alignItems: 'center',
},
title: {
fontSize: 20,
fontWeight: 'bold',
color: '#333',
},
timeRangeContainer: {
flexDirection: 'row',
justifyContent: 'space-between',
marginBottom: 16,
paddingHorizontal: 8,
},
timeRangeButton: {
paddingVertical: 8,
paddingHorizontal: 16,
borderRadius: 16,
backgroundColor: '#f0f0f0',
},
timeRangeButtonActive: {
backgroundColor: '#007AFF',
},
timeRangeButtonText: {
color: '#666',
fontSize: 14,
},
timeRangeButtonTextActive: {
color: '#fff',
fontWeight: '600',
},
button: {
backgroundColor: '#007AFF',
paddingVertical: 12,
borderRadius: 8,
alignItems: 'center',
marginBottom: 20,
},
buttonDisabled: {
opacity: 0.6,
},
buttonText: {
color: '#fff',
fontSize: 16,
fontWeight: '600',
},
statsContainer: {
backgroundColor: '#f8f8f8',
borderRadius: 12,
padding: 16,
},
statsRow: {
flexDirection: 'row',
justifyContent: 'space-between',
marginBottom: 16,
},
statItem: {
alignItems: 'center',
flex: 1,
},
statValue: {
fontSize: 20,
fontWeight: 'bold',
color: '#007AFF',
marginBottom: 4,
},
statLabel: {
fontSize: 12,
color: '#666',
},
monthlyContainer: {
marginTop: 16,
},
sectionTitle: {
fontSize: 16,
fontWeight: '600',
color: '#333',
marginBottom: 12,
},
monthlyItem: {
flexDirection: 'row',
justifyContent: 'space-between',
paddingVertical: 10,
borderBottomWidth: 1,
borderBottomColor: '#eee',
},
monthText: {
fontSize: 14,
color: '#333',
},
countText: {
fontSize: 14,
color: '#666',
},
});
export default MediaStatsScreen;