feat(call): Details card for the call-ended toast (#253)
The call-ended toast gets a Details button (and stays 10 s) that opens a card with who was in the call and the peak, connection verdict and reconnects, how long your / each person's / everyone's connection was poor (likely the server when everyone), RTT, packet loss and jitter, and your talk, mute, deafen and screenshare time, in technical wording. Deafened time is tracked by CallEmbed; the rest comes from the fork's new io.lotus.call_summary `details` (lotus.29). Settings → Calls gets "Summary After Calls" (on by default). Fixes #253 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
8784a8158c
commit
8645a55769
@@ -0,0 +1,84 @@
|
||||
import React from 'react';
|
||||
import FocusTrap from 'focus-trap-react';
|
||||
import {
|
||||
Box,
|
||||
config,
|
||||
Dialog,
|
||||
Header,
|
||||
Icon,
|
||||
IconButton,
|
||||
Icons,
|
||||
Overlay,
|
||||
OverlayBackdrop,
|
||||
OverlayCenter,
|
||||
Text,
|
||||
} from 'folds';
|
||||
import { useAtom } from 'jotai';
|
||||
import { callSummaryViewAtom } from '../../state/callSummary';
|
||||
import { useModalStyle } from '../../hooks/useModalStyle';
|
||||
import { stopPropagation } from '../../utils/keyboard';
|
||||
|
||||
/**
|
||||
* [Gitea #253] The post-call "Details" card: opened from the call-ended toast,
|
||||
* shows who was in the call, connection quality and whose side it was on,
|
||||
* network numbers and your own activity (technical wording).
|
||||
*/
|
||||
export function CallSummaryDialog() {
|
||||
const [view, setView] = useAtom(callSummaryViewAtom);
|
||||
const modalStyle = useModalStyle(400);
|
||||
if (!view) return null;
|
||||
const close = () => setView(undefined);
|
||||
return (
|
||||
<Overlay open backdrop={<OverlayBackdrop />}>
|
||||
<OverlayCenter>
|
||||
<FocusTrap
|
||||
focusTrapOptions={{
|
||||
initialFocus: false,
|
||||
onDeactivate: close,
|
||||
clickOutsideDeactivates: true,
|
||||
escapeDeactivates: stopPropagation,
|
||||
}}
|
||||
>
|
||||
<Dialog
|
||||
variant="Surface"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="call-summary-title"
|
||||
style={modalStyle}
|
||||
>
|
||||
<Header
|
||||
variant="Surface"
|
||||
size="500"
|
||||
style={{
|
||||
padding: `0 ${config.space.S200} 0 ${config.space.S400}`,
|
||||
borderBottomWidth: config.borderWidth.B300,
|
||||
}}
|
||||
>
|
||||
<Box grow="Yes" alignItems="Center" gap="200">
|
||||
<Icon src={Icons.Phone} size="100" />
|
||||
<Text id="call-summary-title" size="H4" truncate>
|
||||
Call summary · {view.roomName}
|
||||
</Text>
|
||||
</Box>
|
||||
<IconButton size="300" radii="300" onClick={close} aria-label="Close">
|
||||
<Icon src={Icons.Cross} />
|
||||
</IconButton>
|
||||
</Header>
|
||||
<Box direction="Column" gap="400" style={{ padding: config.space.S400 }}>
|
||||
{view.sections.map((section) => (
|
||||
<Box key={section.title} direction="Column" gap="100">
|
||||
<Text size="L400">{section.title}</Text>
|
||||
{section.lines.map((line) => (
|
||||
<Text key={line} size="T300" style={{ fontVariantNumeric: 'tabular-nums' }}>
|
||||
{line}
|
||||
</Text>
|
||||
))}
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
</Dialog>
|
||||
</FocusTrap>
|
||||
</OverlayCenter>
|
||||
</Overlay>
|
||||
);
|
||||
}
|
||||
@@ -1749,6 +1749,7 @@ function Calls() {
|
||||
);
|
||||
|
||||
const [pttMode, setPttMode] = useSetting(settingsAtom, 'pttMode');
|
||||
const [callSummaryToast, setCallSummaryToast] = useSetting(settingsAtom, 'callSummaryToast');
|
||||
const [callRejoin, setCallRejoin] = useSetting(settingsAtom, 'callRejoinAfterRestart');
|
||||
const [haptics, setHaptics] = useSetting(settingsAtom, 'hapticFeedback');
|
||||
const [pttKey, setPttKey] = useSetting(settingsAtom, 'pttKey');
|
||||
@@ -2062,6 +2063,13 @@ function Calls() {
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<SettingTile
|
||||
title="Summary After Calls"
|
||||
description="When a call ends, show how long it was and how the connection held up, with a Details button for who was there, network numbers and your own talk/mute time."
|
||||
after={
|
||||
<Switch variant="Primary" value={callSummaryToast} onChange={setCallSummaryToast} />
|
||||
}
|
||||
/>
|
||||
{hapticsSupported() && (
|
||||
<SettingTile
|
||||
title="Haptic Feedback"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useEffect, useRef, CSSProperties } from 'react';
|
||||
import { useAtomValue, useSetAtom } from 'jotai';
|
||||
import { color, config, Icon, IconButton, Icons } from 'folds';
|
||||
import { Button, color, config, Icon, IconButton, Icons, Text } from 'folds';
|
||||
import { ScreenSize, useScreenSize } from '../../hooks/useScreenSize';
|
||||
import { toastQueueAtom, dismissToastAtom, ToastNotif } from '../../state/toast';
|
||||
import { useSetting } from '../../state/hooks/settings';
|
||||
@@ -43,13 +43,17 @@ function ToastCard({ toast }: ToastCardProps) {
|
||||
|
||||
useEffect(() => {
|
||||
if (toast.sticky) return;
|
||||
timerRef.current = setTimeout(() => {
|
||||
dismiss(toast.id);
|
||||
}, 4000);
|
||||
timerRef.current = setTimeout(
|
||||
() => {
|
||||
dismiss(toast.id);
|
||||
},
|
||||
// [Gitea #253] A toast with a button stays long enough to use it.
|
||||
toast.actionLabel ? 10_000 : 4000,
|
||||
);
|
||||
return () => {
|
||||
if (timerRef.current !== null) clearTimeout(timerRef.current);
|
||||
};
|
||||
}, [dismiss, toast.id, toast.sticky]);
|
||||
}, [dismiss, toast.id, toast.sticky, toast.actionLabel]);
|
||||
|
||||
const handleCardClick = () => {
|
||||
if (toast.onClick) {
|
||||
@@ -212,6 +216,24 @@ function ToastCard({ toast }: ToastCardProps) {
|
||||
</div>
|
||||
<div style={bodyStyle}>{toast.body}</div>
|
||||
{toast.roomName && <div style={roomNameStyle}>{toast.roomName}</div>}
|
||||
{toast.actionLabel && toast.onAction && (
|
||||
<div style={{ marginTop: config.space.S200 }}>
|
||||
<Button
|
||||
type="button"
|
||||
size="300"
|
||||
radii="300"
|
||||
variant="Secondary"
|
||||
fill="Soft"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
toast.onAction?.();
|
||||
dismiss(toast.id);
|
||||
}}
|
||||
>
|
||||
<Text size="B300">{toast.actionLabel}</Text>
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user