From b359d28771fd8337ae5f22fc4b43325874cb9c33 Mon Sep 17 00:00:00 2001 From: ayumi-signal <143036029+ayumi-signal@users.noreply.github.com> Date: Mon, 18 Mar 2024 12:47:22 -0700 Subject: [PATCH] Fix Timeline to not peek group call for direct calls --- .../conversation/Timeline.stories.tsx | 1 + ts/components/conversation/Timeline.tsx | 66 +++++++++++++------ ts/state/smart/Timeline.tsx | 2 + ts/util/clearTimeoutIfNecessary.ts | 4 ++ 4 files changed, 52 insertions(+), 21 deletions(-) diff --git a/ts/components/conversation/Timeline.stories.tsx b/ts/components/conversation/Timeline.stories.tsx index 0110b47bcb2..af5ba0b4dc5 100644 --- a/ts/components/conversation/Timeline.stories.tsx +++ b/ts/components/conversation/Timeline.stories.tsx @@ -463,6 +463,7 @@ const useProps = (overrideProps: Partial = {}): PropsType => ({ overrideProps.invitedContactsForNewlyCreatedGroup || [], warning: overrideProps.warning, hasContactSpoofingReview: false, + conversationType: 'direct', id: uuid(), renderItem, diff --git a/ts/components/conversation/Timeline.tsx b/ts/components/conversation/Timeline.tsx index bbf191f9782..e805cfd05a2 100644 --- a/ts/components/conversation/Timeline.tsx +++ b/ts/components/conversation/Timeline.tsx @@ -88,6 +88,7 @@ type PropsHousekeepingType = { isSomeoneTyping: boolean; unreadCount?: number; unreadMentionsCount?: number; + conversationType: 'direct' | 'group'; targetedMessageId?: string; invitedContactsForNewlyCreatedGroup: Array; @@ -497,21 +498,8 @@ export class Timeline extends React.Component< } }, 500); - public override componentDidMount(): void { - const containerEl = this.containerRef.current; - const messagesEl = this.messagesRef.current; - const { isConversationSelected } = this.props; - strictAssert( - // We don't render anything unless the conversation is selected - (containerEl && messagesEl) || !isConversationSelected, - ' mounted without some refs' - ); - - this.updateIntersectionObserver(); - - window.SignalContext.activeWindowService.registerForActive( - this.markNewestBottomVisibleMessageRead - ); + private setupGroupCallPeekTimeouts(): void { + this.cleanupGroupCallPeekTimeouts(); this.delayedPeekTimeout = setTimeout(() => { const { id, peekGroupCallForTheFirstTime } = this.props; @@ -525,19 +513,46 @@ export class Timeline extends React.Component< }, MINUTE); } - public override componentWillUnmount(): void { + private cleanupGroupCallPeekTimeouts(): void { const { delayedPeekTimeout, peekInterval } = this; + clearTimeoutIfNecessary(delayedPeekTimeout); + this.delayedPeekTimeout = undefined; + + if (peekInterval) { + clearInterval(peekInterval); + this.peekInterval = undefined; + } + } + + public override componentDidMount(): void { + const containerEl = this.containerRef.current; + const messagesEl = this.messagesRef.current; + const { conversationType, isConversationSelected } = this.props; + strictAssert( + // We don't render anything unless the conversation is selected + (containerEl && messagesEl) || !isConversationSelected, + ' mounted without some refs' + ); + + this.updateIntersectionObserver(); + + window.SignalContext.activeWindowService.registerForActive( + this.markNewestBottomVisibleMessageRead + ); + + if (conversationType === 'group') { + this.setupGroupCallPeekTimeouts(); + } + } + + public override componentWillUnmount(): void { window.SignalContext.activeWindowService.unregisterForActive( this.markNewestBottomVisibleMessageRead ); this.intersectionObserver?.disconnect(); - - clearTimeoutIfNecessary(delayedPeekTimeout); - if (peekInterval) { - clearInterval(peekInterval); - } + this.cleanupGroupCallPeekTimeouts(); } public override getSnapshotBeforeUpdate( @@ -588,11 +603,13 @@ export class Timeline extends React.Component< snapshot: Readonly ): void { const { + conversationType: previousConversationType, items: oldItems, messageChangeCounter: previousMessageChangeCounter, messageLoadingState: previousMessageLoadingState, } = prevProps; const { + conversationType, discardMessages, id, items: newItems, @@ -666,6 +683,13 @@ export class Timeline extends React.Component< if (previousMessageChangeCounter !== messageChangeCounter) { this.markNewestBottomVisibleMessageRead(); } + + if (previousConversationType !== conversationType) { + this.cleanupGroupCallPeekTimeouts(); + if (conversationType === 'group') { + this.setupGroupCallPeekTimeouts(); + } + } } private handleBlur = (event: React.FocusEvent): void => { diff --git a/ts/state/smart/Timeline.tsx b/ts/state/smart/Timeline.tsx index d4a79602726..1a982c4a4b5 100644 --- a/ts/state/smart/Timeline.tsx +++ b/ts/state/smart/Timeline.tsx @@ -210,6 +210,7 @@ export const SmartTimeline = memo(function SmartTimeline({ typingContactIdTimestamps = {}, unreadCount, unreadMentionsCount, + type: conversationType, } = conversation ?? {}; const { haveNewest, @@ -240,6 +241,7 @@ export const SmartTimeline = memo(function SmartTimeline({ } clearTargetedMessage={clearTargetedMessage} closeContactSpoofingReview={closeContactSpoofingReview} + conversationType={conversationType} discardMessages={discardMessages} getPreferredBadge={getPreferredBadge} getTimestampForMessage={getTimestampForMessage} diff --git a/ts/util/clearTimeoutIfNecessary.ts b/ts/util/clearTimeoutIfNecessary.ts index 01e25db230f..1fdbf92908d 100644 --- a/ts/util/clearTimeoutIfNecessary.ts +++ b/ts/util/clearTimeoutIfNecessary.ts @@ -1,6 +1,10 @@ // Copyright 2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only +/** + * Calls clearTimeout on a timeout if it is defined. + * Note: Do not use with intervals (e.g. setInterval). Results may be unexpected. + * */ export function clearTimeoutIfNecessary( timeout: undefined | null | ReturnType ): void {