import { useEffect, useRef } from 'react'; import { useSetAtom } from 'jotai'; import { CallEmbed } from '../plugins/call'; import { useCallHangupEvent } from './useCallEmbed'; import { toastQueueAtom } from '../state/toast'; import { describeCallDetails, describeCallEnd } from '../utils/callSummary'; import { callSummaryViewAtom } from '../state/callSummary'; import { useSetting } from '../state/hooks/settings'; import { settingsAtom } from '../state/settings'; // The fork sends io.lotus.call_summary on SFU disconnect, which normally lands // before Element Call's hangup echo; give a late one this long before toasting. const SUMMARY_GRACE_MS = 400; /** * [Gitea #143] "Call ended · 41 min · connection was good" in the existing * toast style when a call you were in ends. Duration comes from our own * join clock; the quality readout from the fork, if it arrived. */ export function useCallEndedToast(embed: CallEmbed): void { const setToast = useSetAtom(toastQueueAtom); const setSummaryView = useSetAtom(callSummaryViewAtom); const [callSummaryToast] = useSetting(settingsAtom, 'callSummaryToast'); const fired = useRef(false); useEffect(() => { fired.current = false; }, [embed]); useCallHangupEvent(embed, () => { if (fired.current || !embed.joined || embed.joinedAt === undefined) return; fired.current = true; if (!callSummaryToast) return; const durationMs = Date.now() - embed.joinedAt; const deafenedMs = embed.deafenedMs(); const roomName = embed.room.name ?? 'Voice call'; const nameOf = (userId: string) => embed.room.getMember(userId)?.name ?? userId; const toast = () => setToast({ id: `call-ended-${Date.now()}`, displayName: 'Lotus Chat', body: describeCallEnd(durationMs, embed.lastSummary), roomName, roomId: embed.roomId, // [Gitea #253] The full breakdown, snapshotted at hangup. actionLabel: 'Details', onAction: () => setSummaryView({ roomName, sections: describeCallDetails(embed.lastSummary, durationMs, deafenedMs, nameOf), }), }); if (embed.lastSummary) toast(); else setTimeout(toast, SUMMARY_GRACE_MS); }); }