import React, { useEffect, useState } from 'react'; import { View, Text, StyleSheet, TouchableOpacity, Image, StatusBar, } from 'react-native'; import { MaterialCommunityIcons } from '@expo/vector-icons'; import { callStore } from '../../stores/callStore'; import { RTCView } from 'react-native-webrtc'; const formatDuration = (seconds: number): string => { const mins = Math.floor(seconds / 60); const secs = seconds % 60; return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`; }; const CallScreen: React.FC = () => { const currentCall = callStore((s) => s.currentCall); const callDuration = callStore((s) => s.callDuration); const peerStream = callStore((s) => s.peerStream); const localStream = callStore((s) => s.localStream); const endCall = callStore((s) => s.endCall); const toggleMute = callStore((s) => s.toggleMute); const toggleSpeaker = callStore((s) => s.toggleSpeaker); const toggleVideo = callStore((s) => s.toggleVideo); const isMinimized = callStore((s) => s.isMinimized); const toggleMinimize = callStore((s) => s.toggleMinimize); // Track whether peer has video const [hasPeerVideo, setHasPeerVideo] = useState(false); // Track whether local has video const [hasLocalVideo, setHasLocalVideo] = useState(false); // Check peer stream for video tracks useEffect(() => { if (peerStream) { const videoTracks = peerStream.getVideoTracks(); const hasVideo = videoTracks.length > 0 && videoTracks.some((t) => t.enabled); setHasPeerVideo(hasVideo); } else { setHasPeerVideo(false); } }, [peerStream]); // Check local stream for video tracks useEffect(() => { if (localStream) { const videoTracks = localStream.getVideoTracks(); const hasVideo = videoTracks.length > 0 && videoTracks.some((t) => t.enabled); setHasLocalVideo(hasVideo); } else { setHasLocalVideo(false); } }, [localStream]); // Don't render full screen when minimized or no call if (!currentCall || isMinimized) return null; const getStatusText = (): string => { switch (currentCall.status) { case 'ringing': return '正在等待对方接听...'; case 'connecting': return '连接中...'; case 'connected': return formatDuration(callDuration); case 'ending': return '通话已结束'; default: return ''; } }; const handleEndCall = () => { endCall('hangup'); }; const handleToggleMute = () => { toggleMute(); }; const handleToggleSpeaker = () => { toggleSpeaker(); }; const handleToggleVideo = () => { toggleVideo(); }; const handleMinimize = () => { toggleMinimize(); }; // Determine if we should show video UI const showRemoteVideo = hasPeerVideo; const showLocalVideo = hasLocalVideo; const isVideoCallActive = showRemoteVideo || showLocalVideo; return ( {/* Background */} {/* Remote video - full screen when peer has video */} {showRemoteVideo && peerStream && ( )} {/* Local video - picture in picture */} {showLocalVideo && localStream && ( )} {/* Top area: minimize button */} {/* Center: Peer info - only show when no video */} {!isVideoCallActive && ( {currentCall.peerAvatar ? ( ) : ( {currentCall.peerName?.charAt(0).toUpperCase() || '?'} )} {currentCall.peerName || '未知用户'} {getStatusText()} )} {/* Peer name overlay when video is active */} {isVideoCallActive && ( {currentCall.peerName || '未知用户'} {getStatusText()} )} {/* Bottom controls */} {/* Mute */} {currentCall.isMuted ? '取消静音' : '静音'} {/* Video toggle */} {hasLocalVideo ? '关闭摄像头' : '打开摄像头'} {/* Speaker */} {currentCall.isSpeakerOn ? '免提' : '听筒'} {/* End call - prominent red button */} 挂断 ); }; const CONTROL_CIRCLE_SIZE = 56; const END_CALL_SIZE = 64; const styles = StyleSheet.create({ container: { ...StyleSheet.absoluteFillObject, backgroundColor: '#1A1A2E', zIndex: 9999, }, background: { ...StyleSheet.absoluteFillObject, backgroundColor: '#1A1A2E', }, topBar: { position: 'absolute', top: 50, left: 0, right: 0, flexDirection: 'row', justifyContent: 'center', alignItems: 'center', height: 44, zIndex: 100, }, topButton: { width: 44, height: 44, borderRadius: 22, backgroundColor: 'rgba(255, 255, 255, 0.1)', justifyContent: 'center', alignItems: 'center', }, centerArea: { position: 'absolute', top: '28%', left: 0, right: 0, alignItems: 'center', paddingHorizontal: 30, }, avatarOuter: { marginBottom: 16, }, avatar: { width: 100, height: 100, borderRadius: 50, backgroundColor: '#3A3A5C', }, avatarPlaceholder: { justifyContent: 'center', alignItems: 'center', }, avatarText: { fontSize: 40, color: '#fff', fontWeight: '600', }, peerName: { fontSize: 22, fontWeight: '600', color: '#FFFFFF', marginBottom: 6, textAlign: 'center', maxWidth: 280, }, status: { fontSize: 14, color: 'rgba(255, 255, 255, 0.5)', letterSpacing: 0.3, }, fullScreenVideo: { ...StyleSheet.absoluteFillObject, backgroundColor: '#1A1A2E', }, localVideoContainer: { position: 'absolute', top: 100, right: 20, width: 120, height: 160, borderRadius: 16, overflow: 'hidden', backgroundColor: '#2A2A4E', borderWidth: 2, borderColor: 'rgba(255, 255, 255, 0.2)', zIndex: 50, }, localVideo: { width: '100%', height: '100%', }, videoOverlayInfo: { position: 'absolute', top: 100, left: 0, right: 0, alignItems: 'center', zIndex: 50, }, videoPeerName: { fontSize: 18, fontWeight: '600', color: '#FFFFFF', textShadowColor: 'rgba(0, 0, 0, 0.5)', textShadowOffset: { width: 0, height: 1 }, textShadowRadius: 2, }, videoStatus: { fontSize: 14, color: 'rgba(255, 255, 255, 0.8)', marginTop: 4, textShadowColor: 'rgba(0, 0, 0, 0.5)', textShadowOffset: { width: 0, height: 1 }, textShadowRadius: 2, }, controls: { position: 'absolute', bottom: 60, left: 0, right: 0, alignItems: 'center', zIndex: 100, }, controlRow: { flexDirection: 'row', justifyContent: 'center', gap: 24, marginBottom: 24, }, controlButton: { alignItems: 'center', width: 70, }, controlCircle: { width: 56, height: 56, borderRadius: 28, backgroundColor: 'rgba(255, 255, 255, 0.12)', justifyContent: 'center', alignItems: 'center', }, controlCircleActive: { backgroundColor: '#FFFFFF', }, controlButtonActive: {}, controlLabel: { fontSize: 11, color: 'rgba(255, 255, 255, 0.55)', marginTop: 8, textAlign: 'center', }, controlLabelActive: { color: '#FFFFFF', fontWeight: '500', }, endCallButton: { alignItems: 'center', }, endCallCircle: { width: 64, height: 64, borderRadius: 32, backgroundColor: '#E54D42', justifyContent: 'center', alignItems: 'center', }, endCallLabel: { fontSize: 11, color: 'rgba(255, 255, 255, 0.55)', marginTop: 8, }, }); export default CallScreen;