2026-09-20 13:48:14 -04:00
|
|
|
import { useEffect, useRef } from 'react';
|
|
|
|
|
import { useSetAtom } from 'jotai';
|
|
|
|
|
import { CallEmbed } from '../plugins/call';
|
|
|
|
|
import { useCallHangupEvent } from './useCallEmbed';
|
|
|
|
|
import { toastQueueAtom } from '../state/toast';
|
2026-10-20 19:45:11 -04:00
|
|
|
import { describeCallDetails, describeCallEnd } from '../utils/callSummary';
|
|
|
|
|
import { callSummaryViewAtom } from '../state/callSummary';
|
|
|
|
|
import { useSetting } from '../state/hooks/settings';
|
|
|
|
|
import { settingsAtom } from '../state/settings';
|
2026-09-20 13:48:14 -04:00
|
|
|
|
|
|
|
|
// 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);
|
2026-10-20 19:45:11 -04:00
|
|
|
const setSummaryView = useSetAtom(callSummaryViewAtom);
|
|
|
|
|
const [callSummaryToast] = useSetting(settingsAtom, 'callSummaryToast');
|
2026-09-20 13:48:14 -04:00
|
|
|
const fired = useRef(false);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
fired.current = false;
|
|
|
|
|
}, [embed]);
|
|
|
|
|
|
|
|
|
|
useCallHangupEvent(embed, () => {
|
|
|
|
|
if (fired.current || !embed.joined || embed.joinedAt === undefined) return;
|
|
|
|
|
fired.current = true;
|
2026-10-20 19:45:11 -04:00
|
|
|
if (!callSummaryToast) return;
|
2026-09-20 13:48:14 -04:00
|
|
|
const durationMs = Date.now() - embed.joinedAt;
|
2026-10-20 19:45:11 -04:00
|
|
|
const deafenedMs = embed.deafenedMs();
|
|
|
|
|
const roomName = embed.room.name ?? 'Voice call';
|
|
|
|
|
const nameOf = (userId: string) => embed.room.getMember(userId)?.name ?? userId;
|
2026-09-20 13:48:14 -04:00
|
|
|
const toast = () =>
|
|
|
|
|
setToast({
|
|
|
|
|
id: `call-ended-${Date.now()}`,
|
|
|
|
|
displayName: 'Lotus Chat',
|
|
|
|
|
body: describeCallEnd(durationMs, embed.lastSummary),
|
2026-10-20 19:45:11 -04:00
|
|
|
roomName,
|
2026-09-20 13:48:14 -04:00
|
|
|
roomId: embed.roomId,
|
2026-10-20 19:45:11 -04:00
|
|
|
// [Gitea #253] The full breakdown, snapshotted at hangup.
|
|
|
|
|
actionLabel: 'Details',
|
|
|
|
|
onAction: () =>
|
|
|
|
|
setSummaryView({
|
|
|
|
|
roomName,
|
|
|
|
|
sections: describeCallDetails(embed.lastSummary, durationMs, deafenedMs, nameOf),
|
|
|
|
|
}),
|
2026-09-20 13:48:14 -04:00
|
|
|
});
|
|
|
|
|
if (embed.lastSummary) toast();
|
|
|
|
|
else setTimeout(toast, SUMMARY_GRACE_MS);
|
|
|
|
|
});
|
|
|
|
|
}
|