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