blob: 16daab1d9d7605776658be4049f0f2eea9122b86 [file] [log] [blame]
simon1170c322022-10-31 14:51:31 -04001/*
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 */
simon33c06182022-11-02 17:39:31 -040018
idillon18283ac2023-01-07 12:06:42 -050019import { IconButtonProps } from '@mui/material';
20import { styled } from '@mui/material/styles';
simon5c677962022-12-02 16:51:54 -050021import { ChangeEvent, useMemo } from 'react';
simon1170c322022-10-31 14:51:31 -040022
idillon255682e2023-02-06 13:25:26 -050023import { useCallManagerContext } from '../contexts/CallManagerProvider';
idillon27dab022023-02-02 17:55:47 -050024import { useUserMediaContext } from '../contexts/UserMediaProvider';
idillonc45a43d2023-02-10 18:12:10 -050025import { CallStatus, VideoStatus } from '../services/CallManager';
simon9076a9a2022-11-29 17:13:01 -050026import {
idillon18283ac2023-01-07 12:06:42 -050027 ColoredRoundButton,
simon9076a9a2022-11-29 17:13:01 -050028 ExpandableButton,
29 ExpandableButtonProps,
30 ExpandMenuRadioOption,
simon9076a9a2022-11-29 17:13:01 -050031 ToggleIconButton,
32} from './Button';
simon1170c322022-10-31 14:51:31 -040033import {
34 CallEndIcon,
35 ChatBubbleIcon,
36 ExtensionIcon,
simon33c06182022-11-02 17:39:31 -040037 FullScreenIcon,
simon1170c322022-10-31 14:51:31 -040038 GroupAddIcon,
39 MicroIcon,
40 MicroOffIcon,
simon33c06182022-11-02 17:39:31 -040041 MoreVerticalIcon,
simon2d3b6532022-11-08 21:01:57 -050042 PlaceAudioCallIcon,
simon1170c322022-10-31 14:51:31 -040043 RecordingIcon,
simon2d3b6532022-11-08 21:01:57 -050044 RoundCloseIcon,
simon33c06182022-11-02 17:39:31 -040045 ScreenShareArrowIcon,
simon1e2bf342022-12-02 12:19:40 -050046 ScreenShareStopIcon,
simon1170c322022-10-31 14:51:31 -040047 VideoCameraIcon,
48 VideoCameraOffIcon,
49 VolumeIcon,
50} from './SvgIcon';
51
Gabriel Rochon8321a0d2022-11-06 23:18:36 -050052const CallButton = styled((props: ExpandableButtonProps) => {
53 return <ExpandableButton {...props} />;
54})({
simon2d3b6532022-11-08 21:01:57 -050055 color: 'white',
Gabriel Rochon8321a0d2022-11-06 23:18:36 -050056 '&:hover': {
57 backgroundColor: 'rgba(255, 255, 255, 0.15)',
58 },
Gabriel Rochon8321a0d2022-11-06 23:18:36 -050059});
60
simon33c06182022-11-02 17:39:31 -040061export const CallingChatButton = (props: ExpandableButtonProps) => {
idillon255682e2023-02-06 13:25:26 -050062 const { setIsChatShown } = useCallManagerContext();
simonf9d78f22022-11-25 15:47:15 -050063 return (
64 <CallButton
65 aria-label="chat"
66 Icon={ChatBubbleIcon}
67 onClick={() => {
68 setIsChatShown((v) => !v);
69 }}
70 {...props}
71 />
72 );
simon1170c322022-10-31 14:51:31 -040073};
simon33c06182022-11-02 17:39:31 -040074
75export const CallingEndButton = (props: ExpandableButtonProps) => {
idillon255682e2023-02-06 13:25:26 -050076 const { endCall } = useCallManagerContext();
simonf929a362022-11-18 16:53:45 -050077 return (
idillon18283ac2023-01-07 12:06:42 -050078 <ColoredRoundButton
simonf929a362022-11-18 16:53:45 -050079 paletteColor={(theme) => theme.palette.error}
80 onClick={() => {
simonaccd8022022-11-24 15:04:53 -050081 endCall();
simonf929a362022-11-18 16:53:45 -050082 }}
83 aria-label="call end"
84 Icon={CallEndIcon}
85 {...props}
86 />
87 );
simon1170c322022-10-31 14:51:31 -040088};
simon33c06182022-11-02 17:39:31 -040089
90export const CallingExtensionButton = (props: ExpandableButtonProps) => {
Gabriel Rochon8321a0d2022-11-06 23:18:36 -050091 return <CallButton aria-label="extensions" Icon={ExtensionIcon} {...props} />;
simon1170c322022-10-31 14:51:31 -040092};
simon33c06182022-11-02 17:39:31 -040093
94export const CallingFullScreenButton = (props: ExpandableButtonProps) => {
idillon255682e2023-02-06 13:25:26 -050095 const { setIsFullscreen } = useCallManagerContext();
simon2a5cf142022-11-25 15:47:35 -050096 return (
97 <CallButton
98 aria-label="full screen"
99 Icon={FullScreenIcon}
100 onClick={() => {
101 setIsFullscreen((v) => !v);
102 }}
103 {...props}
104 />
105 );
simon1170c322022-10-31 14:51:31 -0400106};
simon33c06182022-11-02 17:39:31 -0400107
108export const CallingGroupButton = (props: ExpandableButtonProps) => {
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500109 return <CallButton aria-label="group options" Icon={GroupAddIcon} {...props} />;
simon1170c322022-10-31 14:51:31 -0400110};
simon33c06182022-11-02 17:39:31 -0400111
112export const CallingMoreVerticalButton = (props: ExpandableButtonProps) => {
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500113 return <CallButton aria-label="more vertical" Icon={MoreVerticalIcon} {...props} />;
simon1170c322022-10-31 14:51:31 -0400114};
simon33c06182022-11-02 17:39:31 -0400115
116export const CallingRecordButton = (props: ExpandableButtonProps) => {
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500117 return <CallButton aria-label="recording options" Icon={RecordingIcon} {...props} />;
simon33c06182022-11-02 17:39:31 -0400118};
119
120export const CallingScreenShareButton = (props: ExpandableButtonProps) => {
simon1170c322022-10-31 14:51:31 -0400121 return (
simon1e2bf342022-12-02 12:19:40 -0500122 <CallButton aria-label="screen share" expandMenuOnClick IconButtonComp={ToggleScreenShareIconButton} {...props} />
123 );
124};
125
126const ToggleScreenShareIconButton = (props: IconButtonProps) => {
idillon255682e2023-02-06 13:25:26 -0500127 const { videoStatus, updateVideoStatus } = useCallManagerContext();
simon1e2bf342022-12-02 12:19:40 -0500128
129 return (
130 <ToggleIconButton
131 IconOff={ScreenShareArrowIcon}
132 IconOn={ScreenShareStopIcon}
133 selected={videoStatus === VideoStatus.ScreenShare}
134 toggle={() => {
135 updateVideoStatus((v) => (v !== VideoStatus.ScreenShare ? VideoStatus.ScreenShare : VideoStatus.Off));
136 }}
simon33c06182022-11-02 17:39:31 -0400137 {...props}
138 />
simon1170c322022-10-31 14:51:31 -0400139 );
140};
141
simon9076a9a2022-11-29 17:13:01 -0500142const useMediaDeviceExpandMenuOptions = (kind: MediaDeviceKind): ExpandMenuRadioOption[] | undefined => {
idillon27dab022023-02-02 17:55:47 -0500143 const { currentMediaDeviceIds, mediaDevices } = useUserMediaContext();
simon9a8fe202022-11-15 18:25:49 -0500144
simon9076a9a2022-11-29 17:13:01 -0500145 const options = useMemo(
simon9a8fe202022-11-15 18:25:49 -0500146 () =>
147 mediaDevices[kind].map((device) => ({
148 key: device.deviceId,
149 description: device.label,
150 })),
151 [mediaDevices, kind]
152 );
simon9076a9a2022-11-29 17:13:01 -0500153
simon492e8402022-11-29 16:48:37 -0500154 const currentDevice = currentMediaDeviceIds[kind];
155
156 if (options.length === 0) {
157 return undefined;
158 }
159 return [
160 {
161 options,
162 value: currentDevice.id ?? '',
163 onChange: (e: ChangeEvent<HTMLInputElement>) => {
164 currentDevice.setId(e.target.value);
165 },
166 },
167 ];
simon9a8fe202022-11-15 18:25:49 -0500168};
169
simon33c06182022-11-02 17:39:31 -0400170export const CallingVolumeButton = (props: ExpandableButtonProps) => {
simon9a8fe202022-11-15 18:25:49 -0500171 const options = useMediaDeviceExpandMenuOptions('audiooutput');
simon492e8402022-11-29 16:48:37 -0500172
173 return (
174 <CallButton
175 aria-label="volume options"
simon571240f2022-11-29 23:59:27 -0500176 expandMenuOnClick
simon492e8402022-11-29 16:48:37 -0500177 Icon={VolumeIcon}
simon571240f2022-11-29 23:59:27 -0500178 expandMenuOptions={options}
simon492e8402022-11-29 16:48:37 -0500179 {...props}
180 />
181 );
simon33c06182022-11-02 17:39:31 -0400182};
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500183
184export const CallingMicButton = (props: ExpandableButtonProps) => {
simon9a8fe202022-11-15 18:25:49 -0500185 const options = useMediaDeviceExpandMenuOptions('audioinput');
simon1170c322022-10-31 14:51:31 -0400186
187 return (
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500188 <CallButton
simon1170c322022-10-31 14:51:31 -0400189 aria-label="microphone options"
simon9076a9a2022-11-29 17:13:01 -0500190 expandMenuOptions={options}
simonf929a362022-11-18 16:53:45 -0500191 IconButtonComp={ToggleAudioCameraIconButton}
192 {...props}
193 />
194 );
195};
196
197const ToggleAudioCameraIconButton = (props: IconButtonProps) => {
idillon255682e2023-02-06 13:25:26 -0500198 const { isAudioOn, setIsAudioOn } = useCallManagerContext();
simonf929a362022-11-18 16:53:45 -0500199 return (
200 <ToggleIconButton
201 IconOn={MicroIcon}
202 IconOff={MicroOffIcon}
203 selected={isAudioOn}
simon9076a9a2022-11-29 17:13:01 -0500204 toggle={() => setIsAudioOn((v) => !v)}
simon1170c322022-10-31 14:51:31 -0400205 {...props}
206 />
207 );
208};
209
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500210export const CallingVideoCameraButton = (props: ExpandableButtonProps) => {
simon9a8fe202022-11-15 18:25:49 -0500211 const options = useMediaDeviceExpandMenuOptions('videoinput');
212
simon1170c322022-10-31 14:51:31 -0400213 return (
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500214 <CallButton
simon1170c322022-10-31 14:51:31 -0400215 aria-label="camera options"
simon9076a9a2022-11-29 17:13:01 -0500216 expandMenuOptions={options}
simonf929a362022-11-18 16:53:45 -0500217 IconButtonComp={ToggleVideoCameraIconButton}
218 {...props}
219 />
220 );
221};
222
223const ToggleVideoCameraIconButton = (props: IconButtonProps) => {
idillon255682e2023-02-06 13:25:26 -0500224 const { videoStatus, updateVideoStatus } = useCallManagerContext();
simonf929a362022-11-18 16:53:45 -0500225 return (
226 <ToggleIconButton
227 IconOn={VideoCameraIcon}
228 IconOff={VideoCameraOffIcon}
simon1e2bf342022-12-02 12:19:40 -0500229 selected={videoStatus === VideoStatus.Camera}
230 toggle={() => {
231 updateVideoStatus((v) => (v !== VideoStatus.Camera ? VideoStatus.Camera : VideoStatus.Off));
232 }}
simon1170c322022-10-31 14:51:31 -0400233 {...props}
234 />
235 );
236};
simon2d3b6532022-11-08 21:01:57 -0500237
238// Calling pending/receiving interface
simonf929a362022-11-18 16:53:45 -0500239export const CallingCancelButton = (props: IconButtonProps) => {
idillon255682e2023-02-06 13:25:26 -0500240 const { endCall } = useCallManagerContext();
simonf929a362022-11-18 16:53:45 -0500241
242 return (
idillon18283ac2023-01-07 12:06:42 -0500243 <ColoredRoundButton
simonf929a362022-11-18 16:53:45 -0500244 aria-label="cancel call"
245 onClick={() => {
simonaccd8022022-11-24 15:04:53 -0500246 endCall();
simonf929a362022-11-18 16:53:45 -0500247 }}
248 Icon={CallEndIcon}
249 paletteColor={(theme) => theme.palette.error}
250 {...props}
251 />
252 );
simon2d3b6532022-11-08 21:01:57 -0500253};
254
simonf929a362022-11-18 16:53:45 -0500255export const CallingAnswerAudioButton = (props: IconButtonProps) => {
idillon255682e2023-02-06 13:25:26 -0500256 const { acceptCall, callStatus } = useCallManagerContext();
simonf929a362022-11-18 16:53:45 -0500257
258 return (
idillon18283ac2023-01-07 12:06:42 -0500259 <ColoredRoundButton
Charlie9c8779e2022-11-29 20:10:56 -0500260 disabled={callStatus === CallStatus.Loading || callStatus === CallStatus.Connecting}
simonf929a362022-11-18 16:53:45 -0500261 aria-label="answer call audio"
262 onClick={() => {
MichelleSS55164202022-11-25 18:36:14 -0500263 acceptCall(false);
simonf929a362022-11-18 16:53:45 -0500264 }}
265 Icon={PlaceAudioCallIcon}
266 paletteColor={(theme) => theme.palette.success}
267 {...props}
268 />
269 );
simon2d3b6532022-11-08 21:01:57 -0500270};
271
simonf929a362022-11-18 16:53:45 -0500272export const CallingAnswerVideoButton = (props: IconButtonProps) => {
idillon255682e2023-02-06 13:25:26 -0500273 const { acceptCall, callStatus } = useCallManagerContext();
simonf929a362022-11-18 16:53:45 -0500274 return (
idillon18283ac2023-01-07 12:06:42 -0500275 <ColoredRoundButton
Charlie9c8779e2022-11-29 20:10:56 -0500276 disabled={callStatus === CallStatus.Connecting || callStatus === CallStatus.Loading}
simonf929a362022-11-18 16:53:45 -0500277 aria-label="answer call video"
278 onClick={() => {
MichelleSS55164202022-11-25 18:36:14 -0500279 acceptCall(true);
simonf929a362022-11-18 16:53:45 -0500280 }}
281 paletteColor={(theme) => theme.palette.success}
282 Icon={VideoCameraIcon}
283 {...props}
284 />
285 );
286};
287
288export const CallingRefuseButton = (props: IconButtonProps) => {
idillon255682e2023-02-06 13:25:26 -0500289 const { endCall } = useCallManagerContext();
simonf929a362022-11-18 16:53:45 -0500290 return (
idillon18283ac2023-01-07 12:06:42 -0500291 <ColoredRoundButton
simonf929a362022-11-18 16:53:45 -0500292 aria-label="refuse call"
293 onClick={() => {
simonaccd8022022-11-24 15:04:53 -0500294 endCall();
simonf929a362022-11-18 16:53:45 -0500295 }}
296 paletteColor={(theme) => theme.palette.error}
297 Icon={RoundCloseIcon}
298 {...props}
299 />
300 );
simon2d3b6532022-11-08 21:01:57 -0500301};