chore: remove dead code — IncomingCallNotification and useIncomingDmCall
These are superseded by IncomingCallListener in CallEmbedProvider (merged from v4.12.1). IncomingCallNotification was already removed from Router.tsx in a previous commit.
This commit is contained in:
@@ -1,310 +0,0 @@
|
|||||||
import React, { useCallback, useEffect, useRef } from 'react';
|
|
||||||
import { Avatar, Box, Button, Text } from 'folds';
|
|
||||||
import { UserAvatar } from './user-avatar';
|
|
||||||
import { useMatrixClient } from '../hooks/useMatrixClient';
|
|
||||||
import { useCallStart } from '../hooks/useCallEmbed';
|
|
||||||
import { useSetting } from '../state/hooks/settings';
|
|
||||||
import { settingsAtom } from '../state/settings';
|
|
||||||
import { useIncomingDmCall } from '../hooks/useIncomingDmCall';
|
|
||||||
import { mxcUrlToHttp } from '../utils/matrix';
|
|
||||||
|
|
||||||
function useRingTone(active: boolean) {
|
|
||||||
const stopRef = useRef<(() => void) | null>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!active) {
|
|
||||||
stopRef.current?.();
|
|
||||||
stopRef.current = null;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let ctx: AudioContext;
|
|
||||||
try {
|
|
||||||
ctx = new AudioContext();
|
|
||||||
} catch {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let cancelled = false;
|
|
||||||
|
|
||||||
const pulse = (t: number, freq: number, dur: number) => {
|
|
||||||
const osc = ctx.createOscillator();
|
|
||||||
const gain = ctx.createGain();
|
|
||||||
osc.connect(gain);
|
|
||||||
gain.connect(ctx.destination);
|
|
||||||
osc.frequency.value = freq;
|
|
||||||
osc.type = 'sine';
|
|
||||||
gain.gain.setValueAtTime(0, t);
|
|
||||||
gain.gain.linearRampToValueAtTime(0.22, t + 0.04);
|
|
||||||
gain.gain.setValueAtTime(0.22, t + dur - 0.04);
|
|
||||||
gain.gain.linearRampToValueAtTime(0, t + dur);
|
|
||||||
osc.start(t);
|
|
||||||
osc.stop(t + dur);
|
|
||||||
};
|
|
||||||
|
|
||||||
let ringTimer: ReturnType<typeof setTimeout> | null = null;
|
|
||||||
const ring = () => {
|
|
||||||
if (cancelled) return;
|
|
||||||
const now = ctx.currentTime;
|
|
||||||
pulse(now, 880, 0.18);
|
|
||||||
pulse(now + 0.28, 880, 0.18);
|
|
||||||
ringTimer = setTimeout(ring, 2200);
|
|
||||||
};
|
|
||||||
|
|
||||||
ring();
|
|
||||||
|
|
||||||
const stop = () => {
|
|
||||||
cancelled = true;
|
|
||||||
if (ringTimer !== null) clearTimeout(ringTimer);
|
|
||||||
ctx.close();
|
|
||||||
};
|
|
||||||
|
|
||||||
stopRef.current = stop;
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
stop();
|
|
||||||
stopRef.current = null;
|
|
||||||
};
|
|
||||||
}, [active]);
|
|
||||||
|
|
||||||
return stopRef;
|
|
||||||
}
|
|
||||||
|
|
||||||
function IncomingCallCard() {
|
|
||||||
const mx = useMatrixClient();
|
|
||||||
const startCall = useCallStart(true);
|
|
||||||
const [lotusTerminal] = useSetting(settingsAtom, 'lotusTerminal');
|
|
||||||
const [incoming, dismiss] = useIncomingDmCall();
|
|
||||||
const stopRingRef = useRingTone(!!incoming);
|
|
||||||
|
|
||||||
const stopAndDismiss = useCallback(() => {
|
|
||||||
stopRingRef.current?.();
|
|
||||||
dismiss();
|
|
||||||
}, [dismiss, stopRingRef]);
|
|
||||||
|
|
||||||
const handleAnswer = useCallback(() => {
|
|
||||||
if (!incoming) return;
|
|
||||||
stopRingRef.current?.();
|
|
||||||
startCall(incoming.room);
|
|
||||||
dismiss();
|
|
||||||
}, [incoming, startCall, dismiss, stopRingRef]);
|
|
||||||
|
|
||||||
if (!incoming) return null;
|
|
||||||
|
|
||||||
const callerUser = mx.getUser(incoming.callerId);
|
|
||||||
const callerName = callerUser?.displayName ?? incoming.callerId;
|
|
||||||
const avatarMxc = callerUser?.avatarUrl;
|
|
||||||
const avatarUrl = avatarMxc ? mxcUrlToHttp(mx, avatarMxc, undefined, 64, 64, 'crop') ?? undefined : undefined;
|
|
||||||
|
|
||||||
if (lotusTerminal) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
position: 'fixed',
|
|
||||||
bottom: '80px',
|
|
||||||
right: '24px',
|
|
||||||
zIndex: 9999,
|
|
||||||
background: '#060c14',
|
|
||||||
border: '1px solid rgba(255,107,0,0.45)',
|
|
||||||
borderRadius: '4px',
|
|
||||||
boxShadow: '0 4px 32px rgba(255,107,0,0.18), 0 0 0 1px rgba(255,107,0,0.08)',
|
|
||||||
padding: '16px',
|
|
||||||
width: '272px',
|
|
||||||
fontFamily: "'JetBrains Mono', monospace",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
fontSize: '10px',
|
|
||||||
letterSpacing: '0.1em',
|
|
||||||
color: '#00D4FF',
|
|
||||||
marginBottom: '10px',
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: '6px',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
style={{
|
|
||||||
display: 'inline-block',
|
|
||||||
width: '7px',
|
|
||||||
height: '7px',
|
|
||||||
borderRadius: '50%',
|
|
||||||
background: '#00FF88',
|
|
||||||
boxShadow: '0 0 6px #00FF88',
|
|
||||||
animation: 'lotus-ring-pulse 1s ease-in-out infinite',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
INCOMING CALL
|
|
||||||
</div>
|
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: '10px', marginBottom: '14px' }}>
|
|
||||||
<Avatar size="300" style={{ border: '1px solid rgba(0,212,255,0.3)', borderRadius: '2px', overflow: 'hidden' }}>
|
|
||||||
<UserAvatar
|
|
||||||
userId={incoming.callerId}
|
|
||||||
src={avatarUrl}
|
|
||||||
alt={callerName}
|
|
||||||
renderFallback={() => (
|
|
||||||
<span style={{ fontSize: '16px', color: '#00D4FF', fontFamily: 'JetBrains Mono, monospace' }}>
|
|
||||||
{callerName[0]?.toUpperCase() ?? '?'}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</Avatar>
|
|
||||||
<div>
|
|
||||||
<div style={{ fontSize: '13px', color: '#e8edf5', fontWeight: 600 }}>{callerName}</div>
|
|
||||||
<div style={{ fontSize: '10px', color: 'rgba(232,237,245,0.45)', marginTop: '2px' }}>
|
|
||||||
{incoming.callerId}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div style={{ display: 'flex', gap: '8px' }}>
|
|
||||||
<button
|
|
||||||
onClick={handleAnswer}
|
|
||||||
style={{
|
|
||||||
flex: 1,
|
|
||||||
background: 'transparent',
|
|
||||||
border: '1px solid #00FF88',
|
|
||||||
borderRadius: '3px',
|
|
||||||
color: '#00FF88',
|
|
||||||
fontFamily: 'JetBrains Mono, monospace',
|
|
||||||
fontSize: '11px',
|
|
||||||
fontWeight: 700,
|
|
||||||
letterSpacing: '0.08em',
|
|
||||||
padding: '9px 4px',
|
|
||||||
cursor: 'pointer',
|
|
||||||
boxShadow: '0 0 10px rgba(0,255,136,0.2)',
|
|
||||||
transition: 'box-shadow 0.15s, background 0.15s',
|
|
||||||
}}
|
|
||||||
onMouseEnter={(e) => {
|
|
||||||
(e.currentTarget as HTMLButtonElement).style.background = 'rgba(0,255,136,0.08)';
|
|
||||||
(e.currentTarget as HTMLButtonElement).style.boxShadow = '0 0 16px rgba(0,255,136,0.35)';
|
|
||||||
}}
|
|
||||||
onMouseLeave={(e) => {
|
|
||||||
(e.currentTarget as HTMLButtonElement).style.background = 'transparent';
|
|
||||||
(e.currentTarget as HTMLButtonElement).style.boxShadow = '0 0 10px rgba(0,255,136,0.2)';
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
ANSWER
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={stopAndDismiss}
|
|
||||||
style={{
|
|
||||||
flex: 1,
|
|
||||||
background: 'transparent',
|
|
||||||
border: '1px solid rgba(255,107,0,0.5)',
|
|
||||||
borderRadius: '3px',
|
|
||||||
color: '#FF6B00',
|
|
||||||
fontFamily: 'JetBrains Mono, monospace',
|
|
||||||
fontSize: '11px',
|
|
||||||
fontWeight: 700,
|
|
||||||
letterSpacing: '0.08em',
|
|
||||||
padding: '9px 4px',
|
|
||||||
cursor: 'pointer',
|
|
||||||
transition: 'opacity 0.15s, background 0.15s',
|
|
||||||
}}
|
|
||||||
onMouseEnter={(e) => {
|
|
||||||
(e.currentTarget as HTMLButtonElement).style.background = 'rgba(255,107,0,0.08)';
|
|
||||||
}}
|
|
||||||
onMouseLeave={(e) => {
|
|
||||||
(e.currentTarget as HTMLButtonElement).style.background = 'transparent';
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
DECLINE
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<style>{`
|
|
||||||
@keyframes lotus-ring-pulse {
|
|
||||||
0%, 100% { opacity: 1; transform: scale(1); }
|
|
||||||
50% { opacity: 0.4; transform: scale(0.7); }
|
|
||||||
}
|
|
||||||
`}</style>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
position: 'fixed',
|
|
||||||
bottom: '80px',
|
|
||||||
right: '24px',
|
|
||||||
zIndex: 9999,
|
|
||||||
background: 'var(--bg-surface)',
|
|
||||||
border: '1px solid var(--bg-surface-border)',
|
|
||||||
borderRadius: '16px',
|
|
||||||
boxShadow: '0 8px 40px rgba(0,0,0,0.45)',
|
|
||||||
padding: '16px',
|
|
||||||
width: '272px',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
fontSize: '11px',
|
|
||||||
color: 'var(--text-secondary)',
|
|
||||||
marginBottom: '10px',
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: '6px',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
style={{
|
|
||||||
display: 'inline-block',
|
|
||||||
width: '7px',
|
|
||||||
height: '7px',
|
|
||||||
borderRadius: '50%',
|
|
||||||
background: 'var(--clr-success-main)',
|
|
||||||
animation: 'lotus-ring-pulse 1s ease-in-out infinite',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
Incoming call
|
|
||||||
</div>
|
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: '12px', marginBottom: '14px' }}>
|
|
||||||
<Avatar size="300">
|
|
||||||
<UserAvatar
|
|
||||||
userId={incoming.callerId}
|
|
||||||
src={avatarUrl}
|
|
||||||
alt={callerName}
|
|
||||||
renderFallback={() => (
|
|
||||||
<Text size="H4">{callerName[0]?.toUpperCase() ?? '?'}</Text>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</Avatar>
|
|
||||||
<div>
|
|
||||||
<Text size="T400" style={{ fontWeight: 600 }}>{callerName}</Text>
|
|
||||||
<Text size="T200" style={{ opacity: 0.5 }}>{incoming.callerId}</Text>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Box direction="Row" gap="200">
|
|
||||||
<Button
|
|
||||||
onClick={handleAnswer}
|
|
||||||
variant="Success"
|
|
||||||
fill="Solid"
|
|
||||||
size="400"
|
|
||||||
style={{ flex: 1 }}
|
|
||||||
>
|
|
||||||
<Text size="B400">Answer</Text>
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={stopAndDismiss}
|
|
||||||
variant="Critical"
|
|
||||||
fill="Solid"
|
|
||||||
size="400"
|
|
||||||
style={{ flex: 1 }}
|
|
||||||
>
|
|
||||||
<Text size="B400">Decline</Text>
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
<style>{`
|
|
||||||
@keyframes lotus-ring-pulse {
|
|
||||||
0%, 100% { opacity: 1; transform: scale(1); }
|
|
||||||
50% { opacity: 0.4; transform: scale(0.7); }
|
|
||||||
}
|
|
||||||
`}</style>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function IncomingCallNotification() {
|
|
||||||
return <IncomingCallCard />;
|
|
||||||
}
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
import { Room } from 'matrix-js-sdk';
|
|
||||||
import {
|
|
||||||
MatrixRTCSession,
|
|
||||||
MatrixRTCSessionEvent,
|
|
||||||
} from 'matrix-js-sdk/lib/matrixrtc/MatrixRTCSession';
|
|
||||||
import { MatrixRTCSessionManagerEvents } from 'matrix-js-sdk/lib/matrixrtc/MatrixRTCSessionManager';
|
|
||||||
import { useEffect, useRef, useState } from 'react';
|
|
||||||
import { useAtomValue } from 'jotai';
|
|
||||||
import { useMatrixClient } from './useMatrixClient';
|
|
||||||
import { callEmbedAtom } from '../state/callEmbed';
|
|
||||||
|
|
||||||
export type IncomingDmCall = {
|
|
||||||
room: Room;
|
|
||||||
callerId: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const isDmRoom = (room: Room): boolean =>
|
|
||||||
room.hasEncryptionStateEvent() && room.getMembers().length <= 2;
|
|
||||||
|
|
||||||
export const useIncomingDmCall = (): [IncomingDmCall | null, () => void] => {
|
|
||||||
const mx = useMatrixClient();
|
|
||||||
const callEmbed = useAtomValue(callEmbedAtom);
|
|
||||||
const [incoming, setIncoming] = useState<IncomingDmCall | null>(null);
|
|
||||||
const sessionRef = useRef<MatrixRTCSession | null>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const myUserId = mx.getUserId();
|
|
||||||
|
|
||||||
const handleSessionStarted = (roomId: string, session: MatrixRTCSession) => {
|
|
||||||
if (callEmbed) return;
|
|
||||||
const room = mx.getRoom(roomId);
|
|
||||||
if (!room || !isDmRoom(room)) return;
|
|
||||||
const memberships = session.memberships ?? [];
|
|
||||||
if (memberships.length === 0) return;
|
|
||||||
const callerMembership = memberships.find((m) => m.sender !== myUserId);
|
|
||||||
if (!callerMembership) return;
|
|
||||||
if (memberships.find((m) => m.sender === myUserId)) return;
|
|
||||||
sessionRef.current = session;
|
|
||||||
setIncoming({ room, callerId: callerMembership.sender ?? callerMembership.deviceId });
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSessionEnded = (roomId: string) => {
|
|
||||||
setIncoming((prev) => (prev?.room.roomId === roomId ? null : prev));
|
|
||||||
};
|
|
||||||
|
|
||||||
mx.matrixRTC.on(MatrixRTCSessionManagerEvents.SessionStarted, handleSessionStarted);
|
|
||||||
mx.matrixRTC.on(MatrixRTCSessionManagerEvents.SessionEnded, handleSessionEnded);
|
|
||||||
return () => {
|
|
||||||
mx.matrixRTC.off(MatrixRTCSessionManagerEvents.SessionStarted, handleSessionStarted);
|
|
||||||
mx.matrixRTC.off(MatrixRTCSessionManagerEvents.SessionEnded, handleSessionEnded);
|
|
||||||
};
|
|
||||||
}, [mx, callEmbed]);
|
|
||||||
|
|
||||||
// Dismiss if caller leaves before answered
|
|
||||||
useEffect(() => {
|
|
||||||
const session = sessionRef.current;
|
|
||||||
if (!session || !incoming) return;
|
|
||||||
const myUserId = mx.getUserId();
|
|
||||||
const check = () => {
|
|
||||||
const memberships = session.memberships ?? [];
|
|
||||||
if (!memberships.some((m) => m.sender !== myUserId)) setIncoming(null);
|
|
||||||
};
|
|
||||||
session.on(MatrixRTCSessionEvent.MembershipsChanged, check);
|
|
||||||
return () => {
|
|
||||||
session.off(MatrixRTCSessionEvent.MembershipsChanged, check);
|
|
||||||
};
|
|
||||||
}, [incoming, mx]);
|
|
||||||
|
|
||||||
// Auto-dismiss after 30 seconds
|
|
||||||
useEffect(() => {
|
|
||||||
if (!incoming) return;
|
|
||||||
const t = setTimeout(() => setIncoming(null), 30_000);
|
|
||||||
return () => clearTimeout(t);
|
|
||||||
}, [incoming]);
|
|
||||||
|
|
||||||
// Dismiss when user joins a call
|
|
||||||
useEffect(() => {
|
|
||||||
if (callEmbed) setIncoming(null);
|
|
||||||
}, [callEmbed]);
|
|
||||||
|
|
||||||
const dismiss = () => {
|
|
||||||
sessionRef.current = null;
|
|
||||||
setIncoming(null);
|
|
||||||
};
|
|
||||||
|
|
||||||
return [incoming, dismiss];
|
|
||||||
};
|
|
||||||
Reference in New Issue
Block a user