| undefined
): boolean => {
@@ -97,11 +95,11 @@ export const isInSpeakerView = (
);
};
-function DirectCallHeaderMessage({
- callState,
- i18n,
+function CallDuration({
joinedAt,
-}: DirectCallHeaderMessagePropsType): JSX.Element | null {
+}: {
+ joinedAt: number | null;
+}): JSX.Element | null {
const [acceptedDuration, setAcceptedDuration] = useState<
number | undefined
>();
@@ -117,14 +115,8 @@ function DirectCallHeaderMessage({
return clearInterval.bind(null, interval);
}, [joinedAt]);
- if (callState === CallState.Accepted && acceptedDuration) {
- return (
- <>
- {i18n('icu:callDuration', {
- duration: renderDuration(acceptedDuration),
- })}
- >
- );
+ if (acceptedDuration) {
+ return <>{renderDuration(acceptedDuration)}>;
}
return null;
}
@@ -161,7 +153,6 @@ export function CallScreen({
presentingSource,
remoteParticipants,
showNeedsScreenRecordingPermissionsWarning,
- showParticipantsList,
} = activeCall;
const isSpeaking = useValueAtFixedRate(
@@ -260,6 +251,7 @@ export function CallScreen({
};
}, [toggleAudio, toggleVideo]);
+ useMutedToast(hasLocalAudio, i18n);
useReconnectingToast({ activeCall, i18n });
useScreenSharingStoppedToast({ activeCall, i18n });
@@ -272,10 +264,10 @@ export function CallScreen({
);
const isSendingVideo = hasLocalVideo || presentingSource;
+ const isReconnecting: boolean = callingIsReconnecting(activeCall);
let isRinging: boolean;
let hasCallStarted: boolean;
- let headerMessage: ReactNode | undefined;
let headerTitle: string | undefined;
let isConnected: boolean;
let participantCount: number;
@@ -287,14 +279,6 @@ export function CallScreen({
activeCall.callState === CallState.Prering ||
activeCall.callState === CallState.Ringing;
hasCallStarted = !isRinging;
- headerMessage = (
-
- );
- headerTitle = isRinging ? undefined : conversation.title;
isConnected = activeCall.callState === CallState.Accepted;
participantCount = isConnected ? 2 : 0;
remoteParticipantsElement = hasCallStarted ? (
@@ -302,7 +286,7 @@ export function CallScreen({
conversation={conversation}
hasRemoteVideo={hasRemoteVideo}
i18n={i18n}
- isReconnecting={isReconnecting(activeCall)}
+ isReconnecting={isReconnecting}
setRendererCanvas={setRendererCanvas}
/>
) : (
@@ -338,7 +322,7 @@ export function CallScreen({
remoteParticipants={activeCall.remoteParticipants}
setGroupCallVideoRequest={setGroupCallVideoRequest}
remoteAudioLevels={activeCall.remoteAudioLevels}
- isCallReconnecting={isReconnecting(activeCall)}
+ isCallReconnecting={isReconnecting}
/>
);
break;
@@ -454,6 +438,46 @@ export function CallScreen({
presentingButtonType = CallingButtonType.PRESENTING_OFF;
}
+ const callStatus: ReactNode | string = React.useMemo(() => {
+ if (isRinging) {
+ return i18n('icu:outgoingCallRinging');
+ }
+ if (isReconnecting) {
+ return i18n('icu:callReconnecting');
+ }
+ if (isGroupCall) {
+ return (
+
+ );
+ }
+ // joinedAt is only available for direct calls
+ if (isConnected) {
+ return ;
+ }
+ if (hasLocalVideo) {
+ return i18n('icu:ContactListItem__menu__video-call');
+ }
+ if (hasLocalAudio) {
+ return i18n('icu:CallControls__InfoDisplay--audio-call');
+ }
+ return null;
+ }, [
+ i18n,
+ isRinging,
+ isConnected,
+ activeCall.joinedAt,
+ isReconnecting,
+ isGroupCall,
+ participantCount,
+ hasLocalVideo,
+ hasLocalAudio,
+ toggleParticipants,
+ ]);
+
return (
-
+ {conversation.title}
+ {callStatus}
+
+
+
+
+
+
+
-
-
-
+ >
+
+
{localPreviewNode}
diff --git a/ts/components/CallingButton.stories.tsx b/ts/components/CallingButton.stories.tsx
index 34340a84cd..4d51cdf705 100644
--- a/ts/components/CallingButton.stories.tsx
+++ b/ts/components/CallingButton.stories.tsx
@@ -26,7 +26,7 @@ export default {
},
},
args: {
- buttonType: CallingButtonType.HANG_UP,
+ buttonType: CallingButtonType.RING_ON,
i18n,
onClick: action('on-click'),
onMouseEnter: action('on-mouse-enter'),
diff --git a/ts/components/CallingButton.tsx b/ts/components/CallingButton.tsx
index 253e4a65d7..d07ec980ac 100644
--- a/ts/components/CallingButton.tsx
+++ b/ts/components/CallingButton.tsx
@@ -13,7 +13,6 @@ export enum CallingButtonType {
AUDIO_DISABLED = 'AUDIO_DISABLED',
AUDIO_OFF = 'AUDIO_OFF',
AUDIO_ON = 'AUDIO_ON',
- HANG_UP = 'HANG_UP',
PRESENTING_DISABLED = 'PRESENTING_DISABLED',
PRESENTING_OFF = 'PRESENTING_OFF',
PRESENTING_ON = 'PRESENTING_ON',
@@ -48,88 +47,69 @@ export function CallingButton({
let classNameSuffix = '';
let tooltipContent = '';
- let label = '';
let disabled = false;
if (buttonType === CallingButtonType.AUDIO_DISABLED) {
classNameSuffix = 'audio--disabled';
tooltipContent = i18n('icu:calling__button--audio-disabled');
- label = i18n('icu:calling__button--audio__label');
disabled = true;
} else if (buttonType === CallingButtonType.AUDIO_OFF) {
classNameSuffix = 'audio--off';
tooltipContent = i18n('icu:calling__button--audio-on');
- label = i18n('icu:calling__button--audio__label');
} else if (buttonType === CallingButtonType.AUDIO_ON) {
classNameSuffix = 'audio--on';
tooltipContent = i18n('icu:calling__button--audio-off');
- label = i18n('icu:calling__button--audio__label');
} else if (buttonType === CallingButtonType.VIDEO_DISABLED) {
classNameSuffix = 'video--disabled';
tooltipContent = i18n('icu:calling__button--video-disabled');
disabled = true;
- label = i18n('icu:calling__button--video__label');
} else if (buttonType === CallingButtonType.VIDEO_OFF) {
classNameSuffix = 'video--off';
tooltipContent = i18n('icu:calling__button--video-on');
- label = i18n('icu:calling__button--video__label');
} else if (buttonType === CallingButtonType.VIDEO_ON) {
classNameSuffix = 'video--on';
tooltipContent = i18n('icu:calling__button--video-off');
- label = i18n('icu:calling__button--video__label');
- } else if (buttonType === CallingButtonType.HANG_UP) {
- classNameSuffix = 'hangup';
- tooltipContent = i18n('icu:calling__hangup');
- label = i18n('icu:calling__hangup');
} else if (buttonType === CallingButtonType.RING_DISABLED) {
classNameSuffix = 'ring--disabled';
disabled = true;
tooltipContent = i18n(
'icu:calling__button--ring__disabled-because-group-is-too-large'
);
- label = i18n('icu:calling__button--ring__label');
} else if (buttonType === CallingButtonType.RING_OFF) {
classNameSuffix = 'ring--off';
- tooltipContent = i18n('icu:calling__button--ring__on');
- label = i18n('icu:calling__button--ring__label');
+ tooltipContent = i18n('icu:CallingButton--ring-on');
} else if (buttonType === CallingButtonType.RING_ON) {
classNameSuffix = 'ring--on';
- tooltipContent = i18n('icu:calling__button--ring__off');
- label = i18n('icu:calling__button--ring__label');
+ tooltipContent = i18n('icu:CallingButton__ring-off');
} else if (buttonType === CallingButtonType.PRESENTING_DISABLED) {
classNameSuffix = 'presenting--disabled';
tooltipContent = i18n('icu:calling__button--presenting-disabled');
disabled = true;
- label = i18n('icu:calling__button--presenting__label');
} else if (buttonType === CallingButtonType.PRESENTING_ON) {
classNameSuffix = 'presenting--on';
tooltipContent = i18n('icu:calling__button--presenting-off');
- label = i18n('icu:calling__button--presenting__label');
} else if (buttonType === CallingButtonType.PRESENTING_OFF) {
classNameSuffix = 'presenting--off';
tooltipContent = i18n('icu:calling__button--presenting-on');
- label = i18n('icu:calling__button--presenting__label');
}
- const className = classNames(
- 'CallingButton__icon',
- `CallingButton__icon--${classNameSuffix}`
- );
-
return (
-
-
-
+
+
);
}
diff --git a/ts/components/CallingHeader.stories.tsx b/ts/components/CallingHeader.stories.tsx
index 45e0275fb2..251c5dbde3 100644
--- a/ts/components/CallingHeader.stories.tsx
+++ b/ts/components/CallingHeader.stories.tsx
@@ -24,9 +24,7 @@ export default {
isGroupCall: false,
message: '',
participantCount: 0,
- showParticipantsList: false,
title: 'With Someone',
- toggleParticipants: action('toggle-participants'),
togglePip: action('toggle-pip'),
toggleSettings: action('toggle-settings'),
},
@@ -52,14 +50,7 @@ export function WithParticipants(args: PropsType): JSX.Element {
}
export function WithParticipantsShown(args: PropsType): JSX.Element {
- return (
-
- );
+ return ;
}
export function LongTitle(args: PropsType): JSX.Element {
diff --git a/ts/components/CallingHeader.tsx b/ts/components/CallingHeader.tsx
index 99081b09d0..14b05acc04 100644
--- a/ts/components/CallingHeader.tsx
+++ b/ts/components/CallingHeader.tsx
@@ -15,9 +15,7 @@ export type PropsType = {
message?: ReactNode;
onCancel?: () => void;
participantCount: number;
- showParticipantsList: boolean;
title?: string;
- toggleParticipants?: () => void;
togglePip?: () => void;
toggleSettings: () => void;
toggleSpeakerView?: () => void;
@@ -30,9 +28,7 @@ export function CallingHeader({
message,
onCancel,
participantCount,
- showParticipantsList,
title,
- toggleParticipants,
togglePip,
toggleSettings,
toggleSpeakerView,
@@ -46,48 +42,24 @@ export function CallingHeader({
{message}
) : null}
- {isGroupCall && participantCount ? (
+ {togglePip && (
- ) : null}
-
-
-
-
-
+ )}
{isGroupCall && participantCount > 2 && toggleSpeakerView && (
-
-
- )}
- {togglePip && (
-
-
-
+ >
+
+
)}
+
+
+
+
+
{onCancel && (
-
+
+ >
+
+
)}
diff --git a/ts/components/CallingLobby.tsx b/ts/components/CallingLobby.tsx
index 5d911ef90c..9244dc32df 100644
--- a/ts/components/CallingLobby.tsx
+++ b/ts/components/CallingLobby.tsx
@@ -12,6 +12,7 @@ import type {
import { CallingButton, CallingButtonType } from './CallingButton';
import { TooltipPlacement } from './Tooltip';
import { CallBackgroundBlur } from './CallBackgroundBlur';
+import { CallParticipantCount } from './CallParticipantCount';
import { CallingHeader } from './CallingHeader';
import { CallingPreCallInfo, RingMode } from './CallingPreCallInfo';
import {
@@ -23,6 +24,7 @@ import { useIsOnline } from '../hooks/useIsOnline';
import * as KeyboardLayout from '../services/keyboardLayout';
import type { ConversationType } from '../state/ducks/conversations';
import { useCallingToasts } from './CallingToast';
+import { useMutedToast } from './CallingToastManager';
export type PropsType = {
availableCameras: Array
;
@@ -84,7 +86,6 @@ export function CallingLobby({
setLocalPreview,
setLocalVideo,
setOutgoingRing,
- showParticipantsList,
toggleParticipants,
toggleSettings,
outgoingRing,
@@ -143,8 +144,6 @@ export function CallingLobby({
const [isCallConnecting, setIsCallConnecting] = React.useState(false);
- useWasInitiallyMutedToast(hasLocalAudio, i18n);
-
// eslint-disable-next-line no-nested-ternary
const videoButtonType = hasLocalVideo
? CallingButtonType.VIDEO_ON
@@ -201,6 +200,38 @@ export function CallingLobby({
callingLobbyJoinButtonVariant = CallingLobbyJoinButtonVariant.Start;
}
+ const callStatus = React.useMemo(() => {
+ if (isGroupCall) {
+ return (
+
+ );
+ }
+ if (hasLocalVideo) {
+ return i18n('icu:ContactListItem__menu__video-call');
+ }
+ if (hasLocalAudio) {
+ return i18n('icu:CallControls__InfoDisplay--audio-call');
+ }
+ return null;
+ }, [
+ isGroupCall,
+ peekedParticipants.length,
+ i18n,
+ hasLocalVideo,
+ hasLocalAudio,
+ groupMembers?.length,
+ toggleParticipants,
+ ]);
+
+ useMutedToast(hasLocalAudio, i18n);
+ useWasInitiallyMutedToast(hasLocalAudio, i18n);
+ useOutgoingRingToast(isRingButtonVisible, outgoingRing, i18n);
+
return (
@@ -222,8 +253,6 @@ export function CallingLobby({
i18n={i18n}
isGroupCall={isGroupCall}
participantCount={peekedParticipants.length}
- showParticipantsList={showParticipantsList}
- toggleParticipants={toggleParticipants}
toggleSettings={toggleSettings}
onCancel={onCallCanceled}
/>
@@ -249,37 +278,44 @@ export function CallingLobby({
{i18n('icu:calling__your-video-is-off')}
-
-
-
-
+
+
+
{conversation.title}
+
{callStatus}
+
+
+
+
+
+
+
+ {
+ setIsCallConnecting(true);
+ onJoinCall();
+ }}
+ variant={callingLobbyJoinButtonVariant}
+ />
+
-
-
{
- setIsCallConnecting(true);
- onJoinCall();
- }}
- variant={callingLobbyJoinButtonVariant}
- />
);
@@ -313,3 +349,51 @@ function useWasInitiallyMutedToast(
}
}, [hideToast, wasInitiallyMuted, hasLocalAudio]);
}
+
+function useOutgoingRingToast(
+ isRingButtonVisible: boolean,
+ outgoingRing: boolean,
+ i18n: LocalizerType
+): void {
+ const [previousOutgoingRing, setPreviousOutgoingRing] = React.useState<
+ undefined | boolean
+ >(undefined);
+ const { showToast, hideToast } = useCallingToasts();
+ const RINGING_TOAST_KEY = 'ringing';
+
+ React.useEffect(() => {
+ if (!isRingButtonVisible) {
+ return;
+ }
+
+ setPreviousOutgoingRing(outgoingRing);
+ }, [isRingButtonVisible, outgoingRing]);
+
+ React.useEffect(() => {
+ if (!isRingButtonVisible) {
+ return;
+ }
+
+ if (
+ previousOutgoingRing !== undefined &&
+ outgoingRing !== previousOutgoingRing
+ ) {
+ hideToast(RINGING_TOAST_KEY);
+ showToast({
+ key: RINGING_TOAST_KEY,
+ content: outgoingRing
+ ? i18n('icu:CallControls__RingingToast--ringing-on')
+ : i18n('icu:CallControls__RingingToast--ringing-off'),
+ autoClose: true,
+ dismissable: true,
+ });
+ }
+ }, [
+ isRingButtonVisible,
+ outgoingRing,
+ previousOutgoingRing,
+ hideToast,
+ showToast,
+ i18n,
+ ]);
+}
diff --git a/ts/components/CallingLobbyJoinButton.tsx b/ts/components/CallingLobbyJoinButton.tsx
index c016655891..1dad068178 100644
--- a/ts/components/CallingLobbyJoinButton.tsx
+++ b/ts/components/CallingLobbyJoinButton.tsx
@@ -9,9 +9,6 @@ import type { LocalizerType } from '../types/Util';
import { Button, ButtonVariant } from './Button';
import { Spinner } from './Spinner';
-const PADDING_HORIZONTAL = 48;
-const PADDING_VERTICAL = 12;
-
export enum CallingLobbyJoinButtonVariant {
CallIsFull = 'CallIsFull',
Join = 'Join',
@@ -47,18 +44,24 @@ export function CallingLobbyJoinButton({
const childrenByVariant: Record = {
[CallingLobbyJoinButtonVariant.CallIsFull]: i18n(
- 'icu:calling__call-is-full'
+ 'icu:CallingLobbyJoinButton--call-full'
+ ),
+ [CallingLobbyJoinButtonVariant.Loading]: (
+
+ ),
+ [CallingLobbyJoinButtonVariant.Join]: i18n(
+ 'icu:CallingLobbyJoinButton--join'
+ ),
+ [CallingLobbyJoinButtonVariant.Start]: i18n(
+ 'icu:CallingLobbyJoinButton--start'
),
- [CallingLobbyJoinButtonVariant.Loading]: ,
- [CallingLobbyJoinButtonVariant.Join]: i18n('icu:calling__join'),
- [CallingLobbyJoinButtonVariant.Start]: i18n('icu:calling__start'),
};
return (
<>
{Boolean(width && height) && (
-
- {participant.hasRemoteAudio === false ? (
-
- ) : null}
+
{participant.hasRemoteVideo === false ? (
) : null}
{participant.presenting ? (
) : null}
+ {participant.hasRemoteAudio === false ? (
+
+ ) : null}
)
diff --git a/ts/components/CallingToastManager.tsx b/ts/components/CallingToastManager.tsx
index 65ce586e6e..14606cf18a 100644
--- a/ts/components/CallingToastManager.tsx
+++ b/ts/components/CallingToastManager.tsx
@@ -98,3 +98,35 @@ export function useScreenSharingStoppedToast({
}
}, [activeCall, previousPresenter, showToast, i18n]);
}
+
+export function useMutedToast(
+ hasLocalAudio: boolean,
+ i18n: LocalizerType
+): void {
+ const [previousHasLocalAudio, setPreviousHasLocalAudio] = useState<
+ undefined | boolean
+ >(undefined);
+ const { showToast, hideToast } = useCallingToasts();
+ const MUTED_TOAST_KEY = 'muted';
+
+ useEffect(() => {
+ setPreviousHasLocalAudio(hasLocalAudio);
+ }, [hasLocalAudio]);
+
+ useEffect(() => {
+ if (
+ previousHasLocalAudio !== undefined &&
+ hasLocalAudio !== previousHasLocalAudio
+ ) {
+ hideToast(MUTED_TOAST_KEY);
+ showToast({
+ key: MUTED_TOAST_KEY,
+ content: hasLocalAudio
+ ? i18n('icu:CallControls__MutedToast--unmuted')
+ : i18n('icu:CallControls__MutedToast--muted'),
+ autoClose: true,
+ dismissable: true,
+ });
+ }
+ }, [hasLocalAudio, previousHasLocalAudio, hideToast, showToast, i18n]);
+}
diff --git a/ts/components/GroupCallOverflowArea.tsx b/ts/components/GroupCallOverflowArea.tsx
index 7655e8d1af..fcac255688 100644
--- a/ts/components/GroupCallOverflowArea.tsx
+++ b/ts/components/GroupCallOverflowArea.tsx
@@ -13,7 +13,7 @@ const OVERFLOW_SCROLLED_TO_EDGE_THRESHOLD = 20;
const OVERFLOW_SCROLL_BUTTON_RATIO = 0.75;
// This should be an integer, as sub-pixel widths can cause performance issues.
-export const OVERFLOW_PARTICIPANT_WIDTH = 140;
+export const OVERFLOW_PARTICIPANT_WIDTH = 107;
export type PropsType = {
getFrameBuffer: () => Buffer;