From 78cfa7eca3d8f0d671f62b8188e48ef39aa9c4ff Mon Sep 17 00:00:00 2001 From: ayumi-signal <143036029+ayumi-signal@users.noreply.github.com> Date: Wed, 19 Feb 2025 09:13:27 -0800 Subject: [PATCH] Fix call animation from speaker to grid view so animating speaker is on top --- stylesheets/_modules.scss | 6 ++++ ts/components/GroupCallRemoteParticipant.tsx | 35 ++++++++++++++++++-- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/stylesheets/_modules.scss b/stylesheets/_modules.scss index 67fe242bb..b0b040bb5 100644 --- a/stylesheets/_modules.scss +++ b/stylesheets/_modules.scss @@ -4352,6 +4352,8 @@ button.module-image__border-overlay:focus { line-height: 0; overflow: hidden; border-radius: 10px; + + // Should match GroupCallRemoteParticipant CONTAINER_TRANSITION_TIME transition: top 200ms linear, inset-inline-start 200ms linear, @@ -4531,6 +4533,10 @@ button.module-image__border-overlay:focus { } } + &--is-on-top { + z-index: variables.$z-index-above-above-base; + } + &:hover { .module-ongoing-call__group-call-remote-participant__info__contact-name { display: block; diff --git a/ts/components/GroupCallRemoteParticipant.tsx b/ts/components/GroupCallRemoteParticipant.tsx index f6eb2d58c..d2ec7aea3 100644 --- a/ts/components/GroupCallRemoteParticipant.tsx +++ b/ts/components/GroupCallRemoteParticipant.tsx @@ -10,7 +10,7 @@ import React, { useEffect, } from 'react'; import classNames from 'classnames'; -import { noop } from 'lodash'; +import { debounce, noop } from 'lodash'; import type { VideoFrameSource } from '@signalapp/ringrtc'; import type { GroupCallRemoteParticipantType } from '../types/Calling'; import type { LocalizerType } from '../types/Util'; @@ -36,6 +36,9 @@ const MAX_TIME_TO_SHOW_STALE_VIDEO_FRAMES = 10000; const MAX_TIME_TO_SHOW_STALE_SCREENSHARE_FRAMES = 60000; const DELAY_TO_SHOW_MISSING_MEDIA_KEYS = 5000; +// Should match transition time in .module-ongoing-call__group-call-remote-participant +const CONTAINER_TRANSITION_TIME = 200; + type BasePropsType = { getFrameBuffer: () => Buffer; getGroupCallVideoFrameSource: (demuxId: number) => VideoFrameSource; @@ -111,6 +114,10 @@ export const GroupCallRemoteParticipant: React.FC = React.memo( SPEAKING_LINGER_MS ); const previousSharingScreen = usePrevious(sharingScreen, sharingScreen); + const prevIsActiveSpeakerInSpeakerView = usePrevious( + isActiveSpeakerInSpeakerView, + isActiveSpeakerInSpeakerView + ); const isImageDataCached = sharingScreen && imageDataCache.current?.has(demuxId); @@ -120,6 +127,7 @@ export const GroupCallRemoteParticipant: React.FC = React.memo( videoAspectRatio ? videoAspectRatio >= 1 : true ); const [showErrorDialog, setShowErrorDialog] = useState(false); + const [isOnTop, setIsOnTop] = useState(false); // We have some state (`hasReceivedVideoRecently`) and this ref. We can't have a // single state value like `lastReceivedVideoAt` because (1) it won't automatically @@ -290,6 +298,27 @@ export const GroupCallRemoteParticipant: React.FC = React.memo( }; }, [hasRemoteVideo, isVisible, renderVideoFrame, videoFrameSource]); + const setIsOnTopDebounced = useMemo( + () => debounce(setIsOnTop, CONTAINER_TRANSITION_TIME), + [setIsOnTop] + ); + + // When in speaker view or while transitioning out of it, keep the main speaker + // z-indexed above all other participants + useEffect(() => { + if (isActiveSpeakerInSpeakerView !== prevIsActiveSpeakerInSpeakerView) { + if (isActiveSpeakerInSpeakerView) { + setIsOnTop(true); + } else { + setIsOnTopDebounced(false); + } + } + }, [ + prevIsActiveSpeakerInSpeakerView, + isActiveSpeakerInSpeakerView, + setIsOnTopDebounced, + ]); + let canvasStyles: CSSProperties; let containerStyles: CSSProperties; @@ -521,7 +550,9 @@ export const GroupCallRemoteParticipant: React.FC = React.memo( remoteParticipantsCount > 1 && 'module-ongoing-call__group-call-remote-participant--speaking', isHandRaised && - 'module-ongoing-call__group-call-remote-participant--hand-raised' + 'module-ongoing-call__group-call-remote-participant--hand-raised', + isOnTop && + 'module-ongoing-call__group-call-remote-participant--is-on-top' )} ref={intersectionRef} style={containerStyles}