- Create VoiceCallContext for global voice call state management - Add FloatingCallBubble component with drag support - Add minimize button to voice call screen - Show bubble when call is minimized, tap to return to call - Button shows active call state with green color 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
138 lines
3.2 KiB
TypeScript
138 lines
3.2 KiB
TypeScript
/**
|
|
* Voice Call Context
|
|
*
|
|
* Global state for voice calls that persists across screens.
|
|
* Enables floating bubble when call is active and user navigates away.
|
|
*/
|
|
|
|
import React, { createContext, useContext, useState, useCallback, ReactNode } from 'react';
|
|
|
|
interface VoiceCallState {
|
|
// Whether a voice call is currently active
|
|
isActive: boolean;
|
|
// Whether the call UI is minimized (showing bubble instead of full screen)
|
|
isMinimized: boolean;
|
|
// LiveKit connection details
|
|
token: string | undefined;
|
|
wsUrl: string | undefined;
|
|
// Call metadata
|
|
beneficiaryName: string | undefined;
|
|
beneficiaryId: string | undefined;
|
|
// Call duration in seconds
|
|
callDuration: number;
|
|
}
|
|
|
|
interface VoiceCallContextValue {
|
|
// Current call state
|
|
callState: VoiceCallState;
|
|
|
|
// Start a new voice call
|
|
startCall: (params: {
|
|
token: string;
|
|
wsUrl: string;
|
|
beneficiaryName?: string;
|
|
beneficiaryId?: string;
|
|
}) => void;
|
|
|
|
// End the current call
|
|
endCall: () => void;
|
|
|
|
// Minimize call (show floating bubble)
|
|
minimizeCall: () => void;
|
|
|
|
// Maximize call (show full screen)
|
|
maximizeCall: () => void;
|
|
|
|
// Update call duration
|
|
updateDuration: (seconds: number) => void;
|
|
|
|
// Check if call is active
|
|
isCallActive: boolean;
|
|
}
|
|
|
|
const initialState: VoiceCallState = {
|
|
isActive: false,
|
|
isMinimized: false,
|
|
token: undefined,
|
|
wsUrl: undefined,
|
|
beneficiaryName: undefined,
|
|
beneficiaryId: undefined,
|
|
callDuration: 0,
|
|
};
|
|
|
|
const VoiceCallContext = createContext<VoiceCallContextValue | undefined>(undefined);
|
|
|
|
export function VoiceCallProvider({ children }: { children: ReactNode }) {
|
|
const [callState, setCallState] = useState<VoiceCallState>(initialState);
|
|
|
|
const startCall = useCallback((params: {
|
|
token: string;
|
|
wsUrl: string;
|
|
beneficiaryName?: string;
|
|
beneficiaryId?: string;
|
|
}) => {
|
|
console.log('[VoiceCallContext] Starting call');
|
|
setCallState({
|
|
isActive: true,
|
|
isMinimized: false,
|
|
token: params.token,
|
|
wsUrl: params.wsUrl,
|
|
beneficiaryName: params.beneficiaryName,
|
|
beneficiaryId: params.beneficiaryId,
|
|
callDuration: 0,
|
|
});
|
|
}, []);
|
|
|
|
const endCall = useCallback(() => {
|
|
console.log('[VoiceCallContext] Ending call');
|
|
setCallState(initialState);
|
|
}, []);
|
|
|
|
const minimizeCall = useCallback(() => {
|
|
console.log('[VoiceCallContext] Minimizing call');
|
|
setCallState(prev => ({
|
|
...prev,
|
|
isMinimized: true,
|
|
}));
|
|
}, []);
|
|
|
|
const maximizeCall = useCallback(() => {
|
|
console.log('[VoiceCallContext] Maximizing call');
|
|
setCallState(prev => ({
|
|
...prev,
|
|
isMinimized: false,
|
|
}));
|
|
}, []);
|
|
|
|
const updateDuration = useCallback((seconds: number) => {
|
|
setCallState(prev => ({
|
|
...prev,
|
|
callDuration: seconds,
|
|
}));
|
|
}, []);
|
|
|
|
return (
|
|
<VoiceCallContext.Provider
|
|
value={{
|
|
callState,
|
|
startCall,
|
|
endCall,
|
|
minimizeCall,
|
|
maximizeCall,
|
|
updateDuration,
|
|
isCallActive: callState.isActive,
|
|
}}
|
|
>
|
|
{children}
|
|
</VoiceCallContext.Provider>
|
|
);
|
|
}
|
|
|
|
export function useVoiceCall() {
|
|
const context = useContext(VoiceCallContext);
|
|
if (!context) {
|
|
throw new Error('useVoiceCall must be used within VoiceCallProvider');
|
|
}
|
|
return context;
|
|
}
|