diff --git a/ts/components/CallScreen.dom.stories.tsx b/ts/components/CallScreen.dom.stories.tsx index 376301aa63..48f3a58105 100644 --- a/ts/components/CallScreen.dom.stories.tsx +++ b/ts/components/CallScreen.dom.stories.tsx @@ -960,7 +960,7 @@ export function GroupCallSuggestLowerHand(): React.JSX.Element { // are raised function useHandRaiser( activeCall: ActiveGroupCallType, - frequency = 3000, + frequency = 2000, min = 0, max = 5 ) { diff --git a/ts/components/CallingRaisedHandsList.dom.tsx b/ts/components/CallingRaisedHandsList.dom.tsx index 1e6b187246..445a045eff 100644 --- a/ts/components/CallingRaisedHandsList.dom.tsx +++ b/ts/components/CallingRaisedHandsList.dom.tsx @@ -1,7 +1,7 @@ // Copyright 2023 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -import React from 'react'; +import React, { useEffect, useRef } from 'react'; import { animated, useSpring } from '@react-spring/web'; import { Avatar, AvatarSize } from './Avatar.dom.js'; @@ -205,9 +205,16 @@ export function CallingRaisedHandsListButton({ syncedLocalHandRaised, syncedLocalHandRaised ); + const prevShownRaisedHandsCountRef = useRef(raisedHandsCount); + const prevShownSyncedLocalHandRaisedRef = useRef( + syncedLocalHandRaised + ); - React.useEffect(() => { - if (raisedHandsCount > prevRaisedHandsCount) { + useEffect(() => { + if ( + raisedHandsCount > prevRaisedHandsCount || + (raisedHandsCount > 0 && !isVisible) + ) { setIsVisible(true); opacitySpringApi.stop(); drop(Promise.all(opacitySpringApi.start({ opacity: 1 }))); @@ -237,6 +244,7 @@ export function CallingRaisedHandsListButton({ ); } }, [ + isVisible, raisedHandsCount, prevRaisedHandsCount, opacitySpringApi, @@ -244,6 +252,18 @@ export function CallingRaisedHandsListButton({ setIsVisible, ]); + useEffect(() => { + if (isVisible && raisedHandsCount === 0 && prevRaisedHandsCount > 0) { + prevShownRaisedHandsCountRef.current = prevRaisedHandsCount; + prevShownSyncedLocalHandRaisedRef.current = prevSyncedLocalHandRaised; + } + }, [ + isVisible, + prevRaisedHandsCount, + prevSyncedLocalHandRaised, + raisedHandsCount, + ]); + if (!isVisible) { return null; } @@ -252,9 +272,14 @@ export function CallingRaisedHandsListButton({ // abrupt label changes. let shownSyncedLocalHandRaised: boolean = syncedLocalHandRaised; let shownRaisedHandsCount: number = raisedHandsCount; - if (raisedHandsCount === 0 && prevRaisedHandsCount) { - shownRaisedHandsCount = prevRaisedHandsCount; - shownSyncedLocalHandRaised = prevSyncedLocalHandRaised; + if (raisedHandsCount === 0) { + if (prevRaisedHandsCount > 0) { + shownRaisedHandsCount = prevRaisedHandsCount; + shownSyncedLocalHandRaised = prevSyncedLocalHandRaised; + } else { + shownRaisedHandsCount = prevShownRaisedHandsCountRef.current; + shownSyncedLocalHandRaised = prevShownSyncedLocalHandRaisedRef.current; + } } return ( diff --git a/ts/util/lint/exceptions.json b/ts/util/lint/exceptions.json index 907b39be68..827808cd4a 100644 --- a/ts/util/lint/exceptions.json +++ b/ts/util/lint/exceptions.json @@ -1050,6 +1050,22 @@ "reasonCategory": "usageTrusted", "updated": "2025-12-30T20:46:48.849Z" }, + { + "rule": "React-useRef", + "path": "ts/components/CallingRaisedHandsList.dom.tsx", + "line": " const prevShownRaisedHandsCountRef = useRef(raisedHandsCount);", + "reasonCategory": "usageTrusted", + "updated": "2026-01-09T21:23:45.188Z", + "reasonDetail": "Maintain last shown raised hand count to prevent 0" + }, + { + "rule": "React-useRef", + "path": "ts/components/CallingRaisedHandsList.dom.tsx", + "line": " const prevShownSyncedLocalHandRaisedRef = useRef(", + "reasonCategory": "usageTrusted", + "updated": "2026-01-09T21:23:45.188Z", + "reasonDetail": "Maintain last shown raised hands to prevent 0" + }, { "rule": "React-useRef", "path": "ts/components/CallingToast.dom.tsx",