blob: 0b179bd84bff9726dbb4b39c6cd9912deb3f1622 [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';
simon9076a9a2022-11-29 17:13:01 -050024import {
idillon18283ac2023-01-07 12:06:42 -050025 ColoredRoundButton,
simon9076a9a2022-11-29 17:13:01 -050026 ExpandableButton,
27 ExpandableButtonProps,
28 ExpandMenuRadioOption,
simon9076a9a2022-11-29 17:13:01 -050029 ToggleIconButton,
30} from './Button';
simon1170c322022-10-31 14:51:31 -040031import {
32 CallEndIcon,
33 ChatBubbleIcon,
34 ExtensionIcon,
simon33c06182022-11-02 17:39:31 -040035 FullScreenIcon,
simon1170c322022-10-31 14:51:31 -040036 GroupAddIcon,
37 MicroIcon,
38 MicroOffIcon,
simon33c06182022-11-02 17:39:31 -040039 MoreVerticalIcon,
simon2d3b6532022-11-08 21:01:57 -050040 PlaceAudioCallIcon,
simon1170c322022-10-31 14:51:31 -040041 RecordingIcon,
simon2d3b6532022-11-08 21:01:57 -050042 RoundCloseIcon,
simon33c06182022-11-02 17:39:31 -040043 ScreenShareArrowIcon,
simon1e2bf342022-12-02 12:19:40 -050044 ScreenShareStopIcon,
simon1170c322022-10-31 14:51:31 -040045 VideoCameraIcon,
46 VideoCameraOffIcon,
47 VolumeIcon,
48} from './SvgIcon';
49
Gabriel Rochon8321a0d2022-11-06 23:18:36 -050050const CallButton = styled((props: ExpandableButtonProps) => {
51 return <ExpandableButton {...props} />;
52})({
simon2d3b6532022-11-08 21:01:57 -050053 color: 'white',
Gabriel Rochon8321a0d2022-11-06 23:18:36 -050054 '&:hover': {
55 backgroundColor: 'rgba(255, 255, 255, 0.15)',
56 },
Gabriel Rochon8321a0d2022-11-06 23:18:36 -050057});
58
simon33c06182022-11-02 17:39:31 -040059export const CallingChatButton = (props: ExpandableButtonProps) => {
simon5c677962022-12-02 16:51:54 -050060 const { setIsChatShown } = useCallContext();
simonf9d78f22022-11-25 15:47:15 -050061 return (
62 <CallButton
63 aria-label="chat"
64 Icon={ChatBubbleIcon}
65 onClick={() => {
66 setIsChatShown((v) => !v);
67 }}
68 {...props}
69 />
70 );
simon1170c322022-10-31 14:51:31 -040071};
simon33c06182022-11-02 17:39:31 -040072
73export const CallingEndButton = (props: ExpandableButtonProps) => {
simon5c677962022-12-02 16:51:54 -050074 const { endCall } = useCallContext();
simonf929a362022-11-18 16:53:45 -050075 return (
idillon18283ac2023-01-07 12:06:42 -050076 <ColoredRoundButton
simonf929a362022-11-18 16:53:45 -050077 paletteColor={(theme) => theme.palette.error}
78 onClick={() => {
simonaccd8022022-11-24 15:04:53 -050079 endCall();
simonf929a362022-11-18 16:53:45 -050080 }}
81 aria-label="call end"
82 Icon={CallEndIcon}
83 {...props}
84 />
85 );
simon1170c322022-10-31 14:51:31 -040086};
simon33c06182022-11-02 17:39:31 -040087
88export const CallingExtensionButton = (props: ExpandableButtonProps) => {
Gabriel Rochon8321a0d2022-11-06 23:18:36 -050089 return <CallButton aria-label="extensions" Icon={ExtensionIcon} {...props} />;
simon1170c322022-10-31 14:51:31 -040090};
simon33c06182022-11-02 17:39:31 -040091
92export const CallingFullScreenButton = (props: ExpandableButtonProps) => {
simon5c677962022-12-02 16:51:54 -050093 const { setIsFullscreen } = useCallContext();
simon2a5cf142022-11-25 15:47:35 -050094 return (
95 <CallButton
96 aria-label="full screen"
97 Icon={FullScreenIcon}
98 onClick={() => {
99 setIsFullscreen((v) => !v);
100 }}
101 {...props}
102 />
103 );
simon1170c322022-10-31 14:51:31 -0400104};
simon33c06182022-11-02 17:39:31 -0400105
106export const CallingGroupButton = (props: ExpandableButtonProps) => {
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500107 return <CallButton aria-label="group options" Icon={GroupAddIcon} {...props} />;
simon1170c322022-10-31 14:51:31 -0400108};
simon33c06182022-11-02 17:39:31 -0400109
110export const CallingMoreVerticalButton = (props: ExpandableButtonProps) => {
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500111 return <CallButton aria-label="more vertical" Icon={MoreVerticalIcon} {...props} />;
simon1170c322022-10-31 14:51:31 -0400112};
simon33c06182022-11-02 17:39:31 -0400113
114export const CallingRecordButton = (props: ExpandableButtonProps) => {
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500115 return <CallButton aria-label="recording options" Icon={RecordingIcon} {...props} />;
simon33c06182022-11-02 17:39:31 -0400116};
117
118export const CallingScreenShareButton = (props: ExpandableButtonProps) => {
simon1170c322022-10-31 14:51:31 -0400119 return (
simon1e2bf342022-12-02 12:19:40 -0500120 <CallButton aria-label="screen share" expandMenuOnClick IconButtonComp={ToggleScreenShareIconButton} {...props} />
121 );
122};
123
124const ToggleScreenShareIconButton = (props: IconButtonProps) => {
simon5c677962022-12-02 16:51:54 -0500125 const { videoStatus, updateVideoStatus } = useCallContext();
simon1e2bf342022-12-02 12:19:40 -0500126
127 return (
128 <ToggleIconButton
129 IconOff={ScreenShareArrowIcon}
130 IconOn={ScreenShareStopIcon}
131 selected={videoStatus === VideoStatus.ScreenShare}
132 toggle={() => {
133 updateVideoStatus((v) => (v !== VideoStatus.ScreenShare ? VideoStatus.ScreenShare : VideoStatus.Off));
134 }}
simon33c06182022-11-02 17:39:31 -0400135 {...props}
136 />
simon1170c322022-10-31 14:51:31 -0400137 );
138};
139
simon9076a9a2022-11-29 17:13:01 -0500140const useMediaDeviceExpandMenuOptions = (kind: MediaDeviceKind): ExpandMenuRadioOption[] | undefined => {
simon5c677962022-12-02 16:51:54 -0500141 const { currentMediaDeviceIds, mediaDevices } = useCallContext();
simon9a8fe202022-11-15 18:25:49 -0500142
simon9076a9a2022-11-29 17:13:01 -0500143 const options = useMemo(
simon9a8fe202022-11-15 18:25:49 -0500144 () =>
145 mediaDevices[kind].map((device) => ({
146 key: device.deviceId,
147 description: device.label,
148 })),
149 [mediaDevices, kind]
150 );
simon9076a9a2022-11-29 17:13:01 -0500151
simon492e8402022-11-29 16:48:37 -0500152 const currentDevice = currentMediaDeviceIds[kind];
153
154 if (options.length === 0) {
155 return undefined;
156 }
157 return [
158 {
159 options,
160 value: currentDevice.id ?? '',
161 onChange: (e: ChangeEvent<HTMLInputElement>) => {
162 currentDevice.setId(e.target.value);
163 },
164 },
165 ];
simon9a8fe202022-11-15 18:25:49 -0500166};
167
simon33c06182022-11-02 17:39:31 -0400168export const CallingVolumeButton = (props: ExpandableButtonProps) => {
simon9a8fe202022-11-15 18:25:49 -0500169 const options = useMediaDeviceExpandMenuOptions('audiooutput');
simon492e8402022-11-29 16:48:37 -0500170
171 return (
172 <CallButton
173 aria-label="volume options"
simon571240f2022-11-29 23:59:27 -0500174 expandMenuOnClick
simon492e8402022-11-29 16:48:37 -0500175 Icon={VolumeIcon}
simon571240f2022-11-29 23:59:27 -0500176 expandMenuOptions={options}
simon492e8402022-11-29 16:48:37 -0500177 {...props}
178 />
179 );
simon33c06182022-11-02 17:39:31 -0400180};
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500181
182export const CallingMicButton = (props: ExpandableButtonProps) => {
simon9a8fe202022-11-15 18:25:49 -0500183 const options = useMediaDeviceExpandMenuOptions('audioinput');
simon1170c322022-10-31 14:51:31 -0400184
185 return (
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500186 <CallButton
simon1170c322022-10-31 14:51:31 -0400187 aria-label="microphone options"
simon9076a9a2022-11-29 17:13:01 -0500188 expandMenuOptions={options}
simonf929a362022-11-18 16:53:45 -0500189 IconButtonComp={ToggleAudioCameraIconButton}
190 {...props}
191 />
192 );
193};
194
195const ToggleAudioCameraIconButton = (props: IconButtonProps) => {
simon5c677962022-12-02 16:51:54 -0500196 const { isAudioOn, setIsAudioOn } = useCallContext();
simonf929a362022-11-18 16:53:45 -0500197 return (
198 <ToggleIconButton
199 IconOn={MicroIcon}
200 IconOff={MicroOffIcon}
201 selected={isAudioOn}
simon9076a9a2022-11-29 17:13:01 -0500202 toggle={() => setIsAudioOn((v) => !v)}
simon1170c322022-10-31 14:51:31 -0400203 {...props}
204 />
205 );
206};
207
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500208export const CallingVideoCameraButton = (props: ExpandableButtonProps) => {
simon9a8fe202022-11-15 18:25:49 -0500209 const options = useMediaDeviceExpandMenuOptions('videoinput');
210
simon1170c322022-10-31 14:51:31 -0400211 return (
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500212 <CallButton
simon1170c322022-10-31 14:51:31 -0400213 aria-label="camera options"
simon9076a9a2022-11-29 17:13:01 -0500214 expandMenuOptions={options}
simonf929a362022-11-18 16:53:45 -0500215 IconButtonComp={ToggleVideoCameraIconButton}
216 {...props}
217 />
218 );
219};
220
221const ToggleVideoCameraIconButton = (props: IconButtonProps) => {
simon5c677962022-12-02 16:51:54 -0500222 const { videoStatus, updateVideoStatus } = useCallContext();
simonf929a362022-11-18 16:53:45 -0500223 return (
224 <ToggleIconButton
225 IconOn={VideoCameraIcon}
226 IconOff={VideoCameraOffIcon}
simon1e2bf342022-12-02 12:19:40 -0500227 selected={videoStatus === VideoStatus.Camera}
228 toggle={() => {
229 updateVideoStatus((v) => (v !== VideoStatus.Camera ? VideoStatus.Camera : VideoStatus.Off));
230 }}
simon1170c322022-10-31 14:51:31 -0400231 {...props}
232 />
233 );
234};
simon2d3b6532022-11-08 21:01:57 -0500235
236// Calling pending/receiving interface
simonf929a362022-11-18 16:53:45 -0500237export const CallingCancelButton = (props: IconButtonProps) => {
simon5c677962022-12-02 16:51:54 -0500238 const { endCall } = useCallContext();
simonf929a362022-11-18 16:53:45 -0500239
240 return (
idillon18283ac2023-01-07 12:06:42 -0500241 <ColoredRoundButton
simonf929a362022-11-18 16:53:45 -0500242 aria-label="cancel call"
243 onClick={() => {
simonaccd8022022-11-24 15:04:53 -0500244 endCall();
simonf929a362022-11-18 16:53:45 -0500245 }}
246 Icon={CallEndIcon}
247 paletteColor={(theme) => theme.palette.error}
248 {...props}
249 />
250 );
simon2d3b6532022-11-08 21:01:57 -0500251};
252
simonf929a362022-11-18 16:53:45 -0500253export const CallingAnswerAudioButton = (props: IconButtonProps) => {
simon5c677962022-12-02 16:51:54 -0500254 const { acceptCall, callStatus } = useCallContext();
simonf929a362022-11-18 16:53:45 -0500255
256 return (
idillon18283ac2023-01-07 12:06:42 -0500257 <ColoredRoundButton
Charlie9c8779e2022-11-29 20:10:56 -0500258 disabled={callStatus === CallStatus.Loading || callStatus === CallStatus.Connecting}
simonf929a362022-11-18 16:53:45 -0500259 aria-label="answer call audio"
260 onClick={() => {
MichelleSS55164202022-11-25 18:36:14 -0500261 acceptCall(false);
simonf929a362022-11-18 16:53:45 -0500262 }}
263 Icon={PlaceAudioCallIcon}
264 paletteColor={(theme) => theme.palette.success}
265 {...props}
266 />
267 );
simon2d3b6532022-11-08 21:01:57 -0500268};
269
simonf929a362022-11-18 16:53:45 -0500270export const CallingAnswerVideoButton = (props: IconButtonProps) => {
simon5c677962022-12-02 16:51:54 -0500271 const { acceptCall, callStatus } = useCallContext();
simonf929a362022-11-18 16:53:45 -0500272 return (
idillon18283ac2023-01-07 12:06:42 -0500273 <ColoredRoundButton
Charlie9c8779e2022-11-29 20:10:56 -0500274 disabled={callStatus === CallStatus.Connecting || callStatus === CallStatus.Loading}
simonf929a362022-11-18 16:53:45 -0500275 aria-label="answer call video"
276 onClick={() => {
MichelleSS55164202022-11-25 18:36:14 -0500277 acceptCall(true);
simonf929a362022-11-18 16:53:45 -0500278 }}
279 paletteColor={(theme) => theme.palette.success}
280 Icon={VideoCameraIcon}
281 {...props}
282 />
283 );
284};
285
286export const CallingRefuseButton = (props: IconButtonProps) => {
simon5c677962022-12-02 16:51:54 -0500287 const { endCall } = useCallContext();
simonf929a362022-11-18 16:53:45 -0500288 return (
idillon18283ac2023-01-07 12:06:42 -0500289 <ColoredRoundButton
simonf929a362022-11-18 16:53:45 -0500290 aria-label="refuse call"
291 onClick={() => {
simonaccd8022022-11-24 15:04:53 -0500292 endCall();
simonf929a362022-11-18 16:53:45 -0500293 }}
294 paletteColor={(theme) => theme.palette.error}
295 Icon={RoundCloseIcon}
296 {...props}
297 />
298 );
simon2d3b6532022-11-08 21:01:57 -0500299};