blob: b127a77f218e50624e477d102ff3f7587859dfff [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) => {
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500121 return <CallButton aria-label="full screen" Icon={FullScreenIcon} {...props} />;
simon1170c322022-10-31 14:51:31 -0400122};
simon33c06182022-11-02 17:39:31 -0400123
124export const CallingGroupButton = (props: ExpandableButtonProps) => {
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500125 return <CallButton aria-label="group options" Icon={GroupAddIcon} {...props} />;
simon1170c322022-10-31 14:51:31 -0400126};
simon33c06182022-11-02 17:39:31 -0400127
128export const CallingMoreVerticalButton = (props: ExpandableButtonProps) => {
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500129 return <CallButton aria-label="more vertical" Icon={MoreVerticalIcon} {...props} />;
simon1170c322022-10-31 14:51:31 -0400130};
simon33c06182022-11-02 17:39:31 -0400131
132export const CallingRecordButton = (props: ExpandableButtonProps) => {
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500133 return <CallButton aria-label="recording options" Icon={RecordingIcon} {...props} />;
simon33c06182022-11-02 17:39:31 -0400134};
135
136export const CallingScreenShareButton = (props: ExpandableButtonProps) => {
simon4e7445c2022-11-16 21:18:46 -0500137 const { t } = useTranslation();
simon1170c322022-10-31 14:51:31 -0400138 return (
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500139 <CallButton
simon33c06182022-11-02 17:39:31 -0400140 aria-label="screen share"
141 Icon={ScreenShareArrowIcon}
142 expandMenuOptions={[
143 {
simon4e7445c2022-11-16 21:18:46 -0500144 description: t('share_screen'),
simon33c06182022-11-02 17:39:31 -0400145 icon: <ScreenShareRegularIcon />,
146 },
147 {
simon4e7445c2022-11-16 21:18:46 -0500148 description: t('share_window'),
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500149 icon: <WindowIcon />,
150 },
151 {
simon4e7445c2022-11-16 21:18:46 -0500152 description: t('share_screen_area'),
simon33c06182022-11-02 17:39:31 -0400153 icon: <ScreenShareScreenAreaIcon />,
154 },
155 {
simon4e7445c2022-11-16 21:18:46 -0500156 description: t('share_file'),
simon33c06182022-11-02 17:39:31 -0400157 icon: <FileIcon />,
158 },
159 ]}
160 {...props}
161 />
simon1170c322022-10-31 14:51:31 -0400162 );
163};
164
simon9a8fe202022-11-15 18:25:49 -0500165const useMediaDeviceExpandMenuOptions = (kind: MediaDeviceKind) => {
simonf929a362022-11-18 16:53:45 -0500166 const { mediaDevices } = useContext(CallContext);
simon9a8fe202022-11-15 18:25:49 -0500167
168 return useMemo(
169 () =>
170 mediaDevices[kind].map((device) => ({
171 key: device.deviceId,
172 description: device.label,
173 })),
174 [mediaDevices, kind]
175 );
176};
177
simon33c06182022-11-02 17:39:31 -0400178export const CallingVolumeButton = (props: ExpandableButtonProps) => {
simon9a8fe202022-11-15 18:25:49 -0500179 const options = useMediaDeviceExpandMenuOptions('audiooutput');
180
simon33c06182022-11-02 17:39:31 -0400181 return (
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500182 <CallButton
simon33c06182022-11-02 17:39:31 -0400183 aria-label="volume options"
184 Icon={VolumeIcon}
185 expandMenuOptions={[
186 {
simon9a8fe202022-11-15 18:25:49 -0500187 options,
simon33c06182022-11-02 17:39:31 -0400188 },
189 ]}
190 {...props}
191 />
192 );
193};
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500194
195export const CallingMicButton = (props: ExpandableButtonProps) => {
simon9a8fe202022-11-15 18:25:49 -0500196 const options = useMediaDeviceExpandMenuOptions('audioinput');
simon1170c322022-10-31 14:51:31 -0400197
198 return (
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500199 <CallButton
simon1170c322022-10-31 14:51:31 -0400200 aria-label="microphone options"
simon9a8fe202022-11-15 18:25:49 -0500201 expandMenuOptions={[
202 {
203 options,
204 },
205 ]}
simonf929a362022-11-18 16:53:45 -0500206 IconButtonComp={ToggleAudioCameraIconButton}
207 {...props}
208 />
209 );
210};
211
212const ToggleAudioCameraIconButton = (props: IconButtonProps) => {
213 const { isAudioOn, setAudioStatus } = useContext(CallContext);
214 return (
215 <ToggleIconButton
216 IconOn={MicroIcon}
217 IconOff={MicroOffIcon}
218 selected={isAudioOn}
219 toggle={() => setAudioStatus(!isAudioOn)}
simon1170c322022-10-31 14:51:31 -0400220 {...props}
221 />
222 );
223};
224
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500225export const CallingVideoCameraButton = (props: ExpandableButtonProps) => {
simon9a8fe202022-11-15 18:25:49 -0500226 const options = useMediaDeviceExpandMenuOptions('videoinput');
227
simon1170c322022-10-31 14:51:31 -0400228 return (
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500229 <CallButton
simon1170c322022-10-31 14:51:31 -0400230 aria-label="camera options"
simon9a8fe202022-11-15 18:25:49 -0500231 expandMenuOptions={[
232 {
233 options,
234 },
235 ]}
simonf929a362022-11-18 16:53:45 -0500236 IconButtonComp={ToggleVideoCameraIconButton}
237 {...props}
238 />
239 );
240};
241
242const ToggleVideoCameraIconButton = (props: IconButtonProps) => {
243 const { isVideoOn, setVideoStatus } = useContext(CallContext);
244 return (
245 <ToggleIconButton
246 IconOn={VideoCameraIcon}
247 IconOff={VideoCameraOffIcon}
248 selected={isVideoOn}
249 toggle={() => setVideoStatus(!isVideoOn)}
simon1170c322022-10-31 14:51:31 -0400250 {...props}
251 />
252 );
253};
simon2d3b6532022-11-08 21:01:57 -0500254
255// Calling pending/receiving interface
simonf929a362022-11-18 16:53:45 -0500256export const CallingCancelButton = (props: IconButtonProps) => {
257 const navigate = useNavigate();
258
259 return (
260 <ColoredCallButton
261 aria-label="cancel call"
262 onClick={() => {
263 navigate(-1);
264 }}
265 Icon={CallEndIcon}
266 paletteColor={(theme) => theme.palette.error}
267 {...props}
268 />
269 );
simon2d3b6532022-11-08 21:01:57 -0500270};
271
simonf929a362022-11-18 16:53:45 -0500272export const CallingAnswerAudioButton = (props: IconButtonProps) => {
273 const { acceptCall } = useContext(CallContext);
274
275 return (
276 <ColoredCallButton
277 aria-label="answer call audio"
278 onClick={() => {
279 acceptCall();
280 }}
281 Icon={PlaceAudioCallIcon}
282 paletteColor={(theme) => theme.palette.success}
283 {...props}
284 />
285 );
simon2d3b6532022-11-08 21:01:57 -0500286};
287
simonf929a362022-11-18 16:53:45 -0500288export const CallingAnswerVideoButton = (props: IconButtonProps) => {
289 const { acceptCall } = useContext(CallContext);
290
291 return (
292 <ColoredCallButton
293 aria-label="answer call video"
294 onClick={() => {
295 acceptCall();
296 }}
297 paletteColor={(theme) => theme.palette.success}
298 Icon={VideoCameraIcon}
299 {...props}
300 />
301 );
302};
303
304export const CallingRefuseButton = (props: IconButtonProps) => {
305 const navigate = useNavigate();
306 return (
307 <ColoredCallButton
308 aria-label="refuse call"
309 onClick={() => {
310 navigate(-1);
311 }}
312 paletteColor={(theme) => theme.palette.error}
313 Icon={RoundCloseIcon}
314 {...props}
315 />
316 );
simon2d3b6532022-11-08 21:01:57 -0500317};