blob: d853cc1556131ab797d4b06ac83d12db92319769 [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
simon5c677962022-12-02 16:51:54 -050023import { CallStatus, useCallContext, VideoStatus } from '../contexts/CallProvider';
idillon27dab022023-02-02 17:55:47 -050024import { useUserMediaContext } from '../contexts/UserMediaProvider';
simon9076a9a2022-11-29 17:13:01 -050025import {
idillon18283ac2023-01-07 12:06:42 -050026 ColoredRoundButton,
simon9076a9a2022-11-29 17:13:01 -050027 ExpandableButton,
28 ExpandableButtonProps,
29 ExpandMenuRadioOption,
simon9076a9a2022-11-29 17:13:01 -050030 ToggleIconButton,
31} from './Button';
simon1170c322022-10-31 14:51:31 -040032import {
33 CallEndIcon,
34 ChatBubbleIcon,
35 ExtensionIcon,
simon33c06182022-11-02 17:39:31 -040036 FullScreenIcon,
simon1170c322022-10-31 14:51:31 -040037 GroupAddIcon,
38 MicroIcon,
39 MicroOffIcon,
simon33c06182022-11-02 17:39:31 -040040 MoreVerticalIcon,
simon2d3b6532022-11-08 21:01:57 -050041 PlaceAudioCallIcon,
simon1170c322022-10-31 14:51:31 -040042 RecordingIcon,
simon2d3b6532022-11-08 21:01:57 -050043 RoundCloseIcon,
simon33c06182022-11-02 17:39:31 -040044 ScreenShareArrowIcon,
simon1e2bf342022-12-02 12:19:40 -050045 ScreenShareStopIcon,
simon1170c322022-10-31 14:51:31 -040046 VideoCameraIcon,
47 VideoCameraOffIcon,
48 VolumeIcon,
49} from './SvgIcon';
50
Gabriel Rochon8321a0d2022-11-06 23:18:36 -050051const CallButton = styled((props: ExpandableButtonProps) => {
52 return <ExpandableButton {...props} />;
53})({
simon2d3b6532022-11-08 21:01:57 -050054 color: 'white',
Gabriel Rochon8321a0d2022-11-06 23:18:36 -050055 '&:hover': {
56 backgroundColor: 'rgba(255, 255, 255, 0.15)',
57 },
Gabriel Rochon8321a0d2022-11-06 23:18:36 -050058});
59
simon33c06182022-11-02 17:39:31 -040060export const CallingChatButton = (props: ExpandableButtonProps) => {
simon5c677962022-12-02 16:51:54 -050061 const { setIsChatShown } = useCallContext();
simonf9d78f22022-11-25 15:47:15 -050062 return (
63 <CallButton
64 aria-label="chat"
65 Icon={ChatBubbleIcon}
66 onClick={() => {
67 setIsChatShown((v) => !v);
68 }}
69 {...props}
70 />
71 );
simon1170c322022-10-31 14:51:31 -040072};
simon33c06182022-11-02 17:39:31 -040073
74export const CallingEndButton = (props: ExpandableButtonProps) => {
simon5c677962022-12-02 16:51:54 -050075 const { endCall } = useCallContext();
simonf929a362022-11-18 16:53:45 -050076 return (
idillon18283ac2023-01-07 12:06:42 -050077 <ColoredRoundButton
simonf929a362022-11-18 16:53:45 -050078 paletteColor={(theme) => theme.palette.error}
79 onClick={() => {
simonaccd8022022-11-24 15:04:53 -050080 endCall();
simonf929a362022-11-18 16:53:45 -050081 }}
82 aria-label="call end"
83 Icon={CallEndIcon}
84 {...props}
85 />
86 );
simon1170c322022-10-31 14:51:31 -040087};
simon33c06182022-11-02 17:39:31 -040088
89export const CallingExtensionButton = (props: ExpandableButtonProps) => {
Gabriel Rochon8321a0d2022-11-06 23:18:36 -050090 return <CallButton aria-label="extensions" Icon={ExtensionIcon} {...props} />;
simon1170c322022-10-31 14:51:31 -040091};
simon33c06182022-11-02 17:39:31 -040092
93export const CallingFullScreenButton = (props: ExpandableButtonProps) => {
simon5c677962022-12-02 16:51:54 -050094 const { setIsFullscreen } = useCallContext();
simon2a5cf142022-11-25 15:47:35 -050095 return (
96 <CallButton
97 aria-label="full screen"
98 Icon={FullScreenIcon}
99 onClick={() => {
100 setIsFullscreen((v) => !v);
101 }}
102 {...props}
103 />
104 );
simon1170c322022-10-31 14:51:31 -0400105};
simon33c06182022-11-02 17:39:31 -0400106
107export const CallingGroupButton = (props: ExpandableButtonProps) => {
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500108 return <CallButton aria-label="group options" Icon={GroupAddIcon} {...props} />;
simon1170c322022-10-31 14:51:31 -0400109};
simon33c06182022-11-02 17:39:31 -0400110
111export const CallingMoreVerticalButton = (props: ExpandableButtonProps) => {
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500112 return <CallButton aria-label="more vertical" Icon={MoreVerticalIcon} {...props} />;
simon1170c322022-10-31 14:51:31 -0400113};
simon33c06182022-11-02 17:39:31 -0400114
115export const CallingRecordButton = (props: ExpandableButtonProps) => {
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500116 return <CallButton aria-label="recording options" Icon={RecordingIcon} {...props} />;
simon33c06182022-11-02 17:39:31 -0400117};
118
119export const CallingScreenShareButton = (props: ExpandableButtonProps) => {
simon1170c322022-10-31 14:51:31 -0400120 return (
simon1e2bf342022-12-02 12:19:40 -0500121 <CallButton aria-label="screen share" expandMenuOnClick IconButtonComp={ToggleScreenShareIconButton} {...props} />
122 );
123};
124
125const ToggleScreenShareIconButton = (props: IconButtonProps) => {
simon5c677962022-12-02 16:51:54 -0500126 const { videoStatus, updateVideoStatus } = useCallContext();
simon1e2bf342022-12-02 12:19:40 -0500127
128 return (
129 <ToggleIconButton
130 IconOff={ScreenShareArrowIcon}
131 IconOn={ScreenShareStopIcon}
132 selected={videoStatus === VideoStatus.ScreenShare}
133 toggle={() => {
134 updateVideoStatus((v) => (v !== VideoStatus.ScreenShare ? VideoStatus.ScreenShare : VideoStatus.Off));
135 }}
simon33c06182022-11-02 17:39:31 -0400136 {...props}
137 />
simon1170c322022-10-31 14:51:31 -0400138 );
139};
140
simon9076a9a2022-11-29 17:13:01 -0500141const useMediaDeviceExpandMenuOptions = (kind: MediaDeviceKind): ExpandMenuRadioOption[] | undefined => {
idillon27dab022023-02-02 17:55:47 -0500142 const { currentMediaDeviceIds, mediaDevices } = useUserMediaContext();
simon9a8fe202022-11-15 18:25:49 -0500143
simon9076a9a2022-11-29 17:13:01 -0500144 const options = useMemo(
simon9a8fe202022-11-15 18:25:49 -0500145 () =>
146 mediaDevices[kind].map((device) => ({
147 key: device.deviceId,
148 description: device.label,
149 })),
150 [mediaDevices, kind]
151 );
simon9076a9a2022-11-29 17:13:01 -0500152
simon492e8402022-11-29 16:48:37 -0500153 const currentDevice = currentMediaDeviceIds[kind];
154
155 if (options.length === 0) {
156 return undefined;
157 }
158 return [
159 {
160 options,
161 value: currentDevice.id ?? '',
162 onChange: (e: ChangeEvent<HTMLInputElement>) => {
163 currentDevice.setId(e.target.value);
164 },
165 },
166 ];
simon9a8fe202022-11-15 18:25:49 -0500167};
168
simon33c06182022-11-02 17:39:31 -0400169export const CallingVolumeButton = (props: ExpandableButtonProps) => {
simon9a8fe202022-11-15 18:25:49 -0500170 const options = useMediaDeviceExpandMenuOptions('audiooutput');
simon492e8402022-11-29 16:48:37 -0500171
172 return (
173 <CallButton
174 aria-label="volume options"
simon571240f2022-11-29 23:59:27 -0500175 expandMenuOnClick
simon492e8402022-11-29 16:48:37 -0500176 Icon={VolumeIcon}
simon571240f2022-11-29 23:59:27 -0500177 expandMenuOptions={options}
simon492e8402022-11-29 16:48:37 -0500178 {...props}
179 />
180 );
simon33c06182022-11-02 17:39:31 -0400181};
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500182
183export const CallingMicButton = (props: ExpandableButtonProps) => {
simon9a8fe202022-11-15 18:25:49 -0500184 const options = useMediaDeviceExpandMenuOptions('audioinput');
simon1170c322022-10-31 14:51:31 -0400185
186 return (
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500187 <CallButton
simon1170c322022-10-31 14:51:31 -0400188 aria-label="microphone options"
simon9076a9a2022-11-29 17:13:01 -0500189 expandMenuOptions={options}
simonf929a362022-11-18 16:53:45 -0500190 IconButtonComp={ToggleAudioCameraIconButton}
191 {...props}
192 />
193 );
194};
195
196const ToggleAudioCameraIconButton = (props: IconButtonProps) => {
simon5c677962022-12-02 16:51:54 -0500197 const { isAudioOn, setIsAudioOn } = useCallContext();
simonf929a362022-11-18 16:53:45 -0500198 return (
199 <ToggleIconButton
200 IconOn={MicroIcon}
201 IconOff={MicroOffIcon}
202 selected={isAudioOn}
simon9076a9a2022-11-29 17:13:01 -0500203 toggle={() => setIsAudioOn((v) => !v)}
simon1170c322022-10-31 14:51:31 -0400204 {...props}
205 />
206 );
207};
208
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500209export const CallingVideoCameraButton = (props: ExpandableButtonProps) => {
simon9a8fe202022-11-15 18:25:49 -0500210 const options = useMediaDeviceExpandMenuOptions('videoinput');
211
simon1170c322022-10-31 14:51:31 -0400212 return (
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500213 <CallButton
simon1170c322022-10-31 14:51:31 -0400214 aria-label="camera options"
simon9076a9a2022-11-29 17:13:01 -0500215 expandMenuOptions={options}
simonf929a362022-11-18 16:53:45 -0500216 IconButtonComp={ToggleVideoCameraIconButton}
217 {...props}
218 />
219 );
220};
221
222const ToggleVideoCameraIconButton = (props: IconButtonProps) => {
simon5c677962022-12-02 16:51:54 -0500223 const { videoStatus, updateVideoStatus } = useCallContext();
simonf929a362022-11-18 16:53:45 -0500224 return (
225 <ToggleIconButton
226 IconOn={VideoCameraIcon}
227 IconOff={VideoCameraOffIcon}
simon1e2bf342022-12-02 12:19:40 -0500228 selected={videoStatus === VideoStatus.Camera}
229 toggle={() => {
230 updateVideoStatus((v) => (v !== VideoStatus.Camera ? VideoStatus.Camera : VideoStatus.Off));
231 }}
simon1170c322022-10-31 14:51:31 -0400232 {...props}
233 />
234 );
235};
simon2d3b6532022-11-08 21:01:57 -0500236
237// Calling pending/receiving interface
simonf929a362022-11-18 16:53:45 -0500238export const CallingCancelButton = (props: IconButtonProps) => {
simon5c677962022-12-02 16:51:54 -0500239 const { endCall } = useCallContext();
simonf929a362022-11-18 16:53:45 -0500240
241 return (
idillon18283ac2023-01-07 12:06:42 -0500242 <ColoredRoundButton
simonf929a362022-11-18 16:53:45 -0500243 aria-label="cancel call"
244 onClick={() => {
simonaccd8022022-11-24 15:04:53 -0500245 endCall();
simonf929a362022-11-18 16:53:45 -0500246 }}
247 Icon={CallEndIcon}
248 paletteColor={(theme) => theme.palette.error}
249 {...props}
250 />
251 );
simon2d3b6532022-11-08 21:01:57 -0500252};
253
simonf929a362022-11-18 16:53:45 -0500254export const CallingAnswerAudioButton = (props: IconButtonProps) => {
simon5c677962022-12-02 16:51:54 -0500255 const { acceptCall, callStatus } = useCallContext();
simonf929a362022-11-18 16:53:45 -0500256
257 return (
idillon18283ac2023-01-07 12:06:42 -0500258 <ColoredRoundButton
Charlie9c8779e2022-11-29 20:10:56 -0500259 disabled={callStatus === CallStatus.Loading || callStatus === CallStatus.Connecting}
simonf929a362022-11-18 16:53:45 -0500260 aria-label="answer call audio"
261 onClick={() => {
MichelleSS55164202022-11-25 18:36:14 -0500262 acceptCall(false);
simonf929a362022-11-18 16:53:45 -0500263 }}
264 Icon={PlaceAudioCallIcon}
265 paletteColor={(theme) => theme.palette.success}
266 {...props}
267 />
268 );
simon2d3b6532022-11-08 21:01:57 -0500269};
270
simonf929a362022-11-18 16:53:45 -0500271export const CallingAnswerVideoButton = (props: IconButtonProps) => {
simon5c677962022-12-02 16:51:54 -0500272 const { acceptCall, callStatus } = useCallContext();
simonf929a362022-11-18 16:53:45 -0500273 return (
idillon18283ac2023-01-07 12:06:42 -0500274 <ColoredRoundButton
Charlie9c8779e2022-11-29 20:10:56 -0500275 disabled={callStatus === CallStatus.Connecting || callStatus === CallStatus.Loading}
simonf929a362022-11-18 16:53:45 -0500276 aria-label="answer call video"
277 onClick={() => {
MichelleSS55164202022-11-25 18:36:14 -0500278 acceptCall(true);
simonf929a362022-11-18 16:53:45 -0500279 }}
280 paletteColor={(theme) => theme.palette.success}
281 Icon={VideoCameraIcon}
282 {...props}
283 />
284 );
285};
286
287export const CallingRefuseButton = (props: IconButtonProps) => {
simon5c677962022-12-02 16:51:54 -0500288 const { endCall } = useCallContext();
simonf929a362022-11-18 16:53:45 -0500289 return (
idillon18283ac2023-01-07 12:06:42 -0500290 <ColoredRoundButton
simonf929a362022-11-18 16:53:45 -0500291 aria-label="refuse call"
292 onClick={() => {
simonaccd8022022-11-24 15:04:53 -0500293 endCall();
simonf929a362022-11-18 16:53:45 -0500294 }}
295 paletteColor={(theme) => theme.palette.error}
296 Icon={RoundCloseIcon}
297 {...props}
298 />
299 );
simon2d3b6532022-11-08 21:01:57 -0500300};