blob: 7ccdd91d32e2b7f4d7011a5e2dfd1cfe75de98bf [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) => {
Gabriel Rochon8321a0d2022-11-06 23:18:36 -050087 return <CallButton aria-label="chat" Icon={ChatBubbleIcon} {...props} />;
simon1170c322022-10-31 14:51:31 -040088};
simon33c06182022-11-02 17:39:31 -040089
90export const CallingEndButton = (props: ExpandableButtonProps) => {
simonf929a362022-11-18 16:53:45 -050091 const navigate = useNavigate();
92 return (
93 <ColoredCallButton
94 paletteColor={(theme) => theme.palette.error}
95 onClick={() => {
96 // TODO: Send event to end call
97 navigate('/');
98 }}
99 aria-label="call end"
100 Icon={CallEndIcon}
101 {...props}
102 />
103 );
simon1170c322022-10-31 14:51:31 -0400104};
simon33c06182022-11-02 17:39:31 -0400105
106export const CallingExtensionButton = (props: ExpandableButtonProps) => {
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500107 return <CallButton aria-label="extensions" Icon={ExtensionIcon} {...props} />;
simon1170c322022-10-31 14:51:31 -0400108};
simon33c06182022-11-02 17:39:31 -0400109
110export const CallingFullScreenButton = (props: ExpandableButtonProps) => {
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500111 return <CallButton aria-label="full screen" Icon={FullScreenIcon} {...props} />;
simon1170c322022-10-31 14:51:31 -0400112};
simon33c06182022-11-02 17:39:31 -0400113
114export const CallingGroupButton = (props: ExpandableButtonProps) => {
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500115 return <CallButton aria-label="group options" Icon={GroupAddIcon} {...props} />;
simon1170c322022-10-31 14:51:31 -0400116};
simon33c06182022-11-02 17:39:31 -0400117
118export const CallingMoreVerticalButton = (props: ExpandableButtonProps) => {
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500119 return <CallButton aria-label="more vertical" Icon={MoreVerticalIcon} {...props} />;
simon1170c322022-10-31 14:51:31 -0400120};
simon33c06182022-11-02 17:39:31 -0400121
122export const CallingRecordButton = (props: ExpandableButtonProps) => {
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500123 return <CallButton aria-label="recording options" Icon={RecordingIcon} {...props} />;
simon33c06182022-11-02 17:39:31 -0400124};
125
126export const CallingScreenShareButton = (props: ExpandableButtonProps) => {
simon4e7445c2022-11-16 21:18:46 -0500127 const { t } = useTranslation();
simon1170c322022-10-31 14:51:31 -0400128 return (
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500129 <CallButton
simon33c06182022-11-02 17:39:31 -0400130 aria-label="screen share"
131 Icon={ScreenShareArrowIcon}
132 expandMenuOptions={[
133 {
simon4e7445c2022-11-16 21:18:46 -0500134 description: t('share_screen'),
simon33c06182022-11-02 17:39:31 -0400135 icon: <ScreenShareRegularIcon />,
136 },
137 {
simon4e7445c2022-11-16 21:18:46 -0500138 description: t('share_window'),
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500139 icon: <WindowIcon />,
140 },
141 {
simon4e7445c2022-11-16 21:18:46 -0500142 description: t('share_screen_area'),
simon33c06182022-11-02 17:39:31 -0400143 icon: <ScreenShareScreenAreaIcon />,
144 },
145 {
simon4e7445c2022-11-16 21:18:46 -0500146 description: t('share_file'),
simon33c06182022-11-02 17:39:31 -0400147 icon: <FileIcon />,
148 },
149 ]}
150 {...props}
151 />
simon1170c322022-10-31 14:51:31 -0400152 );
153};
154
simon9a8fe202022-11-15 18:25:49 -0500155const useMediaDeviceExpandMenuOptions = (kind: MediaDeviceKind) => {
simonf929a362022-11-18 16:53:45 -0500156 const { mediaDevices } = useContext(CallContext);
simon9a8fe202022-11-15 18:25:49 -0500157
158 return useMemo(
159 () =>
160 mediaDevices[kind].map((device) => ({
161 key: device.deviceId,
162 description: device.label,
163 })),
164 [mediaDevices, kind]
165 );
166};
167
simon33c06182022-11-02 17:39:31 -0400168export const CallingVolumeButton = (props: ExpandableButtonProps) => {
simon9a8fe202022-11-15 18:25:49 -0500169 const options = useMediaDeviceExpandMenuOptions('audiooutput');
170
simon33c06182022-11-02 17:39:31 -0400171 return (
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500172 <CallButton
simon33c06182022-11-02 17:39:31 -0400173 aria-label="volume options"
174 Icon={VolumeIcon}
175 expandMenuOptions={[
176 {
simon9a8fe202022-11-15 18:25:49 -0500177 options,
simon33c06182022-11-02 17:39:31 -0400178 },
179 ]}
180 {...props}
181 />
182 );
183};
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500184
185export const CallingMicButton = (props: ExpandableButtonProps) => {
simon9a8fe202022-11-15 18:25:49 -0500186 const options = useMediaDeviceExpandMenuOptions('audioinput');
simon1170c322022-10-31 14:51:31 -0400187
188 return (
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500189 <CallButton
simon1170c322022-10-31 14:51:31 -0400190 aria-label="microphone options"
simon9a8fe202022-11-15 18:25:49 -0500191 expandMenuOptions={[
192 {
193 options,
194 },
195 ]}
simonf929a362022-11-18 16:53:45 -0500196 IconButtonComp={ToggleAudioCameraIconButton}
197 {...props}
198 />
199 );
200};
201
202const ToggleAudioCameraIconButton = (props: IconButtonProps) => {
203 const { isAudioOn, setAudioStatus } = useContext(CallContext);
204 return (
205 <ToggleIconButton
206 IconOn={MicroIcon}
207 IconOff={MicroOffIcon}
208 selected={isAudioOn}
209 toggle={() => setAudioStatus(!isAudioOn)}
simon1170c322022-10-31 14:51:31 -0400210 {...props}
211 />
212 );
213};
214
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500215export const CallingVideoCameraButton = (props: ExpandableButtonProps) => {
simon9a8fe202022-11-15 18:25:49 -0500216 const options = useMediaDeviceExpandMenuOptions('videoinput');
217
simon1170c322022-10-31 14:51:31 -0400218 return (
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500219 <CallButton
simon1170c322022-10-31 14:51:31 -0400220 aria-label="camera options"
simon9a8fe202022-11-15 18:25:49 -0500221 expandMenuOptions={[
222 {
223 options,
224 },
225 ]}
simonf929a362022-11-18 16:53:45 -0500226 IconButtonComp={ToggleVideoCameraIconButton}
227 {...props}
228 />
229 );
230};
231
232const ToggleVideoCameraIconButton = (props: IconButtonProps) => {
233 const { isVideoOn, setVideoStatus } = useContext(CallContext);
234 return (
235 <ToggleIconButton
236 IconOn={VideoCameraIcon}
237 IconOff={VideoCameraOffIcon}
238 selected={isVideoOn}
239 toggle={() => setVideoStatus(!isVideoOn)}
simon1170c322022-10-31 14:51:31 -0400240 {...props}
241 />
242 );
243};
simon2d3b6532022-11-08 21:01:57 -0500244
245// Calling pending/receiving interface
simonf929a362022-11-18 16:53:45 -0500246export const CallingCancelButton = (props: IconButtonProps) => {
247 const navigate = useNavigate();
248
249 return (
250 <ColoredCallButton
251 aria-label="cancel call"
252 onClick={() => {
253 navigate(-1);
254 }}
255 Icon={CallEndIcon}
256 paletteColor={(theme) => theme.palette.error}
257 {...props}
258 />
259 );
simon2d3b6532022-11-08 21:01:57 -0500260};
261
simonf929a362022-11-18 16:53:45 -0500262export const CallingAnswerAudioButton = (props: IconButtonProps) => {
263 const { acceptCall } = useContext(CallContext);
264
265 return (
266 <ColoredCallButton
267 aria-label="answer call audio"
268 onClick={() => {
269 acceptCall();
270 }}
271 Icon={PlaceAudioCallIcon}
272 paletteColor={(theme) => theme.palette.success}
273 {...props}
274 />
275 );
simon2d3b6532022-11-08 21:01:57 -0500276};
277
simonf929a362022-11-18 16:53:45 -0500278export const CallingAnswerVideoButton = (props: IconButtonProps) => {
279 const { acceptCall } = useContext(CallContext);
280
281 return (
282 <ColoredCallButton
283 aria-label="answer call video"
284 onClick={() => {
285 acceptCall();
286 }}
287 paletteColor={(theme) => theme.palette.success}
288 Icon={VideoCameraIcon}
289 {...props}
290 />
291 );
292};
293
294export const CallingRefuseButton = (props: IconButtonProps) => {
295 const navigate = useNavigate();
296 return (
297 <ColoredCallButton
298 aria-label="refuse call"
299 onClick={() => {
300 navigate(-1);
301 }}
302 paletteColor={(theme) => theme.palette.error}
303 Icon={RoundCloseIcon}
304 {...props}
305 />
306 );
simon2d3b6532022-11-08 21:01:57 -0500307};