vojo/src/app/components/CallEmbedProvider.tsx

94 lines
3.3 KiB
TypeScript

import React, { ReactNode, useCallback, useEffect, useRef } from 'react';
import { useAtomValue, useSetAtom, useStore } from 'jotai';
import {
CallEmbedContextProvider,
CallEmbedRefContextProvider,
useCallCloseEvent,
useCallHangupEvent,
useCallJoined,
useCallThemeSync,
} from '../hooks/useCallEmbed';
import { callChatAtom, callEmbedAtom } from '../state/callEmbed';
import { CallEmbed } from '../plugins/call';
import { useSelectedRoom } from '../hooks/router/useSelectedRoom';
import { ScreenSize, useScreenSizeContext } from '../hooks/useScreenSize';
import { useAndroidCallForegroundSync } from '../hooks/useAndroidCallForegroundSync';
import { useTelecomConnectionSync } from '../hooks/useTelecomConnectionSync';
import { useOverLockCall } from '../hooks/useOverLockCall';
import { OverLockCallScreen } from '../features/call-status/OverLockCallScreen';
function CallUtils({ embed }: { embed: CallEmbed }) {
const setCallEmbed = useSetAtom(callEmbedAtom);
const store = useStore();
useCallThemeSync(embed);
const clearIfCurrent = useCallback(() => {
if (store.get(callEmbedAtom) !== embed) return;
setCallEmbed(undefined);
}, [store, embed, setCallEmbed]);
useCallHangupEvent(embed, clearIfCurrent);
// EC emits Close (not Hangup) when LiveKit gives up without explicit user
// action — e.g. reconnect fail after a network blip. Without this listener
// the embed lingers as a zombie and the Android FGS stays up.
useCallCloseEvent(embed, clearIfCurrent);
// Widget failed to prepare (bad network, iframe load error). Clear the atom
// so callEmbedAtom's setter can dispose the embed and the Android FGS
// (keyed on the atom) stops.
useEffect(() => {
const unsubscribe = embed.onPreparingError(clearIfCurrent);
return unsubscribe;
}, [embed, clearIfCurrent]);
return null;
}
type CallEmbedProviderProps = {
children?: ReactNode;
};
export function CallEmbedProvider({ children }: CallEmbedProviderProps) {
const callEmbed = useAtomValue(callEmbedAtom);
const callEmbedRef = useRef<HTMLDivElement>(null);
const joined = useCallJoined(callEmbed);
useAndroidCallForegroundSync();
// Self-managed Telecom session (docs/plans/telecom_migration.md). Android-only
// (no-op on web / iOS); keyed on the same joined signal as the FGS sync.
useTelecomConnectionSync();
// Over-lock call screen lifecycle (answer without unlock); Android-only.
useOverLockCall();
const selectedRoom = useSelectedRoom();
const chat = useAtomValue(callChatAtom);
const screenSize = useScreenSizeContext();
const chatOnlyView = chat && screenSize !== ScreenSize.Desktop;
const callVisible =
callEmbed &&
!callEmbed.voiceOnly &&
selectedRoom === callEmbed.roomId &&
joined &&
!chatOnlyView;
return (
<CallEmbedContextProvider value={callEmbed}>
{callEmbed && <CallUtils embed={callEmbed} />}
<CallEmbedRefContextProvider value={callEmbedRef}>{children}</CallEmbedRefContextProvider>
<div
data-call-embed-container
style={{
visibility: callVisible ? undefined : 'hidden',
pointerEvents: callVisible ? undefined : 'none',
position: 'fixed',
top: 0,
left: 0,
width: '100%',
height: '50%',
}}
ref={callEmbedRef}
/>
<OverLockCallScreen />
</CallEmbedContextProvider>
);
}