blob: 6f53b738e35637a64ea4f9f8e5ae874b2fe2c1fd [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';
simonaccd8022022-11-24 15:04:53 -050021import { 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 { WebRtcContext } from '../contexts/WebRtcProvider';
26import {
27 ExpandableButton,
28 ExpandableButtonProps,
29 ExpandMenuRadioOption,
30 ShapedButtonProps,
31 ToggleIconButton,
32} from './Button';
simon1170c322022-10-31 14:51:31 -040033import {
34 CallEndIcon,
35 ChatBubbleIcon,
36 ExtensionIcon,
simon33c06182022-11-02 17:39:31 -040037 FileIcon,
38 FullScreenIcon,
simon1170c322022-10-31 14:51:31 -040039 GroupAddIcon,
40 MicroIcon,
41 MicroOffIcon,
simon33c06182022-11-02 17:39:31 -040042 MoreVerticalIcon,
simon2d3b6532022-11-08 21:01:57 -050043 PlaceAudioCallIcon,
simon1170c322022-10-31 14:51:31 -040044 RecordingIcon,
simon2d3b6532022-11-08 21:01:57 -050045 RoundCloseIcon,
simon33c06182022-11-02 17:39:31 -040046 ScreenShareArrowIcon,
47 ScreenShareRegularIcon,
48 ScreenShareScreenAreaIcon,
simon1170c322022-10-31 14:51:31 -040049 VideoCameraIcon,
50 VideoCameraOffIcon,
51 VolumeIcon,
Gabriel Rochon8321a0d2022-11-06 23:18:36 -050052 WindowIcon,
simon1170c322022-10-31 14:51:31 -040053} from './SvgIcon';
54
Gabriel Rochon8321a0d2022-11-06 23:18:36 -050055const CallButton = styled((props: ExpandableButtonProps) => {
56 return <ExpandableButton {...props} />;
57})({
simon2d3b6532022-11-08 21:01:57 -050058 color: 'white',
Gabriel Rochon8321a0d2022-11-06 23:18:36 -050059 '&:hover': {
60 backgroundColor: 'rgba(255, 255, 255, 0.15)',
61 },
Gabriel Rochon8321a0d2022-11-06 23:18:36 -050062});
63
simon2d3b6532022-11-08 21:01:57 -050064const ColoredCallButton = styled(
65 ({
simonf929a362022-11-18 16:53:45 -050066 paletteColor,
67 Icon,
simon2d3b6532022-11-08 21:01:57 -050068 ...props
simonf929a362022-11-18 16:53:45 -050069 }: ShapedButtonProps & {
70 paletteColor?: PaletteColor | ((theme: Theme) => PaletteColor);
simon2d3b6532022-11-08 21:01:57 -050071 }) => {
simonf929a362022-11-18 16:53:45 -050072 return (
73 <IconButton {...props} disableRipple={true}>
74 <Icon fontSize="inherit" />
75 </IconButton>
76 );
simon2d3b6532022-11-08 21:01:57 -050077 }
simonf929a362022-11-18 16:53:45 -050078)(({ theme, paletteColor = theme.palette.primary }) => {
79 if (typeof paletteColor === 'function') {
80 paletteColor = paletteColor(theme);
81 }
82
simon2d3b6532022-11-08 21:01:57 -050083 return {
simonf929a362022-11-18 16:53:45 -050084 color: paletteColor.contrastText,
85 backgroundColor: paletteColor.dark,
simon2d3b6532022-11-08 21:01:57 -050086 '&:hover': {
simonf929a362022-11-18 16:53:45 -050087 backgroundColor: paletteColor.main,
simon2d3b6532022-11-08 21:01:57 -050088 },
89 };
Gabriel Rochon8321a0d2022-11-06 23:18:36 -050090});
91
simon33c06182022-11-02 17:39:31 -040092export const CallingChatButton = (props: ExpandableButtonProps) => {
simonf9d78f22022-11-25 15:47:15 -050093 const { setIsChatShown } = useContext(CallContext);
94 return (
95 <CallButton
96 aria-label="chat"
97 Icon={ChatBubbleIcon}
98 onClick={() => {
99 setIsChatShown((v) => !v);
100 }}
101 {...props}
102 />
103 );
simon1170c322022-10-31 14:51:31 -0400104};
simon33c06182022-11-02 17:39:31 -0400105
106export const CallingEndButton = (props: ExpandableButtonProps) => {
simonaccd8022022-11-24 15:04:53 -0500107 const { endCall } = useContext(CallContext);
simonf929a362022-11-18 16:53:45 -0500108 return (
109 <ColoredCallButton
110 paletteColor={(theme) => theme.palette.error}
111 onClick={() => {
simonaccd8022022-11-24 15:04:53 -0500112 endCall();
simonf929a362022-11-18 16:53:45 -0500113 }}
114 aria-label="call end"
115 Icon={CallEndIcon}
116 {...props}
117 />
118 );
simon1170c322022-10-31 14:51:31 -0400119};
simon33c06182022-11-02 17:39:31 -0400120
121export const CallingExtensionButton = (props: ExpandableButtonProps) => {
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500122 return <CallButton aria-label="extensions" Icon={ExtensionIcon} {...props} />;
simon1170c322022-10-31 14:51:31 -0400123};
simon33c06182022-11-02 17:39:31 -0400124
125export const CallingFullScreenButton = (props: ExpandableButtonProps) => {
simon2a5cf142022-11-25 15:47:35 -0500126 const { setIsFullscreen } = useContext(CallContext);
127 return (
128 <CallButton
129 aria-label="full screen"
130 Icon={FullScreenIcon}
131 onClick={() => {
132 setIsFullscreen((v) => !v);
133 }}
134 {...props}
135 />
136 );
simon1170c322022-10-31 14:51:31 -0400137};
simon33c06182022-11-02 17:39:31 -0400138
139export const CallingGroupButton = (props: ExpandableButtonProps) => {
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500140 return <CallButton aria-label="group options" Icon={GroupAddIcon} {...props} />;
simon1170c322022-10-31 14:51:31 -0400141};
simon33c06182022-11-02 17:39:31 -0400142
143export const CallingMoreVerticalButton = (props: ExpandableButtonProps) => {
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500144 return <CallButton aria-label="more vertical" Icon={MoreVerticalIcon} {...props} />;
simon1170c322022-10-31 14:51:31 -0400145};
simon33c06182022-11-02 17:39:31 -0400146
147export const CallingRecordButton = (props: ExpandableButtonProps) => {
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500148 return <CallButton aria-label="recording options" Icon={RecordingIcon} {...props} />;
simon33c06182022-11-02 17:39:31 -0400149};
150
151export const CallingScreenShareButton = (props: ExpandableButtonProps) => {
simon4e7445c2022-11-16 21:18:46 -0500152 const { t } = useTranslation();
simon1170c322022-10-31 14:51:31 -0400153 return (
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500154 <CallButton
simon33c06182022-11-02 17:39:31 -0400155 aria-label="screen share"
156 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 => {
181 const { mediaDevices } = useContext(WebRtcContext);
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
192 return options.length > 0 ? [{ options }] : undefined;
simon9a8fe202022-11-15 18:25:49 -0500193};
194
simon33c06182022-11-02 17:39:31 -0400195export const CallingVolumeButton = (props: ExpandableButtonProps) => {
simon9a8fe202022-11-15 18:25:49 -0500196 const options = useMediaDeviceExpandMenuOptions('audiooutput');
197
simon9076a9a2022-11-29 17:13:01 -0500198 return <CallButton aria-label="volume options" Icon={VolumeIcon} expandMenuOptions={options} {...props} />;
simon33c06182022-11-02 17:39:31 -0400199};
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500200
201export const CallingMicButton = (props: ExpandableButtonProps) => {
simon9a8fe202022-11-15 18:25:49 -0500202 const options = useMediaDeviceExpandMenuOptions('audioinput');
simon1170c322022-10-31 14:51:31 -0400203
204 return (
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500205 <CallButton
simon1170c322022-10-31 14:51:31 -0400206 aria-label="microphone options"
simon9076a9a2022-11-29 17:13:01 -0500207 expandMenuOptions={options}
simonf929a362022-11-18 16:53:45 -0500208 IconButtonComp={ToggleAudioCameraIconButton}
209 {...props}
210 />
211 );
212};
213
214const ToggleAudioCameraIconButton = (props: IconButtonProps) => {
simon9076a9a2022-11-29 17:13:01 -0500215 const { isAudioOn, setIsAudioOn } = useContext(CallContext);
simonf929a362022-11-18 16:53:45 -0500216 return (
217 <ToggleIconButton
218 IconOn={MicroIcon}
219 IconOff={MicroOffIcon}
220 selected={isAudioOn}
simon9076a9a2022-11-29 17:13:01 -0500221 toggle={() => setIsAudioOn((v) => !v)}
simon1170c322022-10-31 14:51:31 -0400222 {...props}
223 />
224 );
225};
226
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500227export const CallingVideoCameraButton = (props: ExpandableButtonProps) => {
simon9a8fe202022-11-15 18:25:49 -0500228 const options = useMediaDeviceExpandMenuOptions('videoinput');
229
simon1170c322022-10-31 14:51:31 -0400230 return (
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500231 <CallButton
simon1170c322022-10-31 14:51:31 -0400232 aria-label="camera options"
simon9076a9a2022-11-29 17:13:01 -0500233 expandMenuOptions={options}
simonf929a362022-11-18 16:53:45 -0500234 IconButtonComp={ToggleVideoCameraIconButton}
235 {...props}
236 />
237 );
238};
239
240const ToggleVideoCameraIconButton = (props: IconButtonProps) => {
simon9076a9a2022-11-29 17:13:01 -0500241 const { isVideoOn, setIsVideoOn } = useContext(CallContext);
simonf929a362022-11-18 16:53:45 -0500242 return (
243 <ToggleIconButton
244 IconOn={VideoCameraIcon}
245 IconOff={VideoCameraOffIcon}
246 selected={isVideoOn}
simon9076a9a2022-11-29 17:13:01 -0500247 toggle={() => setIsVideoOn((v) => !v)}
simon1170c322022-10-31 14:51:31 -0400248 {...props}
249 />
250 );
251};
simon2d3b6532022-11-08 21:01:57 -0500252
253// Calling pending/receiving interface
simonf929a362022-11-18 16:53:45 -0500254export const CallingCancelButton = (props: IconButtonProps) => {
simonaccd8022022-11-24 15:04:53 -0500255 const { endCall } = useContext(CallContext);
simonf929a362022-11-18 16:53:45 -0500256
257 return (
258 <ColoredCallButton
259 aria-label="cancel call"
260 onClick={() => {
simonaccd8022022-11-24 15:04:53 -0500261 endCall();
simonf929a362022-11-18 16:53:45 -0500262 }}
263 Icon={CallEndIcon}
264 paletteColor={(theme) => theme.palette.error}
265 {...props}
266 />
267 );
simon2d3b6532022-11-08 21:01:57 -0500268};
269
simonf929a362022-11-18 16:53:45 -0500270export const CallingAnswerAudioButton = (props: IconButtonProps) => {
Charlie9c8779e2022-11-29 20:10:56 -0500271 const { acceptCall, callStatus } = useContext(CallContext);
simonf929a362022-11-18 16:53:45 -0500272
273 return (
274 <ColoredCallButton
Charlie9c8779e2022-11-29 20:10:56 -0500275 disabled={callStatus === CallStatus.Loading || callStatus === CallStatus.Connecting}
simonf929a362022-11-18 16:53:45 -0500276 aria-label="answer call audio"
277 onClick={() => {
MichelleSS55164202022-11-25 18:36:14 -0500278 acceptCall(false);
simonf929a362022-11-18 16:53:45 -0500279 }}
280 Icon={PlaceAudioCallIcon}
281 paletteColor={(theme) => theme.palette.success}
282 {...props}
283 />
284 );
simon2d3b6532022-11-08 21:01:57 -0500285};
286
simonf929a362022-11-18 16:53:45 -0500287export const CallingAnswerVideoButton = (props: IconButtonProps) => {
Charlie9c8779e2022-11-29 20:10:56 -0500288 const { acceptCall, callStatus } = useContext(CallContext);
simonf929a362022-11-18 16:53:45 -0500289 return (
290 <ColoredCallButton
Charlie9c8779e2022-11-29 20:10:56 -0500291 disabled={callStatus === CallStatus.Connecting || callStatus === CallStatus.Loading}
simonf929a362022-11-18 16:53:45 -0500292 aria-label="answer call video"
293 onClick={() => {
MichelleSS55164202022-11-25 18:36:14 -0500294 acceptCall(true);
simonf929a362022-11-18 16:53:45 -0500295 }}
296 paletteColor={(theme) => theme.palette.success}
297 Icon={VideoCameraIcon}
298 {...props}
299 />
300 );
301};
302
303export const CallingRefuseButton = (props: IconButtonProps) => {
simonaccd8022022-11-24 15:04:53 -0500304 const { endCall } = useContext(CallContext);
simonf929a362022-11-18 16:53:45 -0500305 return (
306 <ColoredCallButton
307 aria-label="refuse call"
308 onClick={() => {
simonaccd8022022-11-24 15:04:53 -0500309 endCall();
simonf929a362022-11-18 16:53:45 -0500310 }}
311 paletteColor={(theme) => theme.palette.error}
312 Icon={RoundCloseIcon}
313 {...props}
314 />
315 );
simon2d3b6532022-11-08 21:01:57 -0500316};