blob: db0f5305911e39192182d85ef86bb160f77659fd [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
simonf929a362022-11-18 16:53:45 -050019import { IconButton, IconButtonProps, PaletteColor } from '@mui/material';
20import { styled, Theme } from '@mui/material/styles';
simon492e8402022-11-29 16:48:37 -050021import { ChangeEvent, useContext, useMemo } from 'react';
simon1170c322022-10-31 14:51:31 -040022
simon1e2bf342022-12-02 12:19:40 -050023import { CallContext, CallStatus, VideoStatus } from '../contexts/CallProvider';
simon9076a9a2022-11-29 17:13:01 -050024import {
25 ExpandableButton,
26 ExpandableButtonProps,
27 ExpandMenuRadioOption,
28 ShapedButtonProps,
29 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
simon2d3b6532022-11-08 21:01:57 -050059const ColoredCallButton = styled(
60 ({
simonf929a362022-11-18 16:53:45 -050061 paletteColor,
62 Icon,
simon2d3b6532022-11-08 21:01:57 -050063 ...props
simonf929a362022-11-18 16:53:45 -050064 }: ShapedButtonProps & {
65 paletteColor?: PaletteColor | ((theme: Theme) => PaletteColor);
simon2d3b6532022-11-08 21:01:57 -050066 }) => {
simonf929a362022-11-18 16:53:45 -050067 return (
68 <IconButton {...props} disableRipple={true}>
69 <Icon fontSize="inherit" />
70 </IconButton>
71 );
simon2d3b6532022-11-08 21:01:57 -050072 }
simonf929a362022-11-18 16:53:45 -050073)(({ theme, paletteColor = theme.palette.primary }) => {
74 if (typeof paletteColor === 'function') {
75 paletteColor = paletteColor(theme);
76 }
77
simon2d3b6532022-11-08 21:01:57 -050078 return {
simonf929a362022-11-18 16:53:45 -050079 color: paletteColor.contrastText,
80 backgroundColor: paletteColor.dark,
simon2d3b6532022-11-08 21:01:57 -050081 '&:hover': {
simonf929a362022-11-18 16:53:45 -050082 backgroundColor: paletteColor.main,
simon2d3b6532022-11-08 21:01:57 -050083 },
Charlie4b904152022-11-29 21:03:53 -050084 '&:disabled': {
85 backgroundColor: theme.palette.action.disabled,
86 },
simon2d3b6532022-11-08 21:01:57 -050087 };
Gabriel Rochon8321a0d2022-11-06 23:18:36 -050088});
89
simon33c06182022-11-02 17:39:31 -040090export const CallingChatButton = (props: ExpandableButtonProps) => {
simonf9d78f22022-11-25 15:47:15 -050091 const { setIsChatShown } = useContext(CallContext);
92 return (
93 <CallButton
94 aria-label="chat"
95 Icon={ChatBubbleIcon}
96 onClick={() => {
97 setIsChatShown((v) => !v);
98 }}
99 {...props}
100 />
101 );
simon1170c322022-10-31 14:51:31 -0400102};
simon33c06182022-11-02 17:39:31 -0400103
104export const CallingEndButton = (props: ExpandableButtonProps) => {
simonaccd8022022-11-24 15:04:53 -0500105 const { endCall } = useContext(CallContext);
simonf929a362022-11-18 16:53:45 -0500106 return (
107 <ColoredCallButton
108 paletteColor={(theme) => theme.palette.error}
109 onClick={() => {
simonaccd8022022-11-24 15:04:53 -0500110 endCall();
simonf929a362022-11-18 16:53:45 -0500111 }}
112 aria-label="call end"
113 Icon={CallEndIcon}
114 {...props}
115 />
116 );
simon1170c322022-10-31 14:51:31 -0400117};
simon33c06182022-11-02 17:39:31 -0400118
119export const CallingExtensionButton = (props: ExpandableButtonProps) => {
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500120 return <CallButton aria-label="extensions" Icon={ExtensionIcon} {...props} />;
simon1170c322022-10-31 14:51:31 -0400121};
simon33c06182022-11-02 17:39:31 -0400122
123export const CallingFullScreenButton = (props: ExpandableButtonProps) => {
simon2a5cf142022-11-25 15:47:35 -0500124 const { setIsFullscreen } = useContext(CallContext);
125 return (
126 <CallButton
127 aria-label="full screen"
128 Icon={FullScreenIcon}
129 onClick={() => {
130 setIsFullscreen((v) => !v);
131 }}
132 {...props}
133 />
134 );
simon1170c322022-10-31 14:51:31 -0400135};
simon33c06182022-11-02 17:39:31 -0400136
137export const CallingGroupButton = (props: ExpandableButtonProps) => {
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500138 return <CallButton aria-label="group options" Icon={GroupAddIcon} {...props} />;
simon1170c322022-10-31 14:51:31 -0400139};
simon33c06182022-11-02 17:39:31 -0400140
141export const CallingMoreVerticalButton = (props: ExpandableButtonProps) => {
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500142 return <CallButton aria-label="more vertical" Icon={MoreVerticalIcon} {...props} />;
simon1170c322022-10-31 14:51:31 -0400143};
simon33c06182022-11-02 17:39:31 -0400144
145export const CallingRecordButton = (props: ExpandableButtonProps) => {
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500146 return <CallButton aria-label="recording options" Icon={RecordingIcon} {...props} />;
simon33c06182022-11-02 17:39:31 -0400147};
148
149export const CallingScreenShareButton = (props: ExpandableButtonProps) => {
simon1170c322022-10-31 14:51:31 -0400150 return (
simon1e2bf342022-12-02 12:19:40 -0500151 <CallButton aria-label="screen share" expandMenuOnClick IconButtonComp={ToggleScreenShareIconButton} {...props} />
152 );
153};
154
155const ToggleScreenShareIconButton = (props: IconButtonProps) => {
156 const { videoStatus, updateVideoStatus } = useContext(CallContext);
157
158 return (
159 <ToggleIconButton
160 IconOff={ScreenShareArrowIcon}
161 IconOn={ScreenShareStopIcon}
162 selected={videoStatus === VideoStatus.ScreenShare}
163 toggle={() => {
164 updateVideoStatus((v) => (v !== VideoStatus.ScreenShare ? VideoStatus.ScreenShare : VideoStatus.Off));
165 }}
simon33c06182022-11-02 17:39:31 -0400166 {...props}
167 />
simon1170c322022-10-31 14:51:31 -0400168 );
169};
170
simon9076a9a2022-11-29 17:13:01 -0500171const useMediaDeviceExpandMenuOptions = (kind: MediaDeviceKind): ExpandMenuRadioOption[] | undefined => {
simon492e8402022-11-29 16:48:37 -0500172 const { currentMediaDeviceIds, mediaDevices } = useContext(CallContext);
simon9a8fe202022-11-15 18:25:49 -0500173
simon9076a9a2022-11-29 17:13:01 -0500174 const options = useMemo(
simon9a8fe202022-11-15 18:25:49 -0500175 () =>
176 mediaDevices[kind].map((device) => ({
177 key: device.deviceId,
178 description: device.label,
179 })),
180 [mediaDevices, kind]
181 );
simon9076a9a2022-11-29 17:13:01 -0500182
simon492e8402022-11-29 16:48:37 -0500183 const currentDevice = currentMediaDeviceIds[kind];
184
185 if (options.length === 0) {
186 return undefined;
187 }
188 return [
189 {
190 options,
191 value: currentDevice.id ?? '',
192 onChange: (e: ChangeEvent<HTMLInputElement>) => {
193 currentDevice.setId(e.target.value);
194 },
195 },
196 ];
simon9a8fe202022-11-15 18:25:49 -0500197};
198
simon33c06182022-11-02 17:39:31 -0400199export const CallingVolumeButton = (props: ExpandableButtonProps) => {
simon9a8fe202022-11-15 18:25:49 -0500200 const options = useMediaDeviceExpandMenuOptions('audiooutput');
simon492e8402022-11-29 16:48:37 -0500201
202 return (
203 <CallButton
204 aria-label="volume options"
simon571240f2022-11-29 23:59:27 -0500205 expandMenuOnClick
simon492e8402022-11-29 16:48:37 -0500206 Icon={VolumeIcon}
simon571240f2022-11-29 23:59:27 -0500207 expandMenuOptions={options}
simon492e8402022-11-29 16:48:37 -0500208 {...props}
209 />
210 );
simon33c06182022-11-02 17:39:31 -0400211};
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500212
213export const CallingMicButton = (props: ExpandableButtonProps) => {
simon9a8fe202022-11-15 18:25:49 -0500214 const options = useMediaDeviceExpandMenuOptions('audioinput');
simon1170c322022-10-31 14:51:31 -0400215
216 return (
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500217 <CallButton
simon1170c322022-10-31 14:51:31 -0400218 aria-label="microphone options"
simon9076a9a2022-11-29 17:13:01 -0500219 expandMenuOptions={options}
simonf929a362022-11-18 16:53:45 -0500220 IconButtonComp={ToggleAudioCameraIconButton}
221 {...props}
222 />
223 );
224};
225
226const ToggleAudioCameraIconButton = (props: IconButtonProps) => {
simon9076a9a2022-11-29 17:13:01 -0500227 const { isAudioOn, setIsAudioOn } = useContext(CallContext);
simonf929a362022-11-18 16:53:45 -0500228 return (
229 <ToggleIconButton
230 IconOn={MicroIcon}
231 IconOff={MicroOffIcon}
232 selected={isAudioOn}
simon9076a9a2022-11-29 17:13:01 -0500233 toggle={() => setIsAudioOn((v) => !v)}
simon1170c322022-10-31 14:51:31 -0400234 {...props}
235 />
236 );
237};
238
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500239export const CallingVideoCameraButton = (props: ExpandableButtonProps) => {
simon9a8fe202022-11-15 18:25:49 -0500240 const options = useMediaDeviceExpandMenuOptions('videoinput');
241
simon1170c322022-10-31 14:51:31 -0400242 return (
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500243 <CallButton
simon1170c322022-10-31 14:51:31 -0400244 aria-label="camera options"
simon9076a9a2022-11-29 17:13:01 -0500245 expandMenuOptions={options}
simonf929a362022-11-18 16:53:45 -0500246 IconButtonComp={ToggleVideoCameraIconButton}
247 {...props}
248 />
249 );
250};
251
252const ToggleVideoCameraIconButton = (props: IconButtonProps) => {
simon1e2bf342022-12-02 12:19:40 -0500253 const { videoStatus, updateVideoStatus } = useContext(CallContext);
simonf929a362022-11-18 16:53:45 -0500254 return (
255 <ToggleIconButton
256 IconOn={VideoCameraIcon}
257 IconOff={VideoCameraOffIcon}
simon1e2bf342022-12-02 12:19:40 -0500258 selected={videoStatus === VideoStatus.Camera}
259 toggle={() => {
260 updateVideoStatus((v) => (v !== VideoStatus.Camera ? VideoStatus.Camera : VideoStatus.Off));
261 }}
simon1170c322022-10-31 14:51:31 -0400262 {...props}
263 />
264 );
265};
simon2d3b6532022-11-08 21:01:57 -0500266
267// Calling pending/receiving interface
simonf929a362022-11-18 16:53:45 -0500268export const CallingCancelButton = (props: IconButtonProps) => {
simonaccd8022022-11-24 15:04:53 -0500269 const { endCall } = useContext(CallContext);
simonf929a362022-11-18 16:53:45 -0500270
271 return (
272 <ColoredCallButton
273 aria-label="cancel call"
274 onClick={() => {
simonaccd8022022-11-24 15:04:53 -0500275 endCall();
simonf929a362022-11-18 16:53:45 -0500276 }}
277 Icon={CallEndIcon}
278 paletteColor={(theme) => theme.palette.error}
279 {...props}
280 />
281 );
simon2d3b6532022-11-08 21:01:57 -0500282};
283
simonf929a362022-11-18 16:53:45 -0500284export const CallingAnswerAudioButton = (props: IconButtonProps) => {
Charlie9c8779e2022-11-29 20:10:56 -0500285 const { acceptCall, callStatus } = useContext(CallContext);
simonf929a362022-11-18 16:53:45 -0500286
287 return (
288 <ColoredCallButton
Charlie9c8779e2022-11-29 20:10:56 -0500289 disabled={callStatus === CallStatus.Loading || callStatus === CallStatus.Connecting}
simonf929a362022-11-18 16:53:45 -0500290 aria-label="answer call audio"
291 onClick={() => {
MichelleSS55164202022-11-25 18:36:14 -0500292 acceptCall(false);
simonf929a362022-11-18 16:53:45 -0500293 }}
294 Icon={PlaceAudioCallIcon}
295 paletteColor={(theme) => theme.palette.success}
296 {...props}
297 />
298 );
simon2d3b6532022-11-08 21:01:57 -0500299};
300
simonf929a362022-11-18 16:53:45 -0500301export const CallingAnswerVideoButton = (props: IconButtonProps) => {
Charlie9c8779e2022-11-29 20:10:56 -0500302 const { acceptCall, callStatus } = useContext(CallContext);
simonf929a362022-11-18 16:53:45 -0500303 return (
304 <ColoredCallButton
Charlie9c8779e2022-11-29 20:10:56 -0500305 disabled={callStatus === CallStatus.Connecting || callStatus === CallStatus.Loading}
simonf929a362022-11-18 16:53:45 -0500306 aria-label="answer call video"
307 onClick={() => {
MichelleSS55164202022-11-25 18:36:14 -0500308 acceptCall(true);
simonf929a362022-11-18 16:53:45 -0500309 }}
310 paletteColor={(theme) => theme.palette.success}
311 Icon={VideoCameraIcon}
312 {...props}
313 />
314 );
315};
316
317export const CallingRefuseButton = (props: IconButtonProps) => {
simonaccd8022022-11-24 15:04:53 -0500318 const { endCall } = useContext(CallContext);
simonf929a362022-11-18 16:53:45 -0500319 return (
320 <ColoredCallButton
321 aria-label="refuse call"
322 onClick={() => {
simonaccd8022022-11-24 15:04:53 -0500323 endCall();
simonf929a362022-11-18 16:53:45 -0500324 }}
325 paletteColor={(theme) => theme.palette.error}
326 Icon={RoundCloseIcon}
327 {...props}
328 />
329 );
simon2d3b6532022-11-08 21:01:57 -0500330};