(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() {
|
||||
useInitializeCall();
|
||||
|
||||
return null;
|
||||
return useInitializeCall();
|
||||
}
|
||||
|
||||
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,
|
||||
} from "livekit-client";
|
||||
import z from "zod";
|
||||
import { toast as sonnerToast } from "sonner";
|
||||
import {
|
||||
createScreenShareController,
|
||||
type ScreenShareSession,
|
||||
|
|
@ -33,6 +32,7 @@ import {
|
|||
getSpeakingDetector,
|
||||
disposeSpeakingDetector,
|
||||
} from "./speakingIndicator";
|
||||
import InvitePopup from "./components/invitePopup";
|
||||
|
||||
// logging
|
||||
setLogExtension(
|
||||
|
|
@ -45,6 +45,11 @@ setLogExtension(
|
|||
|
||||
type CallState = "closed" | "closing" | "connecting" | "open" | "encrypting";
|
||||
type CallView = "preview" | "focused" | "grid";
|
||||
type IncomingCallInvite = {
|
||||
callId: string;
|
||||
callSecret: string;
|
||||
senderId: number;
|
||||
};
|
||||
type CurrentCallData =
|
||||
| (z.infer<typeof ttp.call_data.response> & { exists: boolean })
|
||||
| null;
|
||||
|
|
@ -83,6 +88,7 @@ type CallStore = {
|
|||
view: CallView;
|
||||
invitedUserId: number | null;
|
||||
callId: string | null;
|
||||
incomingCallInvite: IncomingCallInvite | null;
|
||||
callSecret: string | null;
|
||||
livekitToken: string | null;
|
||||
currentCallData: CurrentCallData;
|
||||
|
|
@ -837,6 +843,7 @@ export async function disconnect() {
|
|||
state: "closing",
|
||||
invitedUserId: null,
|
||||
callId: null,
|
||||
incomingCallInvite: null,
|
||||
callSecret: null,
|
||||
livekitToken: null,
|
||||
currentCallData: null,
|
||||
|
|
@ -999,6 +1006,7 @@ export function resetCallState() {
|
|||
view: "preview",
|
||||
invitedUserId: null,
|
||||
callId: null,
|
||||
incomingCallInvite: null,
|
||||
callSecret: null,
|
||||
livekitToken: null,
|
||||
currentCallData: null,
|
||||
|
|
@ -1050,6 +1058,7 @@ export const useCall = create<CallStore>(() => ({
|
|||
view: "preview",
|
||||
invitedUserId: null,
|
||||
callId: null,
|
||||
incomingCallInvite: null,
|
||||
callSecret: null,
|
||||
livekitToken: null,
|
||||
currentCallData: null,
|
||||
|
|
@ -1084,6 +1093,7 @@ export function useInitializeCall() {
|
|||
|
||||
const callId = useCall((state) => state.callId);
|
||||
const view = useCall((state) => state.view);
|
||||
const incomingCallInvite = useCall((state) => state.incomingCallInvite);
|
||||
|
||||
const listenersRegistered = useRef(false);
|
||||
const noiseFilter = useMemo(
|
||||
|
|
@ -1113,32 +1123,44 @@ export function useInitializeCall() {
|
|||
}, [decryptText, encryptText, get, getSharedSecret, load, navigate, send]);
|
||||
|
||||
const showCallingScreen = useCallback(
|
||||
async (callId: string, callSecret: string, senderId: number) => {
|
||||
const senderName = await get(senderId).then((data) => data.display);
|
||||
|
||||
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,
|
||||
});
|
||||
},
|
||||
},
|
||||
(callId: string, callSecret: string, senderId: number) => {
|
||||
useCall.setState({
|
||||
incomingCallInvite: { callId, callSecret, 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
|
||||
useEffect(() => {
|
||||
subscribePush(async (message) => {
|
||||
|
|
@ -1482,4 +1504,13 @@ export function useInitializeCall() {
|
|||
});
|
||||
});
|
||||
}, [callId, send, view]);
|
||||
|
||||
return incomingCallInvite ? (
|
||||
<InvitePopup
|
||||
open
|
||||
setOpen={setInvitePopupOpen}
|
||||
onAccept={respondToInvite}
|
||||
user={incomingCallInvite.senderId}
|
||||
/>
|
||||
) : null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,2 @@
|
|||
- Implement context menu features
|
||||
- Add default-emoji-hotkey in settings
|
||||
- On mobile, turn ContextMenu into Drawer
|
||||
|
|
|
|||
Loading…
Reference in a new issue