(feat): replace toast with popup for call invites
(qol): update todo
This commit is contained in:
parent
25e47604b3
commit
6fda7f1c71
4 changed files with 113 additions and 27 deletions
|
|
@ -163,9 +163,7 @@ function AppShell() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function CallInit() {
|
function CallInit() {
|
||||||
useInitializeCall();
|
return useInitializeCall();
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const rootRoute = createRootRoute({
|
const rootRoute = createRootRoute({
|
||||||
|
|
|
||||||
58
packages/call/src/components/invitePopup.tsx
Normal file
58
packages/call/src/components/invitePopup.tsx
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
import {
|
||||||
|
Avatar,
|
||||||
|
AvatarFallback,
|
||||||
|
AvatarImage,
|
||||||
|
Button,
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
} from "@tensamin/ui";
|
||||||
|
import Wrapper from "@tensamin/user/wrapper";
|
||||||
|
import { PhoneIncoming, PhoneMissed } from "lucide-react";
|
||||||
|
|
||||||
|
export default function InvitePopup({
|
||||||
|
open,
|
||||||
|
setOpen,
|
||||||
|
onAccept,
|
||||||
|
user,
|
||||||
|
}: {
|
||||||
|
open: boolean;
|
||||||
|
setOpen: (value: boolean) => void;
|
||||||
|
onAccept: (value: boolean) => void;
|
||||||
|
user: number;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Wrapper
|
||||||
|
userId={user}
|
||||||
|
loading={null}
|
||||||
|
component={(user) => (
|
||||||
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
|
<DialogContent className="flex flex-col gap-5 items-center justify-center w-65 h-80">
|
||||||
|
<Avatar className="size-30">
|
||||||
|
<AvatarImage src={user.avatar} />
|
||||||
|
<AvatarFallback className="text-5xl">
|
||||||
|
{user.display.slice(0, 2).toUpperCase()}
|
||||||
|
</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
<p className="text-xl font-medium">{user.display}</p>
|
||||||
|
<div className="w-full flex justify-center gap-3">
|
||||||
|
<Button
|
||||||
|
className="w-14 h-14"
|
||||||
|
variant="subtleDefault"
|
||||||
|
onClick={() => onAccept(true)}
|
||||||
|
>
|
||||||
|
<PhoneIncoming className="size-5" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
className="w-14 h-14"
|
||||||
|
variant="destructive"
|
||||||
|
onClick={() => onAccept(false)}
|
||||||
|
>
|
||||||
|
<PhoneMissed className="size-5" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -24,7 +24,6 @@ import {
|
||||||
getLogger,
|
getLogger,
|
||||||
} from "livekit-client";
|
} from "livekit-client";
|
||||||
import z from "zod";
|
import z from "zod";
|
||||||
import { toast as sonnerToast } from "sonner";
|
|
||||||
import {
|
import {
|
||||||
createScreenShareController,
|
createScreenShareController,
|
||||||
type ScreenShareSession,
|
type ScreenShareSession,
|
||||||
|
|
@ -33,6 +32,7 @@ import {
|
||||||
getSpeakingDetector,
|
getSpeakingDetector,
|
||||||
disposeSpeakingDetector,
|
disposeSpeakingDetector,
|
||||||
} from "./speakingIndicator";
|
} from "./speakingIndicator";
|
||||||
|
import InvitePopup from "./components/invitePopup";
|
||||||
|
|
||||||
// logging
|
// logging
|
||||||
setLogExtension(
|
setLogExtension(
|
||||||
|
|
@ -45,6 +45,11 @@ setLogExtension(
|
||||||
|
|
||||||
type CallState = "closed" | "closing" | "connecting" | "open" | "encrypting";
|
type CallState = "closed" | "closing" | "connecting" | "open" | "encrypting";
|
||||||
type CallView = "preview" | "focused" | "grid";
|
type CallView = "preview" | "focused" | "grid";
|
||||||
|
type IncomingCallInvite = {
|
||||||
|
callId: string;
|
||||||
|
callSecret: string;
|
||||||
|
senderId: number;
|
||||||
|
};
|
||||||
type CurrentCallData =
|
type CurrentCallData =
|
||||||
| (z.infer<typeof ttp.call_data.response> & { exists: boolean })
|
| (z.infer<typeof ttp.call_data.response> & { exists: boolean })
|
||||||
| null;
|
| null;
|
||||||
|
|
@ -83,6 +88,7 @@ type CallStore = {
|
||||||
view: CallView;
|
view: CallView;
|
||||||
invitedUserId: number | null;
|
invitedUserId: number | null;
|
||||||
callId: string | null;
|
callId: string | null;
|
||||||
|
incomingCallInvite: IncomingCallInvite | null;
|
||||||
callSecret: string | null;
|
callSecret: string | null;
|
||||||
livekitToken: string | null;
|
livekitToken: string | null;
|
||||||
currentCallData: CurrentCallData;
|
currentCallData: CurrentCallData;
|
||||||
|
|
@ -837,6 +843,7 @@ export async function disconnect() {
|
||||||
state: "closing",
|
state: "closing",
|
||||||
invitedUserId: null,
|
invitedUserId: null,
|
||||||
callId: null,
|
callId: null,
|
||||||
|
incomingCallInvite: null,
|
||||||
callSecret: null,
|
callSecret: null,
|
||||||
livekitToken: null,
|
livekitToken: null,
|
||||||
currentCallData: null,
|
currentCallData: null,
|
||||||
|
|
@ -999,6 +1006,7 @@ export function resetCallState() {
|
||||||
view: "preview",
|
view: "preview",
|
||||||
invitedUserId: null,
|
invitedUserId: null,
|
||||||
callId: null,
|
callId: null,
|
||||||
|
incomingCallInvite: null,
|
||||||
callSecret: null,
|
callSecret: null,
|
||||||
livekitToken: null,
|
livekitToken: null,
|
||||||
currentCallData: null,
|
currentCallData: null,
|
||||||
|
|
@ -1050,6 +1058,7 @@ export const useCall = create<CallStore>(() => ({
|
||||||
view: "preview",
|
view: "preview",
|
||||||
invitedUserId: null,
|
invitedUserId: null,
|
||||||
callId: null,
|
callId: null,
|
||||||
|
incomingCallInvite: null,
|
||||||
callSecret: null,
|
callSecret: null,
|
||||||
livekitToken: null,
|
livekitToken: null,
|
||||||
currentCallData: null,
|
currentCallData: null,
|
||||||
|
|
@ -1084,6 +1093,7 @@ export function useInitializeCall() {
|
||||||
|
|
||||||
const callId = useCall((state) => state.callId);
|
const callId = useCall((state) => state.callId);
|
||||||
const view = useCall((state) => state.view);
|
const view = useCall((state) => state.view);
|
||||||
|
const incomingCallInvite = useCall((state) => state.incomingCallInvite);
|
||||||
|
|
||||||
const listenersRegistered = useRef(false);
|
const listenersRegistered = useRef(false);
|
||||||
const noiseFilter = useMemo(
|
const noiseFilter = useMemo(
|
||||||
|
|
@ -1113,32 +1123,44 @@ export function useInitializeCall() {
|
||||||
}, [decryptText, encryptText, get, getSharedSecret, load, navigate, send]);
|
}, [decryptText, encryptText, get, getSharedSecret, load, navigate, send]);
|
||||||
|
|
||||||
const showCallingScreen = useCallback(
|
const showCallingScreen = useCallback(
|
||||||
async (callId: string, callSecret: string, senderId: number) => {
|
(callId: string, callSecret: string, senderId: number) => {
|
||||||
const senderName = await get(senderId).then((data) => data.display);
|
useCall.setState({
|
||||||
|
incomingCallInvite: { callId, callSecret, senderId },
|
||||||
sonnerToast(`Incoming call from ${senderName}`, {
|
|
||||||
action: {
|
|
||||||
label: "Accept",
|
|
||||||
onClick: () => {
|
|
||||||
joinCall(senderId, callSecret, callId, false).catch((err) => {
|
|
||||||
log(1, "call", "red", "Failed to join call", err);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
cancel: {
|
|
||||||
label: "Decline",
|
|
||||||
onClick: () => {
|
|
||||||
log(2, "call", "purple", "Declined call invite", {
|
|
||||||
callId,
|
|
||||||
senderId,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[get],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const setInvitePopupOpen = useCallback((open: boolean) => {
|
||||||
|
if (!open) {
|
||||||
|
useCall.setState({ incomingCallInvite: null });
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const respondToInvite = useCallback((accepted: boolean) => {
|
||||||
|
const invite = useCall.getState().incomingCallInvite;
|
||||||
|
|
||||||
|
useCall.setState({ incomingCallInvite: null });
|
||||||
|
|
||||||
|
if (!invite) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (accepted) {
|
||||||
|
joinCall(invite.senderId, invite.callSecret, invite.callId, false).catch(
|
||||||
|
(err) => {
|
||||||
|
log(1, "call", "red", "Failed to join call", err);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
log(2, "call", "purple", "Declined call invite", {
|
||||||
|
callId: invite.callId,
|
||||||
|
senderId: invite.senderId,
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
// listen to call invites
|
// listen to call invites
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
subscribePush(async (message) => {
|
subscribePush(async (message) => {
|
||||||
|
|
@ -1482,4 +1504,13 @@ export function useInitializeCall() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}, [callId, send, view]);
|
}, [callId, send, view]);
|
||||||
|
|
||||||
|
return incomingCallInvite ? (
|
||||||
|
<InvitePopup
|
||||||
|
open
|
||||||
|
setOpen={setInvitePopupOpen}
|
||||||
|
onAccept={respondToInvite}
|
||||||
|
user={incomingCallInvite.senderId}
|
||||||
|
/>
|
||||||
|
) : null;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,2 @@
|
||||||
- Implement context menu features
|
- Implement context menu features
|
||||||
- Add default-emoji-hotkey in settings
|
- Add default-emoji-hotkey in settings
|
||||||
- On mobile, turn ContextMenu into Drawer
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue