import { useState, useEffect } from "react"; import { useAppState, type CallStatus } from "@/pages/chat/state"; import useCall from "@/pages/chat/hooks/useCall"; import defaultAvatar from "@/images/default-avatar.png"; import { createPortal } from "react-dom"; import { id } from "@/utils/utils"; import { MaterialIconButton } from "@/utils/material"; export function CallWindow() { const { chat, toggleCallMinimize, user } = useAppState(); const { call } = chat; const { acceptCall, rejectCall, remoteAudioRef, endCall, toggleMute, toggleVideo, toggleScreenShare, localVideoRef, remoteVideoRef, localScreenShareRef, remoteScreenShareRef } = useCall(); const [pipPosition, setPipPosition] = useState({ x: window.innerWidth - 420, y: window.innerHeight - 320 }); const [isDragging, setIsDragging] = useState(false); const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 }); const [callDuration, setCallDuration] = useState(0); const [isVisible, setIsVisible] = useState(false); const [shouldRender, setShouldRender] = useState(false); const [wasMinimized, setWasMinimized] = useState(false); const [callData, setCallData] = useState<{ remoteUsername: string | null; status: CallStatus; isInitiator: boolean; isMuted: boolean; } | null>(null); const status = callData?.status || call.status; const remoteUsername = callData?.remoteUsername || call.remoteUsername; const isInitiator = callData?.isInitiator || call.isInitiator; const isMuted = callData?.isMuted || call.isMuted; useEffect(() => { let interval: NodeJS.Timeout; if (call.status === "active" && call.startTime) { interval = setInterval(() => { setCallDuration(Math.floor((Date.now() - call.startTime!) / 1000)); }, 1000); } else { setCallDuration(0); } return () => { if (interval) clearInterval(interval); }; }, [call.status, call.startTime]); // Preserve call data during exit animation useEffect(() => { if (call.isActive) { setCallData({ remoteUsername: call.remoteUsername, status: call.status, isInitiator: call.isInitiator, isMuted: call.isMuted }); } }, [call.isActive, call.remoteUsername, call.status, call.isInitiator, call.isMuted]); // Track minimized state for exit animation useEffect(() => { if (call.isActive) { setWasMinimized(call.isMinimized); } }, [call.isActive, call.isMinimized]); // Handle visibility animation with entrance and exit delays useEffect(() => { if (call.isActive) { setShouldRender(true); // Small delay to ensure DOM is ready, then trigger animation requestAnimationFrame(() => { requestAnimationFrame(() => { setIsVisible(true); }); }); } else { if (shouldRender) { // Call ended - start exit animation setIsVisible(false); // After animation completes, stop rendering const timer = setTimeout(() => { setShouldRender(false); setCallData(null); setWasMinimized(false); }, 400); // Match the CSS transition duration return () => clearTimeout(timer); } else { // Call not active and not rendered - ensure clean state setShouldRender(false); setIsVisible(false); setCallData(null); setWasMinimized(false); } } }, [call.isActive, shouldRender, wasMinimized]); // Handle dragging for PiP mode useEffect(() => { const handleMouseMove = (e: MouseEvent) => { if (isDragging && call.isMinimized) { setPipPosition({ x: e.clientX - dragOffset.x, y: e.clientY - dragOffset.y }); } }; const handleMouseUp = () => { if (isDragging) { setIsDragging(false); } }; if (isDragging) { window.addEventListener("mousemove", handleMouseMove); window.addEventListener("mouseup", handleMouseUp); } return () => { window.removeEventListener("mousemove", handleMouseMove); window.removeEventListener("mouseup", handleMouseUp); }; }, [isDragging, call.isMinimized, dragOffset]); // Cleanup effect to reset state when component unmounts useEffect(() => { return () => { setIsVisible(false); setShouldRender(false); setCallData(null); }; }, []); function formatDuration(seconds: number) { const mins = Math.floor(seconds / 60); const secs = seconds % 60; return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`; } function getStatusText() { switch (status) { case "calling": return "Calling..."; case "connecting": return "Connecting..."; case "active": return formatDuration(callDuration); default: return ""; } } function getGradientClass() { switch (status) { case "calling": return "gradient-calling"; case "connecting": return "gradient-connecting"; case "active": return "gradient-active"; default: return "gradient-default"; } } return ( createPortal( <> {shouldRender && (
{getStatusText()}
{!call.isMinimized && call.encryptionEmojis.length > 0 && (