CallScreen interactivity fixes

This commit is contained in:
Fedor Indutny 2021-09-17 17:20:29 -07:00 committed by GitHub
parent 427055ea47
commit 4dcbb7352f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 64 additions and 37 deletions

View file

@ -75,6 +75,41 @@ export type PropsType = {
toggleSpeakerView: () => void; toggleSpeakerView: () => void;
}; };
type DirectCallHeaderMessagePropsType = {
i18n: LocalizerType;
callState: CallState;
joinedAt?: number;
};
function DirectCallHeaderMessage({
callState,
i18n,
joinedAt,
}: DirectCallHeaderMessagePropsType): JSX.Element | null {
const [acceptedDuration, setAcceptedDuration] = useState<
number | undefined
>();
useEffect(() => {
if (!joinedAt) {
return noop;
}
// It's really jumpy with a value of 500ms.
const interval = setInterval(() => {
setAcceptedDuration(Date.now() - joinedAt);
}, 100);
return clearInterval.bind(null, interval);
}, [joinedAt]);
if (callState === CallState.Reconnecting) {
return <>{i18n('callReconnecting')}</>;
}
if (callState === CallState.Accepted && acceptedDuration) {
return <>{i18n('callDuration', [renderDuration(acceptedDuration)])}</>;
}
return null;
}
export const CallScreen: React.FC<PropsType> = ({ export const CallScreen: React.FC<PropsType> = ({
activeCall, activeCall,
getGroupCallVideoFrameSource, getGroupCallVideoFrameSource,
@ -145,7 +180,6 @@ export const CallScreen: React.FC<PropsType> = ({
setControlsHover(false); setControlsHover(false);
}, [setControlsHover]); }, [setControlsHover]);
const [acceptedDuration, setAcceptedDuration] = useState<number | null>(null);
const [showControls, setShowControls] = useState(true); const [showControls, setShowControls] = useState(true);
const localVideoRef = useRef<HTMLVideoElement | null>(null); const localVideoRef = useRef<HTMLVideoElement | null>(null);
@ -157,17 +191,6 @@ export const CallScreen: React.FC<PropsType> = ({
}; };
}, [setLocalPreview, setRendererCanvas]); }, [setLocalPreview, setRendererCanvas]);
useEffect(() => {
if (!joinedAt) {
return noop;
}
// It's really jumpy with a value of 500ms.
const interval = setInterval(() => {
setAcceptedDuration(Date.now() - joinedAt);
}, 100);
return clearInterval.bind(null, interval);
}, [joinedAt]);
useEffect(() => { useEffect(() => {
if (!showControls || stickyControls || controlsHover) { if (!showControls || stickyControls || controlsHover) {
return noop; return noop;
@ -175,7 +198,7 @@ export const CallScreen: React.FC<PropsType> = ({
const timer = setTimeout(() => { const timer = setTimeout(() => {
setShowControls(false); setShowControls(false);
}, 5000); }, 5000);
return clearInterval.bind(null, timer); return clearTimeout.bind(null, timer);
}, [showControls, stickyControls, controlsHover]); }, [showControls, stickyControls, controlsHover]);
useEffect(() => { useEffect(() => {
@ -215,7 +238,7 @@ export const CallScreen: React.FC<PropsType> = ({
let isRinging: boolean; let isRinging: boolean;
let hasCallStarted: boolean; let hasCallStarted: boolean;
let headerMessage: string | undefined; let headerMessage: ReactNode | undefined;
let headerTitle: string | undefined; let headerTitle: string | undefined;
let isConnected: boolean; let isConnected: boolean;
let participantCount: number; let participantCount: number;
@ -227,10 +250,12 @@ export const CallScreen: React.FC<PropsType> = ({
activeCall.callState === CallState.Prering || activeCall.callState === CallState.Prering ||
activeCall.callState === CallState.Ringing; activeCall.callState === CallState.Ringing;
hasCallStarted = !isRinging; hasCallStarted = !isRinging;
headerMessage = renderDirectCallHeaderMessage( headerMessage = (
i18n, <DirectCallHeaderMessage
activeCall.callState || CallState.Prering, i18n={i18n}
acceptedDuration callState={activeCall.callState || CallState.Prering}
joinedAt={joinedAt}
/>
); );
headerTitle = isRinging ? undefined : conversation.title; headerTitle = isRinging ? undefined : conversation.title;
isConnected = activeCall.callState === CallState.Accepted; isConnected = activeCall.callState === CallState.Accepted;
@ -252,7 +277,6 @@ export const CallScreen: React.FC<PropsType> = ({
activeCall.outgoingRing && !activeCall.remoteParticipants.length; activeCall.outgoingRing && !activeCall.remoteParticipants.length;
hasCallStarted = activeCall.joinState !== GroupCallJoinState.NotJoined; hasCallStarted = activeCall.joinState !== GroupCallJoinState.NotJoined;
participantCount = activeCall.remoteParticipants.length + 1; participantCount = activeCall.remoteParticipants.length + 1;
headerMessage = undefined;
if (isRinging) { if (isRinging) {
headerTitle = undefined; headerTitle = undefined;
@ -399,6 +423,9 @@ export const CallScreen: React.FC<PropsType> = ({
hasCallStarted ? 'call-started' : 'call-not-started' hasCallStarted ? 'call-started' : 'call-not-started'
}` }`
)} )}
onFocus={() => {
setShowControls(true);
}}
onMouseMove={() => { onMouseMove={() => {
setShowControls(true); setShowControls(true);
}} }}
@ -451,27 +478,33 @@ export const CallScreen: React.FC<PropsType> = ({
'module-ongoing-call__footer__actions', 'module-ongoing-call__footer__actions',
controlsFadeClass controlsFadeClass
)} )}
onMouseEnter={onControlsMouseEnter}
onMouseLeave={onControlsMouseLeave}
> >
<CallingButton <CallingButton
buttonType={presentingButtonType} buttonType={presentingButtonType}
i18n={i18n} i18n={i18n}
onMouseEnter={onControlsMouseEnter}
onMouseLeave={onControlsMouseLeave}
onClick={togglePresenting} onClick={togglePresenting}
/> />
<CallingButton <CallingButton
buttonType={videoButtonType} buttonType={videoButtonType}
i18n={i18n} i18n={i18n}
onMouseEnter={onControlsMouseEnter}
onMouseLeave={onControlsMouseLeave}
onClick={toggleVideo} onClick={toggleVideo}
/> />
<CallingButton <CallingButton
buttonType={audioButtonType} buttonType={audioButtonType}
i18n={i18n} i18n={i18n}
onMouseEnter={onControlsMouseEnter}
onMouseLeave={onControlsMouseLeave}
onClick={toggleAudio} onClick={toggleAudio}
/> />
<CallingButton <CallingButton
buttonType={CallingButtonType.HANG_UP} buttonType={CallingButtonType.HANG_UP}
i18n={i18n} i18n={i18n}
onMouseEnter={onControlsMouseEnter}
onMouseLeave={onControlsMouseLeave}
onClick={() => { onClick={() => {
hangUp({ conversationId: conversation.id }); hangUp({ conversationId: conversation.id });
}} }}
@ -502,20 +535,6 @@ function getCallModeClassSuffix(
} }
} }
function renderDirectCallHeaderMessage(
i18n: LocalizerType,
callState: CallState,
acceptedDuration: null | number
): string | undefined {
if (callState === CallState.Reconnecting) {
return i18n('callReconnecting');
}
if (callState === CallState.Accepted && acceptedDuration) {
return i18n('callDuration', [renderDuration(acceptedDuration)]);
}
return undefined;
}
function renderDuration(ms: number): string { function renderDuration(ms: number): string {
const secs = Math.floor((ms / 1000) % 60) const secs = Math.floor((ms / 1000) % 60)
.toString() .toString()

View file

@ -19,6 +19,8 @@ const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
select('buttonType', CallingButtonType, CallingButtonType.HANG_UP), select('buttonType', CallingButtonType, CallingButtonType.HANG_UP),
i18n, i18n,
onClick: action('on-click'), onClick: action('on-click'),
onMouseEnter: action('on-mouse-enter'),
onMouseLeave: action('on-mouse-leave'),
tooltipDirection: select( tooltipDirection: select(
'tooltipDirection', 'tooltipDirection',
TooltipPlacement, TooltipPlacement,

View file

@ -29,6 +29,8 @@ export type PropsType = {
i18n: LocalizerType; i18n: LocalizerType;
isVisible?: boolean; isVisible?: boolean;
onClick: () => void; onClick: () => void;
onMouseEnter?: () => void;
onMouseLeave?: () => void;
tooltipDirection?: TooltipPlacement; tooltipDirection?: TooltipPlacement;
}; };
@ -37,6 +39,8 @@ export const CallingButton = ({
i18n, i18n,
isVisible = true, isVisible = true,
onClick, onClick,
onMouseEnter,
onMouseLeave,
tooltipDirection, tooltipDirection,
}: PropsType): JSX.Element => { }: PropsType): JSX.Element => {
const uniqueButtonId = useMemo(() => uuid(), []); const uniqueButtonId = useMemo(() => uuid(), []);
@ -128,6 +132,8 @@ export const CallingButton = ({
disabled={disabled} disabled={disabled}
id={uniqueButtonId} id={uniqueButtonId}
onClick={onClick} onClick={onClick}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
type="button" type="button"
> >
<div /> <div />

View file

@ -1,7 +1,7 @@
// Copyright 2020-2021 Signal Messenger, LLC // Copyright 2020-2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import React from 'react'; import React, { ReactNode } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { LocalizerType } from '../types/Util'; import { LocalizerType } from '../types/Util';
import { Tooltip } from './Tooltip'; import { Tooltip } from './Tooltip';
@ -11,7 +11,7 @@ export type PropsType = {
i18n: LocalizerType; i18n: LocalizerType;
isInSpeakerView?: boolean; isInSpeakerView?: boolean;
isGroupCall?: boolean; isGroupCall?: boolean;
message?: string; message?: ReactNode;
onCancel?: () => void; onCancel?: () => void;
participantCount: number; participantCount: number;
showParticipantsList: boolean; showParticipantsList: boolean;