Improve permission handling in call flow

Improve permission handling by asking the user to give mic and camera permissions before sending `CallBegin` or `CallAccept` for the caller and receiver respectively.
Followed the flow described here: https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API/Connectivity#session_descriptions

CallProvider:

- Change functions order to place listeners under the function that sends the corresponding WebSocket message.
- Replace `Default` CallStatus with `Loading` for when asking user permissions before sending the `CallBegin`/`CallAccept` message.
- Remove `localStream` and `remoteStream` from `CallContext`. They are now available only in `WebRtcContext`.
- Replace `setAudioStatus` and `setVideoStatus` with `setIsAudioOn` and `setIsVideoOn`. A `useEffect` is now used to disable the tracks when the audio/video status changes.

WebRtcProvider:

- Move WebRTC connection close logic to WebRtcProvider
- Remove `webRtcConnection` from `WebRtcContext`. `WebRtcProvider` is now in charge of setting everything related to the WebRTC Connection.

UI:

- Add `CallPermissionDenied` page for when permissions are denied.
- Rework `CallPending` to display `Loading...` when waiting for user permissions

Change-Id: I48153577cca4c73cdb9b81d2fa78cfdfe2e06d69
diff --git a/client/src/components/CallButtons.tsx b/client/src/components/CallButtons.tsx
index f6f7275..d0c2c5e 100644
--- a/client/src/components/CallButtons.tsx
+++ b/client/src/components/CallButtons.tsx
@@ -22,7 +22,14 @@
 import { useTranslation } from 'react-i18next';
 
 import { CallContext } from '../contexts/CallProvider';
-import { ExpandableButton, ExpandableButtonProps, ShapedButtonProps, ToggleIconButton } from './Button';
+import { WebRtcContext } from '../contexts/WebRtcProvider';
+import {
+  ExpandableButton,
+  ExpandableButtonProps,
+  ExpandMenuRadioOption,
+  ShapedButtonProps,
+  ToggleIconButton,
+} from './Button';
 import {
   CallEndIcon,
   ChatBubbleIcon,
@@ -170,10 +177,10 @@
   );
 };
 
-const useMediaDeviceExpandMenuOptions = (kind: MediaDeviceKind) => {
-  const { mediaDevices } = useContext(CallContext);
+const useMediaDeviceExpandMenuOptions = (kind: MediaDeviceKind): ExpandMenuRadioOption[] | undefined => {
+  const { mediaDevices } = useContext(WebRtcContext);
 
-  return useMemo(
+  const options = useMemo(
     () =>
       mediaDevices[kind].map((device) => ({
         key: device.deviceId,
@@ -181,23 +188,14 @@
       })),
     [mediaDevices, kind]
   );
+
+  return options.length > 0 ? [{ options }] : undefined;
 };
 
 export const CallingVolumeButton = (props: ExpandableButtonProps) => {
   const options = useMediaDeviceExpandMenuOptions('audiooutput');
 
-  return (
-    <CallButton
-      aria-label="volume options"
-      Icon={VolumeIcon}
-      expandMenuOptions={[
-        {
-          options,
-        },
-      ]}
-      {...props}
-    />
-  );
+  return <CallButton aria-label="volume options" Icon={VolumeIcon} expandMenuOptions={options} {...props} />;
 };
 
 export const CallingMicButton = (props: ExpandableButtonProps) => {
@@ -206,11 +204,7 @@
   return (
     <CallButton
       aria-label="microphone options"
-      expandMenuOptions={[
-        {
-          options,
-        },
-      ]}
+      expandMenuOptions={options}
       IconButtonComp={ToggleAudioCameraIconButton}
       {...props}
     />
@@ -218,13 +212,13 @@
 };
 
 const ToggleAudioCameraIconButton = (props: IconButtonProps) => {
-  const { isAudioOn, setAudioStatus } = useContext(CallContext);
+  const { isAudioOn, setIsAudioOn } = useContext(CallContext);
   return (
     <ToggleIconButton
       IconOn={MicroIcon}
       IconOff={MicroOffIcon}
       selected={isAudioOn}
-      toggle={() => setAudioStatus(!isAudioOn)}
+      toggle={() => setIsAudioOn((v) => !v)}
       {...props}
     />
   );
@@ -236,11 +230,7 @@
   return (
     <CallButton
       aria-label="camera options"
-      expandMenuOptions={[
-        {
-          options,
-        },
-      ]}
+      expandMenuOptions={options}
       IconButtonComp={ToggleVideoCameraIconButton}
       {...props}
     />
@@ -248,13 +238,13 @@
 };
 
 const ToggleVideoCameraIconButton = (props: IconButtonProps) => {
-  const { isVideoOn, setVideoStatus } = useContext(CallContext);
+  const { isVideoOn, setIsVideoOn } = useContext(CallContext);
   return (
     <ToggleIconButton
       IconOn={VideoCameraIcon}
       IconOff={VideoCameraOffIcon}
       selected={isVideoOn}
-      toggle={() => setVideoStatus(!isVideoOn)}
+      toggle={() => setIsVideoOn((v) => !v)}
       {...props}
     />
   );