317 lines
9.9 KiB
TypeScript
Raw Permalink 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, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import i18n from '@/i18n';
import { PermissionService } from '@/lib/PermissionService';
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') {
PermissionService.show({ title: i18n.t('permission:title.permissionDenied'), message: i18n.t('permission:message.getStatsPermissionRequired') });
return;
}
// 2. 设置时间范围直接使用Date对象
const dateRange = getDateRange(timeRange);
// 3. 分页获取媒体资源
let allAssets: MediaLibrary.Asset[] = [];
let hasNextPage = true;
let after: MediaLibrary.AssetRef | undefined = undefined;
const pageSize = 100; // 增加每次获取的数量以提高效率
while (hasNextPage) {
const media = await MediaLibrary.getAssetsAsync({
first: pageSize,
after,
sortBy: ['creationTime'],
mediaType: ['photo', 'video', 'audio'],
createdAfter: dateRange?.start,
createdBefore: dateRange?.end,
});
if (media.assets.length > 0) {
allAssets.push(...media.assets);
}
hasNextPage = media.hasNextPage;
after = media.endCursor;
// 可选:增加一个最大获取上限,防止无限循环
if (allAssets.length > 2000) {
console.warn('已达到2000个媒体文件的上限');
break;
}
}
console.log(`总共获取到 ${allAssets.length} 个媒体文件`);
// 4. 使用 reduce 进行统计,更高效
const stats = allAssets.reduce<MediaStats>((acc, asset) => {
acc.total++;
switch (asset.mediaType) {
case 'photo':
acc.photos++;
break;
case 'video':
acc.videos++;
break;
case 'audio':
acc.audios++;
break;
default:
acc.others++;
break;
}
if (asset.creationTime) {
const date = new Date(asset.creationTime);
const monthKey = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`;
acc.byMonth[monthKey] = (acc.byMonth[monthKey] || 0) + 1;
}
return acc;
}, {
total: 0, photos: 0, videos: 0, audios: 0, others: 0, byMonth: {},
});
setStats(stats);
} catch (error) {
console.error('获取媒体库统计信息失败:', error);
PermissionService.show({ title: i18n.t('permission:title.error'), message: i18n.t('permission:message.getStatsFailed') });
} 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;