wellnua-lite/contexts/VoiceCallContext.tsx
Sergei aec300bd98 feat: Add floating bubble during voice calls
- 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>
2026-01-24 20:39:27 -08:00

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;
}