blob: 404bfaeb2a54932c427505076562bfedb84fe449 [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';
simon9a8fe202022-11-15 18:25:49 -050021import React, { useContext, useMemo } from 'react';
simon4e7445c2022-11-16 21:18:46 -050022import { useTranslation } from 'react-i18next';
simonf929a362022-11-18 16:53:45 -050023import { useNavigate } from 'react-router-dom';
simon1170c322022-10-31 14:51:31 -040024
simonf929a362022-11-18 16:53:45 -050025import { CallContext } from '../contexts/CallProvider';
26import { ExpandableButton, ExpandableButtonProps, ShapedButtonProps, ToggleIconButton } from './Button';
simon1170c322022-10-31 14:51:31 -040027import {
28 CallEndIcon,
29 ChatBubbleIcon,
30 ExtensionIcon,
simon33c06182022-11-02 17:39:31 -040031 FileIcon,
32 FullScreenIcon,
simon1170c322022-10-31 14:51:31 -040033 GroupAddIcon,
34 MicroIcon,
35 MicroOffIcon,
simon33c06182022-11-02 17:39:31 -040036 MoreVerticalIcon,
simon2d3b6532022-11-08 21:01:57 -050037 PlaceAudioCallIcon,
simon1170c322022-10-31 14:51:31 -040038 RecordingIcon,
simon2d3b6532022-11-08 21:01:57 -050039 RoundCloseIcon,
simon33c06182022-11-02 17:39:31 -040040 ScreenShareArrowIcon,
41 ScreenShareRegularIcon,
42 ScreenShareScreenAreaIcon,
simon1170c322022-10-31 14:51:31 -040043 VideoCameraIcon,
44 VideoCameraOffIcon,
45 VolumeIcon,
Gabriel Rochon8321a0d2022-11-06 23:18:36 -050046 WindowIcon,
simon1170c322022-10-31 14:51:31 -040047} from './SvgIcon';
48
Gabriel Rochon8321a0d2022-11-06 23:18:36 -050049const CallButton = styled((props: ExpandableButtonProps) => {
50 return <ExpandableButton {...props} />;
51})({
simon2d3b6532022-11-08 21:01:57 -050052 color: 'white',
Gabriel Rochon8321a0d2022-11-06 23:18:36 -050053 '&:hover': {
54 backgroundColor: 'rgba(255, 255, 255, 0.15)',
55 },
Gabriel Rochon8321a0d2022-11-06 23:18:36 -050056});
57
simon2d3b6532022-11-08 21:01:57 -050058const ColoredCallButton = styled(
59 ({
simonf929a362022-11-18 16:53:45 -050060 paletteColor,
61 Icon,
simon2d3b6532022-11-08 21:01:57 -050062 ...props
simonf929a362022-11-18 16:53:45 -050063 }: ShapedButtonProps & {
64 paletteColor?: PaletteColor | ((theme: Theme) => PaletteColor);
simon2d3b6532022-11-08 21:01:57 -050065 }) => {
simonf929a362022-11-18 16:53:45 -050066 return (
67 <IconButton {...props} disableRipple={true}>
68 <Icon fontSize="inherit" />
69 </IconButton>
70 );
simon2d3b6532022-11-08 21:01:57 -050071 }
simonf929a362022-11-18 16:53:45 -050072)(({ theme, paletteColor = theme.palette.primary }) => {
73 if (typeof paletteColor === 'function') {
74 paletteColor = paletteColor(theme);
75 }
76
simon2d3b6532022-11-08 21:01:57 -050077 return {
simonf929a362022-11-18 16:53:45 -050078 color: paletteColor.contrastText,
79 backgroundColor: paletteColor.dark,
simon2d3b6532022-11-08 21:01:57 -050080 '&:hover': {
simonf929a362022-11-18 16:53:45 -050081 backgroundColor: paletteColor.main,
simon2d3b6532022-11-08 21:01:57 -050082 },
83 };
Gabriel Rochon8321a0d2022-11-06 23:18:36 -050084});
85
simon33c06182022-11-02 17:39:31 -040086export const CallingChatButton = (props: ExpandableButtonProps) => {
simonf9d78f22022-11-25 15:47:15 -050087 const { setIsChatShown } = useContext(CallContext);
88 return (
89 <CallButton
90 aria-label="chat"
91 Icon={ChatBubbleIcon}
92 onClick={() => {
93 setIsChatShown((v) => !v);
94 }}
95 {...props}
96 />
97 );
simon1170c322022-10-31 14:51:31 -040098};
simon33c06182022-11-02 17:39:31 -040099
100export const CallingEndButton = (props: ExpandableButtonProps) => {
simonf929a362022-11-18 16:53:45 -0500101 const navigate = useNavigate();
102 return (
103 <ColoredCallButton
104 paletteColor={(theme) => theme.palette.error}
105 onClick={() => {
106 // TODO: Send event to end call
107 navigate('/');
108 }}
109 aria-label="call end"
110 Icon={CallEndIcon}
111 {...props}
112 />
113 );
simon1170c322022-10-31 14:51:31 -0400114};
simon33c06182022-11-02 17:39:31 -0400115
116export const CallingExtensionButton = (props: ExpandableButtonProps) => {
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500117 return <CallButton aria-label="extensions" Icon={ExtensionIcon} {...props} />;
simon1170c322022-10-31 14:51:31 -0400118};
simon33c06182022-11-02 17:39:31 -0400119
120export const CallingFullScreenButton = (props: ExpandableButtonProps) => {
simon2a5cf142022-11-25 15:47:35 -0500121 const { setIsFullscreen } = useContext(CallContext);
122 return (
123 <CallButton
124 aria-label="full screen"
125 Icon={FullScreenIcon}
126 onClick={() => {
127 setIsFullscreen((v) => !v);
128 }}
129 {...props}
130 />
131 );
simon1170c322022-10-31 14:51:31 -0400132};
simon33c06182022-11-02 17:39:31 -0400133
134export const CallingGroupButton = (props: ExpandableButtonProps) => {
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500135 return <CallButton aria-label="group options" Icon={GroupAddIcon} {...props} />;
simon1170c322022-10-31 14:51:31 -0400136};
simon33c06182022-11-02 17:39:31 -0400137
138export const CallingMoreVerticalButton = (props: ExpandableButtonProps) => {
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500139 return <CallButton aria-label="more vertical" Icon={MoreVerticalIcon} {...props} />;
simon1170c322022-10-31 14:51:31 -0400140};
simon33c06182022-11-02 17:39:31 -0400141
142export const CallingRecordButton = (props: ExpandableButtonProps) => {
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500143 return <CallButton aria-label="recording options" Icon={RecordingIcon} {...props} />;
simon33c06182022-11-02 17:39:31 -0400144};
145
146export const CallingScreenShareButton = (props: ExpandableButtonProps) => {
simon4e7445c2022-11-16 21:18:46 -0500147 const { t } = useTranslation();
simon1170c322022-10-31 14:51:31 -0400148 return (
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500149 <CallButton
simon33c06182022-11-02 17:39:31 -0400150 aria-label="screen share"
151 Icon={ScreenShareArrowIcon}
152 expandMenuOptions={[
153 {
simon4e7445c2022-11-16 21:18:46 -0500154 description: t('share_screen'),
simon33c06182022-11-02 17:39:31 -0400155 icon: <ScreenShareRegularIcon />,
156 },
157 {
simon4e7445c2022-11-16 21:18:46 -0500158 description: t('share_window'),
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500159 icon: <WindowIcon />,
160 },
161 {
simon4e7445c2022-11-16 21:18:46 -0500162 description: t('share_screen_area'),
simon33c06182022-11-02 17:39:31 -0400163 icon: <ScreenShareScreenAreaIcon />,
164 },
165 {
simon4e7445c2022-11-16 21:18:46 -0500166 description: t('share_file'),
simon33c06182022-11-02 17:39:31 -0400167 icon: <FileIcon />,
168 },
169 ]}
170 {...props}
171 />
simon1170c322022-10-31 14:51:31 -0400172 );
173};
174
simon9a8fe202022-11-15 18:25:49 -0500175const useMediaDeviceExpandMenuOptions = (kind: MediaDeviceKind) => {
simonf929a362022-11-18 16:53:45 -0500176 const { mediaDevices } = useContext(CallContext);
simon9a8fe202022-11-15 18:25:49 -0500177
178 return useMemo(
179 () =>
180 mediaDevices[kind].map((device) => ({
181 key: device.deviceId,
182 description: device.label,
183 })),
184 [mediaDevices, kind]
185 );
186};
187
simon33c06182022-11-02 17:39:31 -0400188export const CallingVolumeButton = (props: ExpandableButtonProps) => {
simon9a8fe202022-11-15 18:25:49 -0500189 const options = useMediaDeviceExpandMenuOptions('audiooutput');
190
simon33c06182022-11-02 17:39:31 -0400191 return (
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500192 <CallButton
simon33c06182022-11-02 17:39:31 -0400193 aria-label="volume options"
194 Icon={VolumeIcon}
195 expandMenuOptions={[
196 {
simon9a8fe202022-11-15 18:25:49 -0500197 options,
simon33c06182022-11-02 17:39:31 -0400198 },
199 ]}
200 {...props}
201 />
202 );
203};
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500204
205export const CallingMicButton = (props: ExpandableButtonProps) => {
simon9a8fe202022-11-15 18:25:49 -0500206 const options = useMediaDeviceExpandMenuOptions('audioinput');
simon1170c322022-10-31 14:51:31 -0400207
208 return (
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500209 <CallButton
simon1170c322022-10-31 14:51:31 -0400210 aria-label="microphone options"
simon9a8fe202022-11-15 18:25:49 -0500211 expandMenuOptions={[
212 {
213 options,
214 },
215 ]}
simonf929a362022-11-18 16:53:45 -0500216 IconButtonComp={ToggleAudioCameraIconButton}
217 {...props}
218 />
219 );
220};
221
222const ToggleAudioCameraIconButton = (props: IconButtonProps) => {
223 const { isAudioOn, setAudioStatus } = useContext(CallContext);
224 return (
225 <ToggleIconButton
226 IconOn={MicroIcon}
227 IconOff={MicroOffIcon}
228 selected={isAudioOn}
229 toggle={() => setAudioStatus(!isAudioOn)}
simon1170c322022-10-31 14:51:31 -0400230 {...props}
231 />
232 );
233};
234
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500235export const CallingVideoCameraButton = (props: ExpandableButtonProps) => {
simon9a8fe202022-11-15 18:25:49 -0500236 const options = useMediaDeviceExpandMenuOptions('videoinput');
237
simon1170c322022-10-31 14:51:31 -0400238 return (
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500239 <CallButton
simon1170c322022-10-31 14:51:31 -0400240 aria-label="camera options"
simon9a8fe202022-11-15 18:25:49 -0500241 expandMenuOptions={[
242 {
243 options,
244 },
245 ]}
simonf929a362022-11-18 16:53:45 -0500246 IconButtonComp={ToggleVideoCameraIconButton}
247 {...props}
248 />
249 );
250};
251
252const ToggleVideoCameraIconButton = (props: IconButtonProps) => {
253 const { isVideoOn, setVideoStatus } = useContext(CallContext);
254 return (
255 <ToggleIconButton
256 IconOn={VideoCameraIcon}
257 IconOff={VideoCameraOffIcon}
258 selected={isVideoOn}
259 toggle={() => setVideoStatus(!isVideoOn)}
simon1170c322022-10-31 14:51:31 -0400260 {...props}
261 />
262 );
263};
simon2d3b6532022-11-08 21:01:57 -0500264
265// Calling pending/receiving interface
simonf929a362022-11-18 16:53:45 -0500266export const CallingCancelButton = (props: IconButtonProps) => {
267 const navigate = useNavigate();
268
269 return (
270 <ColoredCallButton
271 aria-label="cancel call"
272 onClick={() => {
273 navigate(-1);
274 }}
275 Icon={CallEndIcon}
276 paletteColor={(theme) => theme.palette.error}
277 {...props}
278 />
279 );
simon2d3b6532022-11-08 21:01:57 -0500280};
281
simonf929a362022-11-18 16:53:45 -0500282export const CallingAnswerAudioButton = (props: IconButtonProps) => {
283 const { acceptCall } = useContext(CallContext);
284
285 return (
286 <ColoredCallButton
287 aria-label="answer call audio"
288 onClick={() => {
289 acceptCall();
290 }}
291 Icon={PlaceAudioCallIcon}
292 paletteColor={(theme) => theme.palette.success}
293 {...props}
294 />
295 );
simon2d3b6532022-11-08 21:01:57 -0500296};
297
simonf929a362022-11-18 16:53:45 -0500298export const CallingAnswerVideoButton = (props: IconButtonProps) => {
299 const { acceptCall } = useContext(CallContext);
300
301 return (
302 <ColoredCallButton
303 aria-label="answer call video"
304 onClick={() => {
305 acceptCall();
306 }}
307 paletteColor={(theme) => theme.palette.success}
308 Icon={VideoCameraIcon}
309 {...props}
310 />
311 );
312};
313
314export const CallingRefuseButton = (props: IconButtonProps) => {
315 const navigate = useNavigate();
316 return (
317 <ColoredCallButton
318 aria-label="refuse call"
319 onClick={() => {
320 navigate(-1);
321 }}
322 paletteColor={(theme) => theme.palette.error}
323 Icon={RoundCloseIcon}
324 {...props}
325 />
326 );
simon2d3b6532022-11-08 21:01:57 -0500327};