blob: cc24669fbaf556bc97a2d9b944fba1f0a5d27042 [file] [log] [blame]
simon2d3b6532022-11-08 21:01:57 -05001/*
2 * Copyright (C) 2022 Savoir-faire Linux Inc.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Affero General Public License as
6 * published by the Free Software Foundation; either version 3 of the
7 * License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU Affero General Public License for more details.
13 *
14 * You should have received a copy of the GNU Affero General Public
15 * License along with this program. If not, see
16 * <https://www.gnu.org/licenses/>.
17 */
18
simonf929a362022-11-18 16:53:45 -050019import { Box, CircularProgress, Grid, IconButtonProps, Stack, Typography } from '@mui/material';
Misha Krieger-Raynauldabc80e52022-11-29 19:44:41 -050020import { ComponentType, ReactNode, useContext, useEffect, useMemo, useRef } from 'react';
simon4e7445c2022-11-16 21:18:46 -050021import { useTranslation } from 'react-i18next';
simon9076a9a2022-11-29 17:13:01 -050022import { useLocation } from 'react-router-dom';
simon2d3b6532022-11-08 21:01:57 -050023
24import {
25 CallingAnswerAudioButton,
26 CallingAnswerVideoButton,
simonf929a362022-11-18 16:53:45 -050027 CallingCancelButton,
simon2d3b6532022-11-08 21:01:57 -050028 CallingRefuseButton,
29} from '../components/CallButtons';
Gabriel Rochon15a5fb22022-11-27 19:25:14 -050030import ConversationAvatar from '../components/ConversationAvatar';
simon9076a9a2022-11-29 17:13:01 -050031import { CallContext, CallStatus } from '../contexts/CallProvider';
Gabriel Rochon61f82af2022-11-22 22:28:02 -050032import { ConversationContext } from '../contexts/ConversationProvider';
Misha Krieger-Raynauldabc80e52022-11-29 19:44:41 -050033import { WebRtcContext } from '../contexts/WebRtcProvider';
simon2d3b6532022-11-08 21:01:57 -050034
simon9076a9a2022-11-29 17:13:01 -050035export const CallPending = () => {
Misha Krieger-Raynauldabc80e52022-11-29 19:44:41 -050036 const { localStream } = useContext(WebRtcContext);
Gabriel Rochon15a5fb22022-11-27 19:25:14 -050037 const { conversation } = useContext(ConversationContext);
simon9076a9a2022-11-29 17:13:01 -050038 const { callRole } = useContext(CallContext);
Misha Krieger-Raynauldabc80e52022-11-29 19:44:41 -050039 const localVideoRef = useRef<HTMLVideoElement | null>(null);
40
41 useEffect(() => {
42 if (localStream && localVideoRef.current) {
43 localVideoRef.current.srcObject = localStream;
44 }
45 }, [localStream]);
46
simon2d3b6532022-11-08 21:01:57 -050047 return (
48 <Stack
49 direction="column"
50 justifyContent="center"
51 alignItems="center"
52 height="100%"
53 spacing={4}
simon9f814a32022-11-22 21:40:53 -050054 flexGrow={1}
simon2d3b6532022-11-08 21:01:57 -050055 sx={{
Misha Krieger-Raynauldabc80e52022-11-29 19:44:41 -050056 position: 'relative',
simon2d3b6532022-11-08 21:01:57 -050057 }}
58 >
Misha Krieger-Raynauldabc80e52022-11-29 19:44:41 -050059 <video
60 ref={localVideoRef}
61 autoPlay
62 muted
63 style={{
64 width: '100%',
65 height: '100%',
66 position: 'absolute',
67 objectFit: 'cover',
68 backgroundColor: 'black',
69 zIndex: -1,
70 }}
71 />
simon2d3b6532022-11-08 21:01:57 -050072 <Box
73 sx={{
74 position: 'relative',
75 display: 'flex',
76 alignItems: 'center',
77 justifyContent: 'center',
78 width: '100%',
79 height: '30%',
80 }}
81 >
82 <Box
83 sx={{
84 aspectRatio: '1',
85 height: '100%',
86 position: 'absolute',
87 }}
88 >
89 <CircularProgress
90 disableShrink
91 thickness={1}
92 size="100%"
93 sx={{
94 position: 'absolute',
95 color: 'white',
96 zIndex: 1,
97 }}
98 />
Gabriel Rochon15a5fb22022-11-27 19:25:14 -050099 <ConversationAvatar
simonf929a362022-11-18 16:53:45 -0500100 alt="contact profile picture"
Gabriel Rochon15a5fb22022-11-27 19:25:14 -0500101 displayName={conversation.getDisplayNameNoFallback()}
simon2d3b6532022-11-08 21:01:57 -0500102 style={{
simon2d3b6532022-11-08 21:01:57 -0500103 width: '100%',
104 height: '100%',
simon2d3b6532022-11-08 21:01:57 -0500105 }}
106 />
107 </Box>
108 </Box>
simon9076a9a2022-11-29 17:13:01 -0500109 {callRole === 'caller' ? <CallPendingCallerInterface /> : <CallPendingReceiverInterface />}
simon2d3b6532022-11-08 21:01:57 -0500110 </Stack>
111 );
112};
113
simonf929a362022-11-18 16:53:45 -0500114const CallPendingDetails = ({
115 title,
116 buttons,
117}: {
118 title: ReactNode;
119 buttons: {
120 ButtonComponent: ComponentType<IconButtonProps>;
121 title: ReactNode;
122 }[];
123}) => {
simon2d3b6532022-11-08 21:01:57 -0500124 return (
simon4e7445c2022-11-16 21:18:46 -0500125 <>
simon2d3b6532022-11-08 21:01:57 -0500126 <Typography variant="h1" color="white">
simonf929a362022-11-18 16:53:45 -0500127 {title}
simon2d3b6532022-11-08 21:01:57 -0500128 </Typography>
simon4e7445c2022-11-16 21:18:46 -0500129 <Box width="50%">
simonf929a362022-11-18 16:53:45 -0500130 <Grid container justifyContent="center">
131 {buttons.map(({ ButtonComponent, title: buttonTitle }, i) => (
132 <Grid item key={i} xs={4}>
133 <Stack direction="column" alignItems="center" spacing={1} sx={{}}>
134 <ButtonComponent color="inherit" size="large" />
135 <Typography variant="body2" color="white" sx={{ opacity: 0.75 }}>
136 {buttonTitle}
137 </Typography>
138 </Stack>
139 </Grid>
140 ))}
141 </Grid>
simon4e7445c2022-11-16 21:18:46 -0500142 </Box>
143 </>
simon2d3b6532022-11-08 21:01:57 -0500144 );
145};
146
simon9076a9a2022-11-29 17:13:01 -0500147export const CallPendingCallerInterface = () => {
148 const { callStatus } = useContext(CallContext);
simon4e7445c2022-11-16 21:18:46 -0500149 const { t } = useTranslation();
Gabriel Rochon61f82af2022-11-22 22:28:02 -0500150 const { conversation } = useContext(ConversationContext);
151 const memberName = useMemo(() => conversation.getFirstMember().contact.getRegisteredName(), [conversation]);
simonf929a362022-11-18 16:53:45 -0500152
simon9076a9a2022-11-29 17:13:01 -0500153 let title = t('loading');
154
155 switch (callStatus) {
156 case CallStatus.Ringing:
157 title = t('calling', {
158 member0: memberName,
159 });
160 break;
161 case CallStatus.Connecting:
162 title = t('connecting');
163 break;
164 }
165
simon2d3b6532022-11-08 21:01:57 -0500166 return (
simonf929a362022-11-18 16:53:45 -0500167 <CallPendingDetails
simon9076a9a2022-11-29 17:13:01 -0500168 title={title}
simonf929a362022-11-18 16:53:45 -0500169 buttons={[
170 {
171 ButtonComponent: CallingCancelButton,
172 title: t('end_call'),
173 },
174 ]}
175 />
176 );
177};
178
simon9076a9a2022-11-29 17:13:01 -0500179export const CallPendingReceiverInterface = () => {
180 const { state } = useLocation();
181 const { callStatus } = useContext(CallContext);
182
simonf929a362022-11-18 16:53:45 -0500183 const { t } = useTranslation();
Gabriel Rochon61f82af2022-11-22 22:28:02 -0500184 const { conversation } = useContext(ConversationContext);
185 const memberName = useMemo(() => conversation.getFirstMember().contact.getRegisteredName(), [conversation]);
simonf929a362022-11-18 16:53:45 -0500186
simon9076a9a2022-11-29 17:13:01 -0500187 let title = t('loading');
188
189 switch (callStatus) {
190 case CallStatus.Ringing:
191 title = t('incoming_call', {
192 context: state?.isVideoOn ? 'video' : 'audio',
193 member0: memberName,
194 });
195 break;
196 case CallStatus.Connecting:
197 title = t('connecting');
198 break;
199 }
200
simonf929a362022-11-18 16:53:45 -0500201 return (
202 <CallPendingDetails
simon9076a9a2022-11-29 17:13:01 -0500203 title={title}
simonf929a362022-11-18 16:53:45 -0500204 buttons={[
205 {
206 ButtonComponent: CallingRefuseButton,
207 title: t('refuse_call'),
208 },
209 {
210 ButtonComponent: CallingAnswerAudioButton,
211 title: t('accept_call_audio'),
212 },
213 {
214 ButtonComponent: CallingAnswerVideoButton,
215 title: t('accept_call_video'),
216 },
217 ]}
218 />
simon2d3b6532022-11-08 21:01:57 -0500219 );
220};