blob: d1beab879f57f1eb446d3a888086b125929349da [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 },
88 };
Gabriel Rochon8321a0d2022-11-06 23:18:36 -050089});
90
simon33c06182022-11-02 17:39:31 -040091export const CallingChatButton = (props: ExpandableButtonProps) => {
simonf9d78f22022-11-25 15:47:15 -050092 const { setIsChatShown } = useContext(CallContext);
93 return (
94 <CallButton
95 aria-label="chat"
96 Icon={ChatBubbleIcon}
97 onClick={() => {
98 setIsChatShown((v) => !v);
99 }}
100 {...props}
101 />
102 );
simon1170c322022-10-31 14:51:31 -0400103};
simon33c06182022-11-02 17:39:31 -0400104
105export const CallingEndButton = (props: ExpandableButtonProps) => {
simonaccd8022022-11-24 15:04:53 -0500106 const { endCall } = useContext(CallContext);
simonf929a362022-11-18 16:53:45 -0500107 return (
108 <ColoredCallButton
109 paletteColor={(theme) => theme.palette.error}
110 onClick={() => {
simonaccd8022022-11-24 15:04:53 -0500111 endCall();
simonf929a362022-11-18 16:53:45 -0500112 }}
113 aria-label="call end"
114 Icon={CallEndIcon}
115 {...props}
116 />
117 );
simon1170c322022-10-31 14:51:31 -0400118};
simon33c06182022-11-02 17:39:31 -0400119
120export const CallingExtensionButton = (props: ExpandableButtonProps) => {
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500121 return <CallButton aria-label="extensions" Icon={ExtensionIcon} {...props} />;
simon1170c322022-10-31 14:51:31 -0400122};
simon33c06182022-11-02 17:39:31 -0400123
124export const CallingFullScreenButton = (props: ExpandableButtonProps) => {
simon2a5cf142022-11-25 15:47:35 -0500125 const { setIsFullscreen } = useContext(CallContext);
126 return (
127 <CallButton
128 aria-label="full screen"
129 Icon={FullScreenIcon}
130 onClick={() => {
131 setIsFullscreen((v) => !v);
132 }}
133 {...props}
134 />
135 );
simon1170c322022-10-31 14:51:31 -0400136};
simon33c06182022-11-02 17:39:31 -0400137
138export const CallingGroupButton = (props: ExpandableButtonProps) => {
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500139 return <CallButton aria-label="group options" Icon={GroupAddIcon} {...props} />;
simon1170c322022-10-31 14:51:31 -0400140};
simon33c06182022-11-02 17:39:31 -0400141
142export const CallingMoreVerticalButton = (props: ExpandableButtonProps) => {
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500143 return <CallButton aria-label="more vertical" Icon={MoreVerticalIcon} {...props} />;
simon1170c322022-10-31 14:51:31 -0400144};
simon33c06182022-11-02 17:39:31 -0400145
146export const CallingRecordButton = (props: ExpandableButtonProps) => {
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500147 return <CallButton aria-label="recording options" Icon={RecordingIcon} {...props} />;
simon33c06182022-11-02 17:39:31 -0400148};
149
150export const CallingScreenShareButton = (props: ExpandableButtonProps) => {
simon4e7445c2022-11-16 21:18:46 -0500151 const { t } = useTranslation();
simon1170c322022-10-31 14:51:31 -0400152 return (
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500153 <CallButton
simon33c06182022-11-02 17:39:31 -0400154 aria-label="screen share"
simon571240f2022-11-29 23:59:27 -0500155 expandMenuOnClick
simon33c06182022-11-02 17:39:31 -0400156 Icon={ScreenShareArrowIcon}
157 expandMenuOptions={[
158 {
simon4e7445c2022-11-16 21:18:46 -0500159 description: t('share_screen'),
simon33c06182022-11-02 17:39:31 -0400160 icon: <ScreenShareRegularIcon />,
161 },
162 {
simon4e7445c2022-11-16 21:18:46 -0500163 description: t('share_window'),
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500164 icon: <WindowIcon />,
165 },
166 {
simon4e7445c2022-11-16 21:18:46 -0500167 description: t('share_screen_area'),
simon33c06182022-11-02 17:39:31 -0400168 icon: <ScreenShareScreenAreaIcon />,
169 },
170 {
simon4e7445c2022-11-16 21:18:46 -0500171 description: t('share_file'),
simon33c06182022-11-02 17:39:31 -0400172 icon: <FileIcon />,
173 },
174 ]}
175 {...props}
176 />
simon1170c322022-10-31 14:51:31 -0400177 );
178};
179
simon9076a9a2022-11-29 17:13:01 -0500180const useMediaDeviceExpandMenuOptions = (kind: MediaDeviceKind): ExpandMenuRadioOption[] | undefined => {
simon492e8402022-11-29 16:48:37 -0500181 const { currentMediaDeviceIds, mediaDevices } = useContext(CallContext);
simon9a8fe202022-11-15 18:25:49 -0500182
simon9076a9a2022-11-29 17:13:01 -0500183 const options = useMemo(
simon9a8fe202022-11-15 18:25:49 -0500184 () =>
185 mediaDevices[kind].map((device) => ({
186 key: device.deviceId,
187 description: device.label,
188 })),
189 [mediaDevices, kind]
190 );
simon9076a9a2022-11-29 17:13:01 -0500191
simon492e8402022-11-29 16:48:37 -0500192 const currentDevice = currentMediaDeviceIds[kind];
193
194 if (options.length === 0) {
195 return undefined;
196 }
197 return [
198 {
199 options,
200 value: currentDevice.id ?? '',
201 onChange: (e: ChangeEvent<HTMLInputElement>) => {
202 currentDevice.setId(e.target.value);
203 },
204 },
205 ];
simon9a8fe202022-11-15 18:25:49 -0500206};
207
simon33c06182022-11-02 17:39:31 -0400208export const CallingVolumeButton = (props: ExpandableButtonProps) => {
simon9a8fe202022-11-15 18:25:49 -0500209 const options = useMediaDeviceExpandMenuOptions('audiooutput');
simon492e8402022-11-29 16:48:37 -0500210
211 return (
212 <CallButton
213 aria-label="volume options"
simon571240f2022-11-29 23:59:27 -0500214 expandMenuOnClick
simon492e8402022-11-29 16:48:37 -0500215 Icon={VolumeIcon}
simon571240f2022-11-29 23:59:27 -0500216 expandMenuOptions={options}
simon492e8402022-11-29 16:48:37 -0500217 {...props}
218 />
219 );
simon33c06182022-11-02 17:39:31 -0400220};
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500221
222export const CallingMicButton = (props: ExpandableButtonProps) => {
simon9a8fe202022-11-15 18:25:49 -0500223 const options = useMediaDeviceExpandMenuOptions('audioinput');
simon1170c322022-10-31 14:51:31 -0400224
225 return (
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500226 <CallButton
simon1170c322022-10-31 14:51:31 -0400227 aria-label="microphone options"
simon9076a9a2022-11-29 17:13:01 -0500228 expandMenuOptions={options}
simonf929a362022-11-18 16:53:45 -0500229 IconButtonComp={ToggleAudioCameraIconButton}
230 {...props}
231 />
232 );
233};
234
235const ToggleAudioCameraIconButton = (props: IconButtonProps) => {
simon9076a9a2022-11-29 17:13:01 -0500236 const { isAudioOn, setIsAudioOn } = useContext(CallContext);
simonf929a362022-11-18 16:53:45 -0500237 return (
238 <ToggleIconButton
239 IconOn={MicroIcon}
240 IconOff={MicroOffIcon}
241 selected={isAudioOn}
simon9076a9a2022-11-29 17:13:01 -0500242 toggle={() => setIsAudioOn((v) => !v)}
simon1170c322022-10-31 14:51:31 -0400243 {...props}
244 />
245 );
246};
247
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500248export const CallingVideoCameraButton = (props: ExpandableButtonProps) => {
simon9a8fe202022-11-15 18:25:49 -0500249 const options = useMediaDeviceExpandMenuOptions('videoinput');
250
simon1170c322022-10-31 14:51:31 -0400251 return (
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500252 <CallButton
simon1170c322022-10-31 14:51:31 -0400253 aria-label="camera options"
simon9076a9a2022-11-29 17:13:01 -0500254 expandMenuOptions={options}
simonf929a362022-11-18 16:53:45 -0500255 IconButtonComp={ToggleVideoCameraIconButton}
256 {...props}
257 />
258 );
259};
260
261const ToggleVideoCameraIconButton = (props: IconButtonProps) => {
simon9076a9a2022-11-29 17:13:01 -0500262 const { isVideoOn, setIsVideoOn } = useContext(CallContext);
simonf929a362022-11-18 16:53:45 -0500263 return (
264 <ToggleIconButton
265 IconOn={VideoCameraIcon}
266 IconOff={VideoCameraOffIcon}
267 selected={isVideoOn}
simon9076a9a2022-11-29 17:13:01 -0500268 toggle={() => setIsVideoOn((v) => !v)}
simon1170c322022-10-31 14:51:31 -0400269 {...props}
270 />
271 );
272};
simon2d3b6532022-11-08 21:01:57 -0500273
274// Calling pending/receiving interface
simonf929a362022-11-18 16:53:45 -0500275export const CallingCancelButton = (props: IconButtonProps) => {
simonaccd8022022-11-24 15:04:53 -0500276 const { endCall } = useContext(CallContext);
simonf929a362022-11-18 16:53:45 -0500277
278 return (
279 <ColoredCallButton
280 aria-label="cancel call"
281 onClick={() => {
simonaccd8022022-11-24 15:04:53 -0500282 endCall();
simonf929a362022-11-18 16:53:45 -0500283 }}
284 Icon={CallEndIcon}
285 paletteColor={(theme) => theme.palette.error}
286 {...props}
287 />
288 );
simon2d3b6532022-11-08 21:01:57 -0500289};
290
simonf929a362022-11-18 16:53:45 -0500291export const CallingAnswerAudioButton = (props: IconButtonProps) => {
Charlie9c8779e2022-11-29 20:10:56 -0500292 const { acceptCall, callStatus } = useContext(CallContext);
simonf929a362022-11-18 16:53:45 -0500293
294 return (
295 <ColoredCallButton
Charlie9c8779e2022-11-29 20:10:56 -0500296 disabled={callStatus === CallStatus.Loading || callStatus === CallStatus.Connecting}
simonf929a362022-11-18 16:53:45 -0500297 aria-label="answer call audio"
298 onClick={() => {
MichelleSS55164202022-11-25 18:36:14 -0500299 acceptCall(false);
simonf929a362022-11-18 16:53:45 -0500300 }}
301 Icon={PlaceAudioCallIcon}
302 paletteColor={(theme) => theme.palette.success}
303 {...props}
304 />
305 );
simon2d3b6532022-11-08 21:01:57 -0500306};
307
simonf929a362022-11-18 16:53:45 -0500308export const CallingAnswerVideoButton = (props: IconButtonProps) => {
Charlie9c8779e2022-11-29 20:10:56 -0500309 const { acceptCall, callStatus } = useContext(CallContext);
simonf929a362022-11-18 16:53:45 -0500310 return (
311 <ColoredCallButton
Charlie9c8779e2022-11-29 20:10:56 -0500312 disabled={callStatus === CallStatus.Connecting || callStatus === CallStatus.Loading}
simonf929a362022-11-18 16:53:45 -0500313 aria-label="answer call video"
314 onClick={() => {
MichelleSS55164202022-11-25 18:36:14 -0500315 acceptCall(true);
simonf929a362022-11-18 16:53:45 -0500316 }}
317 paletteColor={(theme) => theme.palette.success}
318 Icon={VideoCameraIcon}
319 {...props}
320 />
321 );
322};
323
324export const CallingRefuseButton = (props: IconButtonProps) => {
simonaccd8022022-11-24 15:04:53 -0500325 const { endCall } = useContext(CallContext);
simonf929a362022-11-18 16:53:45 -0500326 return (
327 <ColoredCallButton
328 aria-label="refuse call"
329 onClick={() => {
simonaccd8022022-11-24 15:04:53 -0500330 endCall();
simonf929a362022-11-18 16:53:45 -0500331 }}
332 paletteColor={(theme) => theme.palette.error}
333 Icon={RoundCloseIcon}
334 {...props}
335 />
336 );
simon2d3b6532022-11-08 21:01:57 -0500337};