import React, { useEffect, useState, useCallback } from 'react'; import { StyleSheet, View, FlatList, RefreshControl, Alert } from 'react-native'; import { Text, Card, Button, useTheme, ActivityIndicator, Surface, IconButton, SegmentedButtons } from 'react-native-paper'; import { SafeAreaView } from 'react-native-safe-area-context'; import { useNavigation } from '@react-navigation/native'; import { getCameras, getClips } from '../api/video'; import { resolveUrl } from '../api/client'; import { formatDateTime, formatDuration, formatFileSize } from '../utils/format'; import type { Camera, VideoClip } from '../types'; export default function VideoScreen() { const theme = useTheme(); const navigation = useNavigation(); const [loading, setLoading] = useState(true); const [refreshing, setRefreshing] = useState(false); const [cameras, setCameras] = useState([]); const [clips, setClips] = useState([]); const [error, setError] = useState(null); const [tab, setTab] = useState('cameras'); const loadData = useCallback(async (isRefresh = false) => { if (isRefresh) setRefreshing(true); try { const [camerasData, clipsData] = await Promise.all([ getCameras().catch(() => [] as Camera[]), getClips({ limit: 20 }).catch(() => [] as VideoClip[]), ]); setCameras(camerasData); setClips(clipsData); setError(null); } catch (err: any) { setError(err?.message || '加载失败'); } finally { setLoading(false); setRefreshing(false); } }, []); useEffect(() => { loadData(); }, [loadData]); const handlePlay = async (camera: Camera) => { const streamUrl = resolveUrl(`/api/v1/video/cameras/${camera.id}/live/stream`); navigation.navigate('VideoPlayer', { cameraId: camera.id, cameraName: camera.name, streamUrl, }); }; const handlePlayClip = async (clip: VideoClip) => { if (clip.playbackUrl) { navigation.navigate('VideoPlayer', { cameraId: clip.cameraId, cameraName: `录像片段 ${clip.startAt ? formatDateTime(clip.startAt) : ''}`, streamUrl: resolveUrl(clip.playbackUrl), }); } else { Alert.alert('提示', '该录像暂无播放地址'); } }; const renderCamera = ({ item }: { item: Camera }) => { const isOnline = item.isOnline ?? item.online; return ( {item.name} 编号: {item.code} {item.position ? 📍 {item.position} : null} {item.resolution ? 分辨率: {item.resolution} : null} {isOnline ? '在线' : '离线'} ); }; const renderClip = ({ item }: { item: VideoClip }) => ( handlePlayClip(item)}> {item.trigger === 'alarm' ? '告警录像' : item.trigger === 'manual' ? '手动录像' : item.trigger === 'schedule' ? '定时录像' : '录像片段'} 开始: {formatDateTime(item.startAt)} 时长: {formatDuration(item.durationSec)} {item.resolution ? ` · ${item.resolution}` : ''} {item.sizeBytes ? ( 大小: {formatFileSize(item.sizeBytes)} ) : null} ); if (loading) { return ( ); } return ( String(item.id)} renderItem={(tab === 'cameras' ? renderCamera : renderClip) as any} contentContainerStyle={styles.list} refreshControl={ loadData(true)} /> } ListEmptyComponent={ error ? ( {error} ) : ( {tab === 'cameras' ? '暂无摄像头' : '暂无录像片段'} ) } /> ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#FAF8F5', }, center: { flex: 1, justifyContent: 'center', alignItems: 'center', }, filterContainer: { padding: 16, paddingBottom: 8, }, list: { padding: 16, paddingTop: 8, paddingBottom: 32, }, card: { marginBottom: 12, borderRadius: 14, backgroundColor: '#FFFFFF', }, cardContent: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingVertical: 8, }, cameraInfo: { flexDirection: 'row', alignItems: 'center', flex: 1, }, iconBox: { borderRadius: 12, }, cameraDetail: { flex: 1, marginLeft: 4, }, cameraName: { color: '#2D2A26', fontWeight: '500', }, cameraCode: { color: '#B8B3AA', fontSize: 12, marginTop: 2, }, cameraMeta: { color: '#B8B3AA', fontSize: 12, marginTop: 2, }, cameraRight: { alignItems: 'flex-end', gap: 6, }, statusBadge: { paddingHorizontal: 10, paddingVertical: 3, borderRadius: 12, elevation: 0, }, statusText: { color: 'white', fontSize: 11, fontWeight: '500', }, playBtn: { borderRadius: 999, }, clipInfo: { flexDirection: 'row', alignItems: 'center', flex: 1, }, clipDetail: { flex: 1, marginLeft: 4, }, clipTitle: { color: '#2D2A26', fontWeight: '500', }, clipMeta: { color: '#B8B3AA', fontSize: 12, marginTop: 2, }, emptyContainer: { alignItems: 'center', paddingTop: 64, }, emptyText: { color: '#B8B3AA', fontSize: 16, }, });