blob: b77fd5a8fc68f3c08e94f2fcd72b76d6ff6b7f3b [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';
simon4e7445c2022-11-16 21:18:46 -050022import { useTranslation } from 'react-i18next';
simon1170c322022-10-31 14:51:31 -040023
Charlie9c8779e2022-11-29 20:10:56 -050024import { CallContext, CallStatus } from '../contexts/CallProvider';
simon9076a9a2022-11-29 17:13:01 -050025import {
26 ExpandableButton,
27 ExpandableButtonProps,
28 ExpandMenuRadioOption,
29 ShapedButtonProps,
30 ToggleIconButton,
31} from './Button';
simon1170c322022-10-31 14:51:31 -040032import {
33 CallEndIcon,
34 ChatBubbleIcon,
35 ExtensionIcon,
simon33c06182022-11-02 17:39:31 -040036 FileIcon,
37 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,
46 ScreenShareRegularIcon,
47 ScreenShareScreenAreaIcon,
simon1170c322022-10-31 14:51:31 -040048 VideoCameraIcon,
49 VideoCameraOffIcon,
50 VolumeIcon,
Gabriel Rochon8321a0d2022-11-06 23:18:36 -050051 WindowIcon,
simon1170c322022-10-31 14:51:31 -040052} from './SvgIcon';
53
Gabriel Rochon8321a0d2022-11-06 23:18:36 -050054const CallButton = styled((props: ExpandableButtonProps) => {
55 return <ExpandableButton {...props} />;
56})({
simon2d3b6532022-11-08 21:01:57 -050057 color: 'white',
Gabriel Rochon8321a0d2022-11-06 23:18:36 -050058 '&:hover': {
59 backgroundColor: 'rgba(255, 255, 255, 0.15)',
60 },
Gabriel Rochon8321a0d2022-11-06 23:18:36 -050061});
62
simon2d3b6532022-11-08 21:01:57 -050063const ColoredCallButton = styled(
64 ({
simonf929a362022-11-18 16:53:45 -050065 paletteColor,
66 Icon,
simon2d3b6532022-11-08 21:01:57 -050067 ...props
simonf929a362022-11-18 16:53:45 -050068 }: ShapedButtonProps & {
69 paletteColor?: PaletteColor | ((theme: Theme) => PaletteColor);
simon2d3b6532022-11-08 21:01:57 -050070 }) => {
simonf929a362022-11-18 16:53:45 -050071 return (
72 <IconButton {...props} disableRipple={true}>
73 <Icon fontSize="inherit" />
74 </IconButton>
75 );
simon2d3b6532022-11-08 21:01:57 -050076 }
simonf929a362022-11-18 16:53:45 -050077)(({ theme, paletteColor = theme.palette.primary }) => {
78 if (typeof paletteColor === 'function') {
79 paletteColor = paletteColor(theme);
80 }
81
simon2d3b6532022-11-08 21:01:57 -050082 return {
simonf929a362022-11-18 16:53:45 -050083 color: paletteColor.contrastText,
84 backgroundColor: paletteColor.dark,
simon2d3b6532022-11-08 21:01:57 -050085 '&:hover': {
simonf929a362022-11-18 16:53:45 -050086 backgroundColor: paletteColor.main,
simon2d3b6532022-11-08 21:01:57 -050087 },
Charlie4b904152022-11-29 21:03:53 -050088 '&:disabled': {
89 backgroundColor: theme.palette.action.disabled,
90 },
simon2d3b6532022-11-08 21:01:57 -050091 };
Gabriel Rochon8321a0d2022-11-06 23:18:36 -050092});
93
simon33c06182022-11-02 17:39:31 -040094export const CallingChatButton = (props: ExpandableButtonProps) => {
simonf9d78f22022-11-25 15:47:15 -050095 const { setIsChatShown } = useContext(CallContext);
96 return (
97 <CallButton
98 aria-label="chat"
99 Icon={ChatBubbleIcon}
100 onClick={() => {
101 setIsChatShown((v) => !v);
102 }}
103 {...props}
104 />
105 );
simon1170c322022-10-31 14:51:31 -0400106};
simon33c06182022-11-02 17:39:31 -0400107
108export const CallingEndButton = (props: ExpandableButtonProps) => {
simonaccd8022022-11-24 15:04:53 -0500109 const { endCall } = useContext(CallContext);
simonf929a362022-11-18 16:53:45 -0500110 return (
111 <ColoredCallButton
112 paletteColor={(theme) => theme.palette.error}
113 onClick={() => {
simonaccd8022022-11-24 15:04:53 -0500114 endCall();
simonf929a362022-11-18 16:53:45 -0500115 }}
116 aria-label="call end"
117 Icon={CallEndIcon}
118 {...props}
119 />
120 );
simon1170c322022-10-31 14:51:31 -0400121};
simon33c06182022-11-02 17:39:31 -0400122
123export const CallingExtensionButton = (props: ExpandableButtonProps) => {
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500124 return <CallButton aria-label="extensions" Icon={ExtensionIcon} {...props} />;
simon1170c322022-10-31 14:51:31 -0400125};
simon33c06182022-11-02 17:39:31 -0400126
127export const CallingFullScreenButton = (props: ExpandableButtonProps) => {
simon2a5cf142022-11-25 15:47:35 -0500128 const { setIsFullscreen } = useContext(CallContext);
129 return (
130 <CallButton
131 aria-label="full screen"
132 Icon={FullScreenIcon}
133 onClick={() => {
134 setIsFullscreen((v) => !v);
135 }}
136 {...props}
137 />
138 );
simon1170c322022-10-31 14:51:31 -0400139};
simon33c06182022-11-02 17:39:31 -0400140
141export const CallingGroupButton = (props: ExpandableButtonProps) => {
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500142 return <CallButton aria-label="group options" Icon={GroupAddIcon} {...props} />;
simon1170c322022-10-31 14:51:31 -0400143};
simon33c06182022-11-02 17:39:31 -0400144
145export const CallingMoreVerticalButton = (props: ExpandableButtonProps) => {
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500146 return <CallButton aria-label="more vertical" Icon={MoreVerticalIcon} {...props} />;
simon1170c322022-10-31 14:51:31 -0400147};
simon33c06182022-11-02 17:39:31 -0400148
149export const CallingRecordButton = (props: ExpandableButtonProps) => {
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500150 return <CallButton aria-label="recording options" Icon={RecordingIcon} {...props} />;
simon33c06182022-11-02 17:39:31 -0400151};
152
153export const CallingScreenShareButton = (props: ExpandableButtonProps) => {
simon4e7445c2022-11-16 21:18:46 -0500154 const { t } = useTranslation();
simon1170c322022-10-31 14:51:31 -0400155 return (
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500156 <CallButton
simon33c06182022-11-02 17:39:31 -0400157 aria-label="screen share"
simon571240f2022-11-29 23:59:27 -0500158 expandMenuOnClick
simon33c06182022-11-02 17:39:31 -0400159 Icon={ScreenShareArrowIcon}
160 expandMenuOptions={[
161 {
simon4e7445c2022-11-16 21:18:46 -0500162 description: t('share_screen'),
simon33c06182022-11-02 17:39:31 -0400163 icon: <ScreenShareRegularIcon />,
164 },
165 {
simon4e7445c2022-11-16 21:18:46 -0500166 description: t('share_window'),
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500167 icon: <WindowIcon />,
168 },
169 {
simon4e7445c2022-11-16 21:18:46 -0500170 description: t('share_screen_area'),
simon33c06182022-11-02 17:39:31 -0400171 icon: <ScreenShareScreenAreaIcon />,
172 },
173 {
simon4e7445c2022-11-16 21:18:46 -0500174 description: t('share_file'),
simon33c06182022-11-02 17:39:31 -0400175 icon: <FileIcon />,
176 },
177 ]}
178 {...props}
179 />
simon1170c322022-10-31 14:51:31 -0400180 );
181};
182
simon9076a9a2022-11-29 17:13:01 -0500183const useMediaDeviceExpandMenuOptions = (kind: MediaDeviceKind): ExpandMenuRadioOption[] | undefined => {
simon492e8402022-11-29 16:48:37 -0500184 const { currentMediaDeviceIds, mediaDevices } = useContext(CallContext);
simon9a8fe202022-11-15 18:25:49 -0500185
simon9076a9a2022-11-29 17:13:01 -0500186 const options = useMemo(
simon9a8fe202022-11-15 18:25:49 -0500187 () =>
188 mediaDevices[kind].map((device) => ({
189 key: device.deviceId,
190 description: device.label,
191 })),
192 [mediaDevices, kind]
193 );
simon9076a9a2022-11-29 17:13:01 -0500194
simon492e8402022-11-29 16:48:37 -0500195 const currentDevice = currentMediaDeviceIds[kind];
196
197 if (options.length === 0) {
198 return undefined;
199 }
200 return [
201 {
202 options,
203 value: currentDevice.id ?? '',
204 onChange: (e: ChangeEvent<HTMLInputElement>) => {
205 currentDevice.setId(e.target.value);
206 },
207 },
208 ];
simon9a8fe202022-11-15 18:25:49 -0500209};
210
simon33c06182022-11-02 17:39:31 -0400211export const CallingVolumeButton = (props: ExpandableButtonProps) => {
simon9a8fe202022-11-15 18:25:49 -0500212 const options = useMediaDeviceExpandMenuOptions('audiooutput');
simon492e8402022-11-29 16:48:37 -0500213
214 return (
215 <CallButton
216 aria-label="volume options"
simon571240f2022-11-29 23:59:27 -0500217 expandMenuOnClick
simon492e8402022-11-29 16:48:37 -0500218 Icon={VolumeIcon}
simon571240f2022-11-29 23:59:27 -0500219 expandMenuOptions={options}
simon492e8402022-11-29 16:48:37 -0500220 {...props}
221 />
222 );
simon33c06182022-11-02 17:39:31 -0400223};
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500224
225export const CallingMicButton = (props: ExpandableButtonProps) => {
simon9a8fe202022-11-15 18:25:49 -0500226 const options = useMediaDeviceExpandMenuOptions('audioinput');
simon1170c322022-10-31 14:51:31 -0400227
228 return (
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500229 <CallButton
simon1170c322022-10-31 14:51:31 -0400230 aria-label="microphone options"
simon9076a9a2022-11-29 17:13:01 -0500231 expandMenuOptions={options}
simonf929a362022-11-18 16:53:45 -0500232 IconButtonComp={ToggleAudioCameraIconButton}
233 {...props}
234 />
235 );
236};
237
238const ToggleAudioCameraIconButton = (props: IconButtonProps) => {
simon9076a9a2022-11-29 17:13:01 -0500239 const { isAudioOn, setIsAudioOn } = useContext(CallContext);
simonf929a362022-11-18 16:53:45 -0500240 return (
241 <ToggleIconButton
242 IconOn={MicroIcon}
243 IconOff={MicroOffIcon}
244 selected={isAudioOn}
simon9076a9a2022-11-29 17:13:01 -0500245 toggle={() => setIsAudioOn((v) => !v)}
simon1170c322022-10-31 14:51:31 -0400246 {...props}
247 />
248 );
249};
250
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500251export const CallingVideoCameraButton = (props: ExpandableButtonProps) => {
simon9a8fe202022-11-15 18:25:49 -0500252 const options = useMediaDeviceExpandMenuOptions('videoinput');
253
simon1170c322022-10-31 14:51:31 -0400254 return (
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500255 <CallButton
simon1170c322022-10-31 14:51:31 -0400256 aria-label="camera options"
simon9076a9a2022-11-29 17:13:01 -0500257 expandMenuOptions={options}
simonf929a362022-11-18 16:53:45 -0500258 IconButtonComp={ToggleVideoCameraIconButton}
259 {...props}
260 />
261 );
262};
263
264const ToggleVideoCameraIconButton = (props: IconButtonProps) => {
simon9076a9a2022-11-29 17:13:01 -0500265 const { isVideoOn, setIsVideoOn } = useContext(CallContext);
simonf929a362022-11-18 16:53:45 -0500266 return (
267 <ToggleIconButton
268 IconOn={VideoCameraIcon}
269 IconOff={VideoCameraOffIcon}
270 selected={isVideoOn}
simon9076a9a2022-11-29 17:13:01 -0500271 toggle={() => setIsVideoOn((v) => !v)}
simon1170c322022-10-31 14:51:31 -0400272 {...props}
273 />
274 );
275};
simon2d3b6532022-11-08 21:01:57 -0500276
277// Calling pending/receiving interface
simonf929a362022-11-18 16:53:45 -0500278export const CallingCancelButton = (props: IconButtonProps) => {
simonaccd8022022-11-24 15:04:53 -0500279 const { endCall } = useContext(CallContext);
simonf929a362022-11-18 16:53:45 -0500280
281 return (
282 <ColoredCallButton
283 aria-label="cancel call"
284 onClick={() => {
simonaccd8022022-11-24 15:04:53 -0500285 endCall();
simonf929a362022-11-18 16:53:45 -0500286 }}
287 Icon={CallEndIcon}
288 paletteColor={(theme) => theme.palette.error}
289 {...props}
290 />
291 );
simon2d3b6532022-11-08 21:01:57 -0500292};
293
simonf929a362022-11-18 16:53:45 -0500294export const CallingAnswerAudioButton = (props: IconButtonProps) => {
Charlie9c8779e2022-11-29 20:10:56 -0500295 const { acceptCall, callStatus } = useContext(CallContext);
simonf929a362022-11-18 16:53:45 -0500296
297 return (
298 <ColoredCallButton
Charlie9c8779e2022-11-29 20:10:56 -0500299 disabled={callStatus === CallStatus.Loading || callStatus === CallStatus.Connecting}
simonf929a362022-11-18 16:53:45 -0500300 aria-label="answer call audio"
301 onClick={() => {
MichelleSS55164202022-11-25 18:36:14 -0500302 acceptCall(false);
simonf929a362022-11-18 16:53:45 -0500303 }}
304 Icon={PlaceAudioCallIcon}
305 paletteColor={(theme) => theme.palette.success}
306 {...props}
307 />
308 );
simon2d3b6532022-11-08 21:01:57 -0500309};
310
simonf929a362022-11-18 16:53:45 -0500311export const CallingAnswerVideoButton = (props: IconButtonProps) => {
Charlie9c8779e2022-11-29 20:10:56 -0500312 const { acceptCall, callStatus } = useContext(CallContext);
simonf929a362022-11-18 16:53:45 -0500313 return (
314 <ColoredCallButton
Charlie9c8779e2022-11-29 20:10:56 -0500315 disabled={callStatus === CallStatus.Connecting || callStatus === CallStatus.Loading}
simonf929a362022-11-18 16:53:45 -0500316 aria-label="answer call video"
317 onClick={() => {
MichelleSS55164202022-11-25 18:36:14 -0500318 acceptCall(true);
simonf929a362022-11-18 16:53:45 -0500319 }}
320 paletteColor={(theme) => theme.palette.success}
321 Icon={VideoCameraIcon}
322 {...props}
323 />
324 );
325};
326
327export const CallingRefuseButton = (props: IconButtonProps) => {
simonaccd8022022-11-24 15:04:53 -0500328 const { endCall } = useContext(CallContext);
simonf929a362022-11-18 16:53:45 -0500329 return (
330 <ColoredCallButton
331 aria-label="refuse call"
332 onClick={() => {
simonaccd8022022-11-24 15:04:53 -0500333 endCall();
simonf929a362022-11-18 16:53:45 -0500334 }}
335 paletteColor={(theme) => theme.palette.error}
336 Icon={RoundCloseIcon}
337 {...props}
338 />
339 );
simon2d3b6532022-11-08 21:01:57 -0500340};