diff --git a/ts/components/Avatar.dom.stories.tsx b/ts/components/Avatar.dom.stories.tsx index 79c45791c0..45f1be2f37 100644 --- a/ts/components/Avatar.dom.stories.tsx +++ b/ts/components/Avatar.dom.stories.tsx @@ -74,7 +74,6 @@ const createProps = (overrideProps: Partial = {}): Props => ({ onClickBadge: action('onClickBadge'), phoneNumber: overrideProps.phoneNumber || '', searchResult: Boolean(overrideProps.searchResult), - sharedGroupNames: [], size: 80, title: overrideProps.title || '', theme: overrideProps.theme || ThemeType.light, diff --git a/ts/components/Avatar.dom.tsx b/ts/components/Avatar.dom.tsx index becf72fecc..892921a4d7 100644 --- a/ts/components/Avatar.dom.tsx +++ b/ts/components/Avatar.dom.tsx @@ -67,7 +67,6 @@ export type Props = { noteToSelf?: boolean; phoneNumber?: string; profileName?: string; - sharedGroupNames: ReadonlyArray; size: AvatarSize; title: string; searchResult?: boolean; diff --git a/ts/components/CallLinkAddNameModal.dom.tsx b/ts/components/CallLinkAddNameModal.dom.tsx index f355d839bf..7cf1215d55 100644 --- a/ts/components/CallLinkAddNameModal.dom.tsx +++ b/ts/components/CallLinkAddNameModal.dom.tsx @@ -91,7 +91,6 @@ export function CallLinkAddNameModal({ color={getColorForCallLink(callLink.rootKey)} conversationType="callLink" size={AvatarSize.SIXTY_FOUR} - sharedGroupNames={[]} title={ callLink.name === '' ? i18n('icu:calling__call-link-default-title') diff --git a/ts/components/CallLinkDetails.dom.tsx b/ts/components/CallLinkDetails.dom.tsx index 9e89229328..54a8ab6a53 100644 --- a/ts/components/CallLinkDetails.dom.tsx +++ b/ts/components/CallLinkDetails.dom.tsx @@ -111,7 +111,6 @@ export function CallLinkDetails({ color={getColorForCallLink(callLink.rootKey)} conversationType="callLink" size={AvatarSize.SIXTY_FOUR} - sharedGroupNames={[]} title={callLink.name ?? i18n('icu:calling__call-link-default-title')} />
@@ -279,7 +278,6 @@ function renderMissingCallLink({ badge={undefined} conversationType="callLink" size={AvatarSize.SIXTY_FOUR} - sharedGroupNames={[]} title={i18n('icu:calling__call-link-default-title')} />
diff --git a/ts/components/CallLinkEditModal.dom.tsx b/ts/components/CallLinkEditModal.dom.tsx index 9056e310a0..87c276b935 100644 --- a/ts/components/CallLinkEditModal.dom.tsx +++ b/ts/components/CallLinkEditModal.dom.tsx @@ -132,7 +132,6 @@ export function CallLinkEditModal({ color={getColorForCallLink(callLink.rootKey)} conversationType="callLink" size={AvatarSize.SIXTY_FOUR} - sharedGroupNames={[]} title={ callLink.name === '' ? i18n('icu:calling__call-link-default-title') diff --git a/ts/components/CallLinkPendingParticipantModal.dom.stories.tsx b/ts/components/CallLinkPendingParticipantModal.dom.stories.tsx index 49d65352a7..ed71beae87 100644 --- a/ts/components/CallLinkPendingParticipantModal.dom.stories.tsx +++ b/ts/components/CallLinkPendingParticipantModal.dom.stories.tsx @@ -14,11 +14,10 @@ const conversation = getDefaultConversation({ acceptedMessageRequest: true, hasMessages: true, }); -const conversationWithSharedGroups = getDefaultConversation({ +const conversationWithAboutText = getDefaultConversation({ acceptedMessageRequest: true, aboutText: 'likes to chat', hasMessages: true, - sharedGroupNames: ['Axolotl lovers'], }); const systemContact = getDefaultConversation({ acceptedMessageRequest: true, @@ -33,11 +32,11 @@ export default { args: { i18n, conversation, + sharedGroupNames: [], approveUser: action('approveUser'), denyUser: action('denyUser'), toggleAboutContactModal: action('toggleAboutContactModal'), onClose: action('onClose'), - updateSharedGroups: action('updateSharedGroups'), }, } satisfies ComponentMeta; @@ -61,7 +60,8 @@ export function WithSharedGroups( return ( ); } diff --git a/ts/components/CallLinkPendingParticipantModal.dom.tsx b/ts/components/CallLinkPendingParticipantModal.dom.tsx index 92d18e3aaa..aefbc59eb4 100644 --- a/ts/components/CallLinkPendingParticipantModal.dom.tsx +++ b/ts/components/CallLinkPendingParticipantModal.dom.tsx @@ -1,7 +1,7 @@ // Copyright 2024 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -import React, { useCallback, useEffect, useMemo } from 'react'; +import React, { useCallback, useMemo } from 'react'; import { Modal } from './Modal.dom.js'; import type { LocalizerType } from '../types/I18N.std.js'; import { Avatar, AvatarSize } from './Avatar.dom.js'; @@ -20,8 +20,8 @@ export type CallLinkPendingParticipantModalProps = { readonly approveUser: (payload: PendingUserActionPayloadType) => void; readonly denyUser: (payload: PendingUserActionPayloadType) => void; readonly onClose: () => void; + readonly sharedGroupNames: ReadonlyArray; readonly toggleAboutContactModal: (conversationId: string) => void; - readonly updateSharedGroups: (conversationId: string) => void; }; export function CallLinkPendingParticipantModal({ @@ -30,14 +30,9 @@ export function CallLinkPendingParticipantModal({ approveUser, denyUser, onClose, + sharedGroupNames, toggleAboutContactModal, - updateSharedGroups, }: CallLinkPendingParticipantModalProps): React.JSX.Element { - useEffect(() => { - // Kick off the expensive hydration of the current sharedGroupNames - updateSharedGroups(conversation.id); - }, [conversation.id, updateSharedGroups]); - const serviceId = useMemo(() => { return conversation.serviceId; }, [conversation]); @@ -70,7 +65,6 @@ export function CallLinkPendingParticipantModal({ hasAvatar={conversation.hasAvatar} i18n={i18n} profileName={conversation.profileName} - sharedGroupNames={conversation.sharedGroupNames} size={AvatarSize.EIGHTY} title={conversation.title} theme={ThemeType.dark} @@ -101,11 +95,8 @@ export function CallLinkPendingParticipantModal({
- {conversation.sharedGroupNames?.length ? ( - + {sharedGroupNames.length > 0 ? ( + ) : ( i18n('icu:no-groups-in-common-warning') )} diff --git a/ts/components/CallManager.dom.stories.tsx b/ts/components/CallManager.dom.stories.tsx index ae9ee7f83e..ee4ce116da 100644 --- a/ts/components/CallManager.dom.stories.tsx +++ b/ts/components/CallManager.dom.stories.tsx @@ -57,7 +57,6 @@ const placeHolderContact: ConversationType = { type: 'direct', title: i18n('icu:unknownContact'), isMe: false, - sharedGroupNames: [], }; const getUnknownContact = (): ConversationType => ({ diff --git a/ts/components/CallNeedPermissionScreen.dom.tsx b/ts/components/CallNeedPermissionScreen.dom.tsx index 07e0fef056..d0cca1423d 100644 --- a/ts/components/CallNeedPermissionScreen.dom.tsx +++ b/ts/components/CallNeedPermissionScreen.dom.tsx @@ -21,7 +21,6 @@ export type Props = { | 'name' | 'phoneNumber' | 'profileName' - | 'sharedGroupNames' | 'title' >; i18n: LocalizerType; @@ -57,7 +56,6 @@ export function CallNeedPermissionScreen({ phoneNumber={conversation.phoneNumber} profileName={conversation.profileName} title={conversation.title} - sharedGroupNames={conversation.sharedGroupNames} size={AvatarSize.EIGHTY} /> diff --git a/ts/components/CallScreen.dom.tsx b/ts/components/CallScreen.dom.tsx index 4113fe73b1..55d35a1c87 100644 --- a/ts/components/CallScreen.dom.tsx +++ b/ts/components/CallScreen.dom.tsx @@ -668,8 +668,6 @@ export function CallScreen({ phoneNumber={me.phoneNumber} profileName={me.profileName} title={me.title} - // See comment above about `sharedGroupNames`. - sharedGroupNames={[]} size={AvatarSize.FORTY} /> @@ -978,7 +976,6 @@ export function CallScreen({ profileName={conversation.profileName} title={conversation.title} size={AvatarSize.EIGHTY} - sharedGroupNames={conversation.sharedGroupNames} />
diff --git a/ts/components/CallingAdhocCallInfo.dom.tsx b/ts/components/CallingAdhocCallInfo.dom.tsx index 8eeb6deda0..bc97915f60 100644 --- a/ts/components/CallingAdhocCallInfo.dom.tsx +++ b/ts/components/CallingAdhocCallInfo.dom.tsx @@ -91,7 +91,6 @@ function UnknownContacts({ i18n={i18n} profileName={participant.profileName} title={participant.title} - sharedGroupNames={participant.sharedGroupNames} size={size} /> ); @@ -219,7 +218,6 @@ export function CallingAdhocCallInfo({ i18n={i18n} profileName={participant.profileName} title={participant.title} - sharedGroupNames={participant.sharedGroupNames} size={AvatarSize.THIRTY_SIX} /> {ourServiceId && participant.serviceId === ourServiceId ? ( diff --git a/ts/components/CallingLobby.dom.tsx b/ts/components/CallingLobby.dom.tsx index 083f6bc0c9..811c5a08d1 100644 --- a/ts/components/CallingLobby.dom.tsx +++ b/ts/components/CallingLobby.dom.tsx @@ -45,7 +45,6 @@ export type PropsType = { | 'name' | 'phoneNumber' | 'profileName' - | 'sharedGroupNames' | 'systemGivenName' | 'systemNickname' | 'title' diff --git a/ts/components/CallingParticipantsList.dom.tsx b/ts/components/CallingParticipantsList.dom.tsx index 5d4895722e..e7073cda48 100644 --- a/ts/components/CallingParticipantsList.dom.tsx +++ b/ts/components/CallingParticipantsList.dom.tsx @@ -135,7 +135,6 @@ export const CallingParticipantsList = React.memo( i18n={i18n} profileName={participant.profileName} title={participant.title} - sharedGroupNames={participant.sharedGroupNames} size={AvatarSize.THIRTY_TWO} /> {ourServiceId && diff --git a/ts/components/CallingPendingParticipants.dom.tsx b/ts/components/CallingPendingParticipants.dom.tsx index 62a16dc632..33104c9513 100644 --- a/ts/components/CallingPendingParticipants.dom.tsx +++ b/ts/components/CallingPendingParticipants.dom.tsx @@ -320,7 +320,6 @@ export function CallingPendingParticipants({ i18n={i18n} profileName={participant.profileName} title={participant.title} - sharedGroupNames={participant.sharedGroupNames} size={AvatarSize.THIRTY_SIX} />
diff --git a/ts/components/CallingPip.dom.tsx b/ts/components/CallingPip.dom.tsx index 1344b0501e..77c9f45793 100644 --- a/ts/components/CallingPip.dom.tsx +++ b/ts/components/CallingPip.dom.tsx @@ -83,7 +83,6 @@ export type PropsType = { | 'phoneNumber' | 'profileName' | 'title' - | 'sharedGroupNames' > >; setGroupCallVideoRequest: ( @@ -535,7 +534,6 @@ export function CallingPip({ profileName={me.profileName} title={me.title} size={avatarSize} - sharedGroupNames={[]} />
diff --git a/ts/components/CallingPipRemoteVideo.dom.tsx b/ts/components/CallingPipRemoteVideo.dom.tsx index 9cf6058c21..e372a67e8c 100644 --- a/ts/components/CallingPipRemoteVideo.dom.tsx +++ b/ts/components/CallingPipRemoteVideo.dom.tsx @@ -54,7 +54,6 @@ function BlurredBackground({ type: conversationType, phoneNumber, profileName, - sharedGroupNames, title, } = activeCall.conversation; const avatarUrl = @@ -75,7 +74,6 @@ function BlurredBackground({ profileName={profileName} title={title} size={avatarSize} - sharedGroupNames={sharedGroupNames} />
diff --git a/ts/components/CallingPreCallInfo.dom.stories.tsx b/ts/components/CallingPreCallInfo.dom.stories.tsx index 7442f68ac4..93d385b287 100644 --- a/ts/components/CallingPreCallInfo.dom.stories.tsx +++ b/ts/components/CallingPreCallInfo.dom.stories.tsx @@ -32,7 +32,6 @@ const getUnknownContact = (): ConversationType => ({ type: 'direct', title: i18n('icu:unknownContact'), isMe: false, - sharedGroupNames: [], serviceId: generateAci(), }); diff --git a/ts/components/CallingPreCallInfo.dom.tsx b/ts/components/CallingPreCallInfo.dom.tsx index 7e1cc1ddf6..8723bec29c 100644 --- a/ts/components/CallingPreCallInfo.dom.tsx +++ b/ts/components/CallingPreCallInfo.dom.tsx @@ -40,7 +40,6 @@ export type PropsType = { | 'isMe' | 'phoneNumber' | 'profileName' - | 'sharedGroupNames' | 'systemGivenName' | 'systemNickname' | 'title' @@ -228,7 +227,6 @@ export function CallingPreCallInfo({ noteToSelf={false} phoneNumber={conversation.phoneNumber} profileName={conversation.profileName} - sharedGroupNames={conversation.sharedGroupNames} size={AvatarSize.SIXTY_FOUR} title={conversation.title} i18n={i18n} diff --git a/ts/components/CallingRaisedHandsList.dom.tsx b/ts/components/CallingRaisedHandsList.dom.tsx index 445a045eff..bb9410515e 100644 --- a/ts/components/CallingRaisedHandsList.dom.tsx +++ b/ts/components/CallingRaisedHandsList.dom.tsx @@ -113,7 +113,6 @@ export function CallingRaisedHandsList({ i18n={i18n} profileName={participant.profileName} title={participant.title} - sharedGroupNames={participant.sharedGroupNames} size={AvatarSize.THIRTY_TWO} /> {ourServiceId && participant.serviceId === ourServiceId ? ( diff --git a/ts/components/CallsList.preload.tsx b/ts/components/CallsList.preload.tsx index c9737c3c27..67c2abcfbd 100644 --- a/ts/components/CallsList.preload.tsx +++ b/ts/components/CallsList.preload.tsx @@ -915,7 +915,6 @@ export function CallsList({ hasAvatar={conversation.hasAvatar} i18n={i18n} title={conversation.title} - sharedGroupNames={[]} size={AvatarSize.THIRTY_SIX} badge={undefined} className="CallsList__ItemAvatar" diff --git a/ts/components/CallsNewCallButton.dom.tsx b/ts/components/CallsNewCallButton.dom.tsx index 063a5c07e6..4c5a509859 100644 --- a/ts/components/CallsNewCallButton.dom.tsx +++ b/ts/components/CallsNewCallButton.dom.tsx @@ -226,7 +226,6 @@ export function CallsNewCall({ conversationType="group" i18n={i18n} title={item.conversation.title} - sharedGroupNames={[]} size={AvatarSize.THIRTY_TWO} badge={undefined} /> diff --git a/ts/components/CompositionArea.dom.stories.tsx b/ts/components/CompositionArea.dom.stories.tsx index 4b094012d1..c984552bf6 100644 --- a/ts/components/CompositionArea.dom.stories.tsx +++ b/ts/components/CompositionArea.dom.stories.tsx @@ -95,6 +95,7 @@ export default { blockAndReportSpam: action('blockAndReportSpam'), deleteConversation: action('deleteConversation'), conversationName: getDefaultConversation(), + getSharedGroupNames: () => [], // GroupV1 Disabled Actions showGV2MigrationDialog: action('showGV2MigrationDialog'), // GroupV2 diff --git a/ts/components/CompositionArea.dom.tsx b/ts/components/CompositionArea.dom.tsx index c5b13791ab..df9ec13d61 100644 --- a/ts/components/CompositionArea.dom.tsx +++ b/ts/components/CompositionArea.dom.tsx @@ -42,6 +42,7 @@ import type { ShowConversationType, } from '../state/ducks/conversations.preload.js'; import type { GetConversationByIdType } from '../state/selectors/conversations.dom.js'; +import type { GetSharedGroupNamesType } from '../util/sharedGroupNames.dom.js'; import type { LinkPreviewForUIType } from '../types/message/LinkPreviews.std.js'; import { isSameLinkPreview } from '../types/message/LinkPreviews.std.js'; @@ -94,7 +95,7 @@ export type OwnProps = Readonly<{ areWeAdmin: boolean | null; areWePending: boolean | null; areWePendingApproval: boolean | null; - sharedGroupNames?: ReadonlyArray; + getSharedGroupNames: GetSharedGroupNamesType; cancelRecording: () => unknown; completeRecording: ( conversationId: string, @@ -298,6 +299,7 @@ export const CompositionArea = memo(function CompositionArea({ areWePending, areWePendingApproval, conversationType, + getSharedGroupNames, groupVersion, isBlocked, isHidden, @@ -324,7 +326,6 @@ export const CompositionArea = memo(function CompositionArea({ // SMS-only contacts isSmsOnlyOrUnregistered, isFetchingUUID, - sharedGroupNames, renderSmartCompositionRecording, renderSmartCompositionRecordingDraft, // Selected messages @@ -911,11 +912,11 @@ export const CompositionArea = memo(function CompositionArea({ conversationType={conversationType} conversationId={conversationId} conversationName={conversationName} + getSharedGroupNames={getSharedGroupNames} i18n={i18n} isBlocked={isBlocked} isHidden={isHidden} isReported={isReported} - sharedGroupNames={sharedGroupNames} acceptConversation={acceptConversation} reportSpam={reportSpam} blockAndReportSpam={blockAndReportSpam} diff --git a/ts/components/ContactPill.dom.tsx b/ts/components/ContactPill.dom.tsx index 98b1acb9a1..eb46f00237 100644 --- a/ts/components/ContactPill.dom.tsx +++ b/ts/components/ContactPill.dom.tsx @@ -23,7 +23,6 @@ export type PropsType = { | 'isMe' | 'phoneNumber' | 'profileName' - | 'sharedGroupNames' | 'title' >; @@ -37,7 +36,6 @@ export function ContactPill({ id, phoneNumber, profileName, - sharedGroupNames, title, onClickRemove, }: PropsType): React.JSX.Element { @@ -57,7 +55,6 @@ export function ContactPill({ phoneNumber={phoneNumber} profileName={profileName} title={title} - sharedGroupNames={sharedGroupNames} size={AvatarSize.TWENTY} /> ): React.JSX.Element { @@ -91,7 +89,6 @@ function renderAvatar( phoneNumber={phoneNumber} profileName={profileName} title={title} - sharedGroupNames={sharedGroupNames} size={AvatarSize.NINETY_SIX} /> diff --git a/ts/components/GroupCallRemoteParticipant.dom.tsx b/ts/components/GroupCallRemoteParticipant.dom.tsx index cd3295ddbe..e66480244c 100644 --- a/ts/components/GroupCallRemoteParticipant.dom.tsx +++ b/ts/components/GroupCallRemoteParticipant.dom.tsx @@ -104,7 +104,6 @@ export const GroupCallRemoteParticipant: React.FC = React.memo( isBlocked, mediaKeysReceived, profileName, - sharedGroupNames, sharingScreen, title, titleNoDefault, @@ -474,7 +473,6 @@ export const GroupCallRemoteParticipant: React.FC = React.memo( i18n={i18n} profileName={profileName} title={title} - sharedGroupNames={sharedGroupNames} size={avatarSize} /> ); diff --git a/ts/components/GroupDialog.dom.tsx b/ts/components/GroupDialog.dom.tsx index 7874db84b7..75f0d80c95 100644 --- a/ts/components/GroupDialog.dom.tsx +++ b/ts/components/GroupDialog.dom.tsx @@ -120,7 +120,6 @@ function Contacts({ noteToSelf={contact.isMe} theme={theme} title={contact.title} - sharedGroupNames={contact.sharedGroupNames} size={AvatarSize.TWENTY_EIGHT} i18n={i18n} /> diff --git a/ts/components/GroupV2JoinDialog.dom.tsx b/ts/components/GroupV2JoinDialog.dom.tsx index 2ec452ee47..0e0fca1eb6 100644 --- a/ts/components/GroupV2JoinDialog.dom.tsx +++ b/ts/components/GroupV2JoinDialog.dom.tsx @@ -75,7 +75,6 @@ export const GroupV2JoinDialog = React.memo(function GroupV2JoinDialogInner({ loading={avatar && !avatar.url} conversationType="group" title={title} - sharedGroupNames={[]} size={80} i18n={i18n} /> diff --git a/ts/components/IncomingCallBar.dom.tsx b/ts/components/IncomingCallBar.dom.tsx index a81ecaaff9..694d06d833 100644 --- a/ts/components/IncomingCallBar.dom.tsx +++ b/ts/components/IncomingCallBar.dom.tsx @@ -38,7 +38,6 @@ export type PropsType = { | 'name' | 'phoneNumber' | 'profileName' - | 'sharedGroupNames' | 'title' | 'type' >; @@ -200,7 +199,6 @@ export function IncomingCallBar(props: PropsType): React.JSX.Element | null { color, phoneNumber, profileName, - sharedGroupNames, title, type: conversationType, } = conversation; @@ -284,7 +282,6 @@ export function IncomingCallBar(props: PropsType): React.JSX.Element | null { phoneNumber={phoneNumber} profileName={profileName} title={title} - sharedGroupNames={sharedGroupNames} size={AvatarSize.FORTY_EIGHT} />
diff --git a/ts/components/LeftPaneSearchInput.dom.tsx b/ts/components/LeftPaneSearchInput.dom.tsx index 7b7f2348ec..a86080d08b 100644 --- a/ts/components/LeftPaneSearchInput.dom.tsx +++ b/ts/components/LeftPaneSearchInput.dom.tsx @@ -182,7 +182,6 @@ export function LeftPaneSearchInput({ hasAvatar={searchConversation.hasAvatar} i18n={i18n} noteToSelf={searchConversation.isMe} - sharedGroupNames={searchConversation.sharedGroupNames} size={AvatarSize.TWENTY} title={searchConversation.title} /> diff --git a/ts/components/Lightbox.dom.tsx b/ts/components/Lightbox.dom.tsx index 5c175ef8d5..2a0ff91f09 100644 --- a/ts/components/Lightbox.dom.tsx +++ b/ts/components/Lightbox.dom.tsx @@ -899,7 +899,6 @@ function LightboxHeader({ i18n={i18n} phoneNumber={conversation.e164} profileName={conversation.profileName} - sharedGroupNames={conversation.sharedGroupNames} size={AvatarSize.THIRTY_TWO} title={conversation.title} /> diff --git a/ts/components/MyStoryButton.dom.tsx b/ts/components/MyStoryButton.dom.tsx index 41c69a8101..9e615e332a 100644 --- a/ts/components/MyStoryButton.dom.tsx +++ b/ts/components/MyStoryButton.dom.tsx @@ -48,7 +48,7 @@ export function MyStoryButton({ ? getNewestMyStory(myStories[0]) : undefined; - const { avatarUrl, color, profileName, sharedGroupNames, title } = me; + const { avatarUrl, color, profileName, title } = me; if (!newestStory) { return ( @@ -67,7 +67,6 @@ export function MyStoryButton({ conversationType="direct" i18n={i18n} profileName={profileName} - sharedGroupNames={sharedGroupNames} size={AvatarSize.FORTY_EIGHT} title={title} /> @@ -118,7 +117,6 @@ export function MyStoryButton({ conversationType="direct" i18n={i18n} profileName={profileName} - sharedGroupNames={sharedGroupNames} size={AvatarSize.FORTY_EIGHT} storyRing={HasStories.Read} title={title} diff --git a/ts/components/NavTabs.dom.tsx b/ts/components/NavTabs.dom.tsx index b22ed55d7a..6fe8577142 100644 --- a/ts/components/NavTabs.dom.tsx +++ b/ts/components/NavTabs.dom.tsx @@ -372,9 +372,6 @@ export function NavTabs({ profileName={me.profileName} theme={theme} title={me.title} - // `sharedGroupNames` makes no sense for yourself, but - // `` needs it to determine blurring. - sharedGroupNames={[]} size={AvatarSize.TWENTY_EIGHT} /> diff --git a/ts/components/Preferences.dom.tsx b/ts/components/Preferences.dom.tsx index f89dcfce91..f025cc2221 100644 --- a/ts/components/Preferences.dom.tsx +++ b/ts/components/Preferences.dom.tsx @@ -2351,9 +2351,6 @@ export function Preferences({ profileName={me.profileName} theme={theme} title={me.title} - // `sharedGroupNames` makes no sense for yourself, but - // `` needs it to determine blurring. - sharedGroupNames={[]} size={AvatarSize.FORTY_EIGHT} /> diff --git a/ts/components/PreferencesDonations.dom.tsx b/ts/components/PreferencesDonations.dom.tsx index c410c67f21..59e97d0b76 100644 --- a/ts/components/PreferencesDonations.dom.tsx +++ b/ts/components/PreferencesDonations.dom.tsx @@ -183,7 +183,6 @@ function DonationHero({ conversationType="direct" title={firstName ?? ''} i18n={i18n} - sharedGroupNames={[]} size={AvatarSize.SEVENTY_TWO} theme={theme} /> diff --git a/ts/components/SafetyNumberChangeDialog.dom.tsx b/ts/components/SafetyNumberChangeDialog.dom.tsx index 067ccd69f4..c143f82799 100644 --- a/ts/components/SafetyNumberChangeDialog.dom.tsx +++ b/ts/components/SafetyNumberChangeDialog.dom.tsx @@ -457,7 +457,6 @@ function ContactRow({ profileName={contact.profileName} theme={theme} title={contact.title} - sharedGroupNames={contact.sharedGroupNames} size={AvatarSize.THIRTY_TWO} />
diff --git a/ts/components/SendStoryModal.dom.tsx b/ts/components/SendStoryModal.dom.tsx index 99ae82f004..790930d18c 100644 --- a/ts/components/SendStoryModal.dom.tsx +++ b/ts/components/SendStoryModal.dom.tsx @@ -576,7 +576,6 @@ export function SendStoryModal({ color={group.color} conversationType={group.type} i18n={i18n} - sharedGroupNames={[]} size={AvatarSize.THIRTY_TWO} title={group.title} /> @@ -724,7 +723,6 @@ export function SendStoryModal({ color={me.color} conversationType={me.type} i18n={i18n} - sharedGroupNames={me.sharedGroupNames} size={AvatarSize.THIRTY_TWO} storyRing={undefined} title={me.title} @@ -837,7 +835,6 @@ export function SendStoryModal({ color={group.color} conversationType={group.type} i18n={i18n} - sharedGroupNames={[]} size={AvatarSize.THIRTY_TWO} storyRing={group.hasStories} title={group.title} diff --git a/ts/components/StoriesSettingsModal.dom.tsx b/ts/components/StoriesSettingsModal.dom.tsx index f62d7467fd..827b97258d 100644 --- a/ts/components/StoriesSettingsModal.dom.tsx +++ b/ts/components/StoriesSettingsModal.dom.tsx @@ -177,7 +177,6 @@ function DistributionListItem({ color={me.color} conversationType={me.type} i18n={i18n} - sharedGroupNames={me.sharedGroupNames} size={AvatarSize.THIRTY_TWO} title={me.title} /> @@ -230,7 +229,6 @@ function GroupStoryItem({ color={groupStory.color} conversationType={groupStory.type} i18n={i18n} - sharedGroupNames={[]} size={AvatarSize.THIRTY_TWO} title={groupStory.title} /> @@ -697,7 +695,6 @@ export function DistributionListSettingsModal({ color={member.color} conversationType={member.type} i18n={i18n} - sharedGroupNames={member.sharedGroupNames} size={AvatarSize.THIRTY_TWO} theme={theme} title={member.title} @@ -1115,7 +1112,6 @@ export function EditDistributionListModal({ color={contact.color} conversationType={contact.type} i18n={i18n} - sharedGroupNames={contact.sharedGroupNames} size={AvatarSize.THIRTY_TWO} theme={theme} title={contact.title} @@ -1213,7 +1209,6 @@ export function EditDistributionListModal({ isMe={contact.isMe} phoneNumber={contact.phoneNumber} profileName={contact.profileName} - sharedGroupNames={contact.sharedGroupNames} title={contact.title} onClickRemove={() => toggleSelectedConversation(contact.id)} /> @@ -1307,7 +1302,6 @@ export function GroupStorySettingsModal({ color={group.color} conversationType={group.type} i18n={i18n} - sharedGroupNames={[]} size={AvatarSize.THIRTY_TWO} title={group.title} /> @@ -1335,7 +1329,6 @@ export function GroupStorySettingsModal({ color={member.color} conversationType={member.type} i18n={i18n} - sharedGroupNames={[]} size={AvatarSize.THIRTY_TWO} title={member.title} /> diff --git a/ts/components/StoryDetailsModal.dom.tsx b/ts/components/StoryDetailsModal.dom.tsx index e8e139797f..94ce44e2a3 100644 --- a/ts/components/StoryDetailsModal.dom.tsx +++ b/ts/components/StoryDetailsModal.dom.tsx @@ -138,7 +138,6 @@ export function StoryDetailsModal({ i18n={i18n} phoneNumber={contact.phoneNumber} profileName={contact.profileName} - sharedGroupNames={contact.sharedGroupNames} size={AvatarSize.THIRTY_TWO} theme={ThemeType.dark} title={contact.title} @@ -178,7 +177,6 @@ export function StoryDetailsModal({ conversationType="direct" i18n={i18n} profileName={sender.profileName} - sharedGroupNames={sender.sharedGroupNames} size={AvatarSize.THIRTY_TWO} theme={ThemeType.dark} title={sender.title} diff --git a/ts/components/StoryListItem.dom.tsx b/ts/components/StoryListItem.dom.tsx index 019a067f51..3bbd322615 100644 --- a/ts/components/StoryListItem.dom.tsx +++ b/ts/components/StoryListItem.dom.tsx @@ -45,17 +45,11 @@ function StoryListItemAvatar({ getPreferredBadge, i18n, profileName, - sharedGroupNames, title, theme, }: Pick< ConversationType, - | 'avatarPlaceholderGradient' - | 'avatarUrl' - | 'color' - | 'profileName' - | 'sharedGroupNames' - | 'title' + 'avatarPlaceholderGradient' | 'avatarUrl' | 'color' | 'profileName' | 'title' > & { avatarStoryRing?: HasStories; badges?: ConversationType['badges']; @@ -72,7 +66,6 @@ function StoryListItemAvatar({ conversationType="direct" i18n={i18n} profileName={profileName} - sharedGroupNames={sharedGroupNames} size={AvatarSize.FORTY_EIGHT} storyRing={avatarStoryRing} theme={theme} diff --git a/ts/components/StoryViewer.dom.tsx b/ts/components/StoryViewer.dom.tsx index 06d4f72a43..7a4073b9ba 100644 --- a/ts/components/StoryViewer.dom.tsx +++ b/ts/components/StoryViewer.dom.tsx @@ -75,7 +75,6 @@ export type PropsType = { | 'id' | 'name' | 'profileName' - | 'sharedGroupNames' | 'sortedGroupMembers' | 'title' | 'left' @@ -192,15 +191,8 @@ export function StoryViewer({ sendState, timestamp, } = story; - const { - avatarUrl, - color, - isMe, - firstName, - profileName, - sharedGroupNames, - title, - } = story.sender; + const { avatarUrl, color, isMe, firstName, profileName, title } = + story.sender; const conversationId = group?.id || story.sender.id; @@ -729,7 +721,6 @@ export function StoryViewer({ conversationType="direct" i18n={i18n} profileName={profileName} - sharedGroupNames={sharedGroupNames} size={AvatarSize.TWENTY_EIGHT} title={title} /> @@ -742,7 +733,6 @@ export function StoryViewer({ conversationType="group" i18n={i18n} profileName={group.profileName} - sharedGroupNames={group.sharedGroupNames} size={AvatarSize.TWENTY_EIGHT} title={group.title} /> diff --git a/ts/components/StoryViewsNRepliesModal.dom.tsx b/ts/components/StoryViewsNRepliesModal.dom.tsx index 7264f8b7c0..0ed32b21d7 100644 --- a/ts/components/StoryViewsNRepliesModal.dom.tsx +++ b/ts/components/StoryViewsNRepliesModal.dom.tsx @@ -399,7 +399,6 @@ export function StoryViewsNRepliesModal({ conversationType="direct" i18n={i18n} profileName={view.recipient.profileName} - sharedGroupNames={view.recipient.sharedGroupNames || []} size={AvatarSize.TWENTY_EIGHT} title={view.recipient.title} /> @@ -631,7 +630,6 @@ function ReplyOrReactionMessage({ conversationType="direct" i18n={i18n} profileName={reply.author.profileName} - sharedGroupNames={reply.author.sharedGroupNames || []} size={AvatarSize.TWENTY_EIGHT} theme={ThemeType.dark} title={reply.author.title} diff --git a/ts/components/conversation/AboutContactModal.dom.stories.tsx b/ts/components/conversation/AboutContactModal.dom.stories.tsx index 26997caa75..58c2846f55 100644 --- a/ts/components/conversation/AboutContactModal.dom.stories.tsx +++ b/ts/components/conversation/AboutContactModal.dom.stories.tsx @@ -40,7 +40,6 @@ const conversationWithSharedGroups = getDefaultConversation({ acceptedMessageRequest: true, aboutText: 'likes to chat', hasMessages: true, - sharedGroupNames: ['Axolotl lovers'], }); const systemContact = getDefaultConversation({ acceptedMessageRequest: true, @@ -67,10 +66,10 @@ export default { toggleSignalConnectionsModal: action('toggleSignalConnections'), toggleSafetyNumberModal: action('toggleSafetyNumberModal'), toggleProfileNameWarningModal: action('toggleProfileNameWarningModal'), - updateSharedGroups: action('updateSharedGroups'), startAvatarDownload: action('startAvatarDownload'), pendingAvatarDownload: false, conversation, + sharedGroupNames: [], fromOrAddedByTrustedContact: false, isSignalConnection: false, }, @@ -123,6 +122,7 @@ export function WithSharedGroups(args: PropsType): React.JSX.Element { ); diff --git a/ts/components/conversation/AboutContactModal.dom.tsx b/ts/components/conversation/AboutContactModal.dom.tsx index 4db2025ab8..a9d06efd16 100644 --- a/ts/components/conversation/AboutContactModal.dom.tsx +++ b/ts/components/conversation/AboutContactModal.dom.tsx @@ -1,7 +1,7 @@ // Copyright 2024 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -import React, { type ReactNode, useCallback, useEffect, useMemo } from 'react'; +import React, { type ReactNode, useCallback, useMemo } from 'react'; import type { ConversationType } from '../../state/ducks/conversations.preload.js'; import type { LocalizerType } from '../../types/Util.std.js'; import { isInSystemContacts } from '../../util/isInSystemContacts.std.js'; @@ -28,11 +28,11 @@ export type PropsType = Readonly<{ fromOrAddedByTrustedContact?: boolean; isSignalConnection: boolean; pendingAvatarDownload?: boolean; + sharedGroupNames: ReadonlyArray; startAvatarDownload?: (id: string) => unknown; toggleSignalConnectionsModal: () => void; toggleSafetyNumberModal: (id: string) => void; toggleProfileNameWarningModal: () => void; - updateSharedGroups: (id: string) => void; }>; export function AboutContactModal({ @@ -41,21 +41,16 @@ export function AboutContactModal({ fromOrAddedByTrustedContact, isSignalConnection, pendingAvatarDownload, + sharedGroupNames, startAvatarDownload, toggleSignalConnectionsModal, toggleSafetyNumberModal, toggleProfileNameWarningModal, - updateSharedGroups, onClose, onOpenNotePreviewModal, }: PropsType): React.JSX.Element { const { avatarUrl, hasAvatar, isMe } = conversation; - useEffect(() => { - // Kick off the expensive hydration of the current sharedGroupNames - updateSharedGroups(conversation.id); - }, [conversation.id, updateSharedGroups]); - // If hasAvatar is true, we show the download button instead of blur const enableClickToLoad = !avatarUrl && !isMe && hasAvatar; @@ -156,7 +151,6 @@ export function AboutContactModal({ i18n={i18n} loading={pendingAvatarDownload && !conversation.avatarUrl} profileName={conversation.profileName} - sharedGroupNames={[]} size={AvatarSize.TWO_HUNDRED_SIXTEEN} title={conversation.title} /> @@ -296,10 +290,7 @@ export function AboutContactModal({
- +
)} diff --git a/ts/components/conversation/ContactModal.dom.stories.tsx b/ts/components/conversation/ContactModal.dom.stories.tsx index 0f17b0e0f3..afa2de0d1c 100644 --- a/ts/components/conversation/ContactModal.dom.stories.tsx +++ b/ts/components/conversation/ContactModal.dom.stories.tsx @@ -58,9 +58,6 @@ export default { toggleAboutContactModal: action('AboutContactModal'), toggleAdmin: action('toggleAdmin'), toggleSafetyNumberModal: action('toggleSafetyNumberModal'), - updateConversationModelSharedGroups: action( - 'updateConversationModelSharedGroups' - ), viewUserStories: action('viewUserStories'), }, } satisfies Meta; diff --git a/ts/components/conversation/ContactModal.dom.tsx b/ts/components/conversation/ContactModal.dom.tsx index 299f68199a..f275442912 100644 --- a/ts/components/conversation/ContactModal.dom.tsx +++ b/ts/components/conversation/ContactModal.dom.tsx @@ -1,7 +1,7 @@ // Copyright 2020 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -import React, { useEffect, useState } from 'react'; +import React, { useState } from 'react'; import type { ReactNode } from 'react'; import type { @@ -63,7 +63,6 @@ type PropsActionType = { togglePip: () => void; toggleSafetyNumberModal: (conversationId: string) => unknown; toggleAddUserToAnotherGroupModal: (conversationId: string) => void; - updateConversationModelSharedGroups: (conversationId: string) => void; viewUserStories: ViewUserStoriesActionCreatorType; }; @@ -108,7 +107,6 @@ export function ContactModal({ toggleAdmin, togglePip, toggleSafetyNumberModal, - updateConversationModelSharedGroups, viewUserStories, }: PropsType): React.JSX.Element { if (!contact) { @@ -121,13 +119,6 @@ export function ContactModal({ ); const modalTheme = getThemeByThemeType(theme); - useEffect(() => { - if (contact?.id) { - // Kick off the expensive hydration of the current sharedGroupNames - updateConversationModelSharedGroups(contact.id); - } - }, [contact?.id, updateConversationModelSharedGroups]); - const renderQuickActions = React.useCallback( (conversationId: string) => { const inAnotherCallTooltipContent = hasActiveCall @@ -343,7 +334,6 @@ export function ContactModal({ }} onClickBadge={() => setView(ContactModalView.ShowingBadges)} profileName={contact.profileName} - sharedGroupNames={contact.sharedGroupNames} size={AvatarSize.EIGHTY} storyRing={hasStories} theme={theme} diff --git a/ts/components/conversation/ContactSpoofingReviewDialog.dom.stories.tsx b/ts/components/conversation/ContactSpoofingReviewDialog.dom.stories.tsx index c201a15171..3377575eee 100644 --- a/ts/components/conversation/ContactSpoofingReviewDialog.dom.stories.tsx +++ b/ts/components/conversation/ContactSpoofingReviewDialog.dom.stories.tsx @@ -32,7 +32,6 @@ const getCommonProps = () => ({ onClose: action('onClose'), showContactModal: action('showContactModal'), toggleSignalConnectionsModal: action('toggleSignalConnectionsModal'), - updateSharedGroups: action('updateSharedGroups'), removeMember: action('removeMember'), theme: ThemeType.light, }); @@ -45,10 +44,12 @@ export function DirectConversationsWithSameTitle(): React.JSX.Element { possiblyUnsafe={{ conversation: getDefaultConversation(), isSignalConnection: false, + sharedGroupNames: [], }} safe={{ conversation: getDefaultConversation(), isSignalConnection: true, + sharedGroupNames: [], }} /> ); @@ -68,14 +69,17 @@ export function NotAdminMany(): React.JSX.Element { oldName: 'Alicia', isSignalConnection: false, conversation: getDefaultConversation({ title: 'Alice' }), + sharedGroupNames: [], })), Bob: times(3, () => ({ isSignalConnection: false, conversation: getDefaultConversation({ title: 'Bob' }), + sharedGroupNames: [], })), Charlie: times(5, () => ({ isSignalConnection: false, conversation: getDefaultConversation({ title: 'Charlie' }), + sharedGroupNames: [], })), }} /> @@ -97,11 +101,13 @@ export function NotAdminOne(): React.JSX.Element { oldName: 'Alicia', isSignalConnection: false, conversation: getDefaultConversation({ title: 'Alice' }), + sharedGroupNames: [], }, { oldName: 'Alice', isSignalConnection: true, conversation: getDefaultConversation({ title: 'Alice' }), + sharedGroupNames: [], }, ], }} @@ -123,14 +129,17 @@ export function AdminMany(): React.JSX.Element { oldName: 'Alicia', isSignalConnection: false, conversation: getDefaultConversation({ title: 'Alice' }), + sharedGroupNames: [], })), Bob: times(3, () => ({ isSignalConnection: false, conversation: getDefaultConversation({ title: 'Bob' }), + sharedGroupNames: [], })), Charlie: times(5, () => ({ isSignalConnection: false, conversation: getDefaultConversation({ title: 'Charlie' }), + sharedGroupNames: [], })), }} /> @@ -152,10 +161,12 @@ export function AdminOne(): React.JSX.Element { oldName: 'Alicia', isSignalConnection: false, conversation: getDefaultConversation({ title: 'Alice' }), + sharedGroupNames: [], }, { isSignalConnection: true, conversation: getDefaultConversation({ title: 'Alice' }), + sharedGroupNames: [], }, ], }} diff --git a/ts/components/conversation/ContactSpoofingReviewDialog.dom.tsx b/ts/components/conversation/ContactSpoofingReviewDialog.dom.tsx index 34adb2d0b6..f93e693f59 100644 --- a/ts/components/conversation/ContactSpoofingReviewDialog.dom.tsx +++ b/ts/components/conversation/ContactSpoofingReviewDialog.dom.tsx @@ -27,10 +27,12 @@ export type ReviewPropsType = Readonly< possiblyUnsafe: { conversation: ConversationType; isSignalConnection: boolean; + sharedGroupNames: ReadonlyArray; }; safe: { conversation: ConversationType; isSignalConnection: boolean; + sharedGroupNames: ReadonlyArray; }; } | { @@ -42,6 +44,7 @@ export type ReviewPropsType = Readonly< oldName?: string; isSignalConnection: boolean; conversation: ConversationType; + sharedGroupNames: ReadonlyArray; }> >; } @@ -55,7 +58,6 @@ export type PropsType = { blockConversation: (conversationId: string) => unknown; deleteConversation: (conversationId: string) => unknown; toggleSignalConnectionsModal: () => void; - updateSharedGroups: (conversationId: string) => void; getPreferredBadge: PreferredBadgeSelectorType; i18n: LocalizerType; onClose: () => void; @@ -84,7 +86,6 @@ export function ContactSpoofingReviewDialog( conversationId, deleteConversation, toggleSignalConnectionsModal, - updateSharedGroups, getPreferredBadge, i18n, onClose, @@ -212,7 +213,7 @@ export function ContactSpoofingReviewDialog( conversation={possiblyUnsafe.conversation} getPreferredBadge={getPreferredBadge} toggleSignalConnectionsModal={toggleSignalConnectionsModal} - updateSharedGroups={updateSharedGroups} + sharedGroupNames={possiblyUnsafe.sharedGroupNames} i18n={i18n} theme={theme} isSignalConnection={possiblyUnsafe.isSignalConnection} @@ -249,7 +250,7 @@ export function ContactSpoofingReviewDialog( conversation={safe.conversation} getPreferredBadge={getPreferredBadge} toggleSignalConnectionsModal={toggleSignalConnectionsModal} - updateSharedGroups={updateSharedGroups} + sharedGroupNames={safe.sharedGroupNames} i18n={i18n} onClick={() => { showContactModal(safe.conversation.id); @@ -343,7 +344,7 @@ export function ContactSpoofingReviewDialog( toggleSignalConnectionsModal={ toggleSignalConnectionsModal } - updateSharedGroups={updateSharedGroups} + sharedGroupNames={conversationInfo.sharedGroupNames} getPreferredBadge={getPreferredBadge} i18n={i18n} theme={theme} diff --git a/ts/components/conversation/ContactSpoofingReviewDialogPerson.dom.stories.tsx b/ts/components/conversation/ContactSpoofingReviewDialogPerson.dom.stories.tsx index db46a8a027..f953cbddfe 100644 --- a/ts/components/conversation/ContactSpoofingReviewDialogPerson.dom.stories.tsx +++ b/ts/components/conversation/ContactSpoofingReviewDialogPerson.dom.stories.tsx @@ -24,7 +24,7 @@ export default { i18n, onClick: action('onClick'), toggleSignalConnectionsModal: action('toggleSignalConnectionsModal'), - updateSharedGroups: action('updateSharedGroups'), + sharedGroupNames: [], getPreferredBadge: () => undefined, conversation: getDefaultConversation(), theme: ThemeType.light, @@ -52,7 +52,5 @@ ProfileNameChanged.args = { export const WithSharedGroups = Template.bind({}); WithSharedGroups.args = { - conversation: getDefaultConversation({ - sharedGroupNames: ['A', 'B', 'C'], - }), + sharedGroupNames: ['A', 'B', 'C'], }; diff --git a/ts/components/conversation/ContactSpoofingReviewDialogPerson.dom.tsx b/ts/components/conversation/ContactSpoofingReviewDialogPerson.dom.tsx index d74bb6eff9..baaecd8679 100644 --- a/ts/components/conversation/ContactSpoofingReviewDialogPerson.dom.tsx +++ b/ts/components/conversation/ContactSpoofingReviewDialogPerson.dom.tsx @@ -2,7 +2,7 @@ // SPDX-License-Identifier: AGPL-3.0-only import type { ReactNode } from 'react'; -import React, { useEffect } from 'react'; +import React from 'react'; import type { ConversationType } from '../../state/ducks/conversations.preload.js'; import type { LocalizerType, ThemeType } from '../../types/Util.std.js'; @@ -21,8 +21,8 @@ export type PropsType = Readonly<{ getPreferredBadge: PreferredBadgeSelectorType; i18n: LocalizerType; onClick?: () => void; + sharedGroupNames: ReadonlyArray; toggleSignalConnectionsModal: () => void; - updateSharedGroups: (conversationId: string) => void; theme: ThemeType; oldName: string | undefined; isSignalConnection: boolean; @@ -34,8 +34,8 @@ export function ContactSpoofingReviewDialogPerson({ getPreferredBadge, i18n, onClick, + sharedGroupNames, toggleSignalConnectionsModal, - updateSharedGroups, theme, oldName, isSignalConnection, @@ -45,11 +45,6 @@ export function ContactSpoofingReviewDialogPerson({ ' expected a direct conversation' ); - useEffect(() => { - // Kick off the expensive hydration of the current sharedGroupNames - updateSharedGroups(conversation.id); - }, [conversation.id, updateSharedGroups]); - const newName = conversation.profileName || conversation.title; let callout: React.JSX.Element | undefined; @@ -124,10 +119,10 @@ export function ContactSpoofingReviewDialogPerson({
- {conversation.sharedGroupNames?.length ? ( + {sharedGroupNames.length > 0 ? ( ) : ( i18n( diff --git a/ts/components/conversation/ConversationHeader.dom.tsx b/ts/components/conversation/ConversationHeader.dom.tsx index 16f37b6972..394f4182a5 100644 --- a/ts/components/conversation/ConversationHeader.dom.tsx +++ b/ts/components/conversation/ConversationHeader.dom.tsx @@ -140,7 +140,6 @@ export type PropsDataType = { isSignalConversation?: boolean; isSmsOnlyOrUnregistered?: boolean; outgoingCallButtonStyle: OutgoingCallButtonStyle; - sharedGroupNames: ReadonlyArray; theme: ThemeType; contactSpoofingWarning: ContactSpoofingWarning | null; @@ -231,7 +230,6 @@ export const ConversationHeader = memo(function ConversationHeader({ onViewUserStories, outgoingCallButtonStyle, setLocalDeleteWarningShown, - sharedGroupNames, theme, contactSpoofingWarning, @@ -344,7 +342,6 @@ export const ConversationHeader = memo(function ConversationHeader({ hasStories={hasStories ?? null} headerRef={headerRef} i18n={i18n} - sharedGroupNames={sharedGroupNames} theme={theme} onViewUserStories={onViewUserStories} onViewConversationDetails={onViewConversationDetails} @@ -478,7 +475,6 @@ function HeaderContent({ hasStories, headerRef, i18n, - sharedGroupNames, theme, isSignalConversation, onViewUserStories, @@ -489,7 +485,6 @@ function HeaderContent({ hasStories: HasStories | null; headerRef: RefObject; i18n: LocalizerType; - sharedGroupNames: ReadonlyArray; theme: ThemeType; isSignalConversation: boolean; onViewUserStories: () => void; @@ -528,7 +523,6 @@ function HeaderContent({ onClick={hasStories ? onViewUserStories : onClick} phoneNumber={conversation.phoneNumber ?? undefined} profileName={conversation.profileName ?? undefined} - sharedGroupNames={sharedGroupNames} size={AvatarSize.THIRTY_TWO} // user may have stories, but we don't show that on Note to Self conversation storyRing={conversation.isMe ? undefined : (hasStories ?? undefined)} diff --git a/ts/components/conversation/ConversationHero.dom.stories.tsx b/ts/components/conversation/ConversationHero.dom.stories.tsx index 4b5497d579..000eb743d7 100644 --- a/ts/components/conversation/ConversationHero.dom.stories.tsx +++ b/ts/components/conversation/ConversationHero.dom.stories.tsx @@ -50,7 +50,7 @@ export default { i18n, isDirectConvoAndHasNickname: false, theme: ThemeType.light, - updateSharedGroups: action('updateSharedGroups'), + sharedGroupNames: [], viewUserStories: action('viewUserStories'), toggleAboutContactModal: action('toggleAboutContactModal'), toggleProfileNameWarningModal: action('toggleProfileNameWarningModal'), diff --git a/ts/components/conversation/ConversationHero.dom.tsx b/ts/components/conversation/ConversationHero.dom.tsx index e66890e3eb..1e363676cb 100644 --- a/ts/components/conversation/ConversationHero.dom.tsx +++ b/ts/components/conversation/ConversationHero.dom.tsx @@ -1,7 +1,7 @@ // Copyright 2020 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -import React, { type ReactNode, useEffect, useState } from 'react'; +import React, { type ReactNode, useState } from 'react'; import classNames from 'classnames'; import type { Props as AvatarProps } from '../Avatar.dom.js'; import { Avatar, AvatarSize, AvatarBlur } from '../Avatar.dom.js'; @@ -39,7 +39,6 @@ export type Props = { phoneNumber?: string; sharedGroupNames?: ReadonlyArray; startAvatarDownload: () => void; - updateSharedGroups: (conversationId: string) => unknown; theme: ThemeType; viewUserStories: ViewUserStoriesActionCreatorType; toggleAboutContactModal: (conversationId: string) => unknown; @@ -74,11 +73,11 @@ const renderExtraInformation = ({ | 'memberships' | 'openConversationDetails' | 'phoneNumber' -> & - Required> & { - onClickProfileNameWarning: () => void; - onToggleSafetyTips: (showSafetyTips: boolean) => void; - }) => { + | 'sharedGroupNames' +> & { + onClickProfileNameWarning: () => void; + onToggleSafetyTips: (showSafetyTips: boolean) => void; +}) => { if (conversationType !== 'direct' && conversationType !== 'group') { return null; } @@ -107,7 +106,7 @@ const renderExtraInformation = ({ const shouldShowReviewCarefully = !acceptedMessageRequest && - (conversationType === 'group' || sharedGroupNames.length <= 1); + (conversationType === 'group' || (sharedGroupNames?.length ?? 0) <= 1); const reviewCarefullyLabel = shouldShowReviewCarefully ? (
@@ -123,7 +122,7 @@ const renderExtraInformation = ({
) : null; @@ -180,7 +179,7 @@ const renderExtraInformation = ({ if ( conversationType === 'direct' && - sharedGroupNames.length === 0 && + (sharedGroupNames?.length ?? 0) === 0 && acceptedMessageRequest && phoneNumber ) { @@ -257,18 +256,12 @@ export function ConversationHero({ startAvatarDownload, theme, title, - updateSharedGroups, viewUserStories, toggleAboutContactModal, toggleProfileNameWarningModal, }: Props): React.JSX.Element { const [isShowingSafetyTips, setIsShowingSafetyTips] = useState(false); - useEffect(() => { - // Kick off the expensive hydration of the current sharedGroupNames - updateSharedGroups(id); - }, [id, updateSharedGroups]); - let avatarBlur: AvatarBlur = AvatarBlur.NoBlur; let avatarOnClick: undefined | (() => void); @@ -339,7 +332,6 @@ export function ConversationHero({ noteToSelf={isMe} onClick={avatarOnClick} profileName={profileName} - sharedGroupNames={sharedGroupNames} size={AvatarSize.EIGHTY} // user may have stories, but we don't show that on Note to Self conversation storyRing={isMe ? undefined : hasStories} @@ -380,7 +372,7 @@ export function ConversationHero({ }, openConversationDetails, phoneNumber, - sharedGroupNames, + sharedGroupNames: sharedGroupNames ?? [], })} {isSignalConversation && }
diff --git a/ts/components/conversation/Message.dom.tsx b/ts/components/conversation/Message.dom.tsx index 6b4bb2b0b0..17b870b07d 100644 --- a/ts/components/conversation/Message.dom.tsx +++ b/ts/components/conversation/Message.dom.tsx @@ -284,7 +284,6 @@ export type PropsData = { | 'isMe' | 'phoneNumber' | 'profileName' - | 'sharedGroupNames' | 'title' >; conversationType: ConversationTypeType; @@ -1705,7 +1704,6 @@ export class Message extends React.PureComponent { color={getColorForCallLink(getKeyFromCallLink(first.url))} conversationType="callLink" i18n={i18n} - sharedGroupNames={[]} size={64} title={title ?? i18n('icu:calling__call-link-default-title')} /> @@ -2327,7 +2325,6 @@ export class Message extends React.PureComponent { }} phoneNumber={author.phoneNumber} profileName={author.profileName} - sharedGroupNames={author.sharedGroupNames} size={GROUP_AVATAR_SIZE} theme={theme} title={author.title} diff --git a/ts/components/conversation/MessageDetail.dom.tsx b/ts/components/conversation/MessageDetail.dom.tsx index a1af875803..42c550f268 100644 --- a/ts/components/conversation/MessageDetail.dom.tsx +++ b/ts/components/conversation/MessageDetail.dom.tsx @@ -51,7 +51,6 @@ export type Contact = Pick< | 'isMe' | 'phoneNumber' | 'profileName' - | 'sharedGroupNames' | 'title' > & { status?: SendStatus; @@ -172,15 +171,8 @@ export function MessageDetail({ const messageDetailRef = useRef(null); function renderAvatar(contact: Contact): React.JSX.Element { - const { - avatarUrl, - badges, - color, - phoneNumber, - profileName, - sharedGroupNames, - title, - } = contact; + const { avatarUrl, badges, color, phoneNumber, profileName, title } = + contact; return ( ); diff --git a/ts/components/conversation/MessageRequestActions.dom.stories.tsx b/ts/components/conversation/MessageRequestActions.dom.stories.tsx index d9d9e38d51..fd60986b55 100644 --- a/ts/components/conversation/MessageRequestActions.dom.stories.tsx +++ b/ts/components/conversation/MessageRequestActions.dom.stories.tsx @@ -60,6 +60,7 @@ function Example(args: Args): React.JSX.Element { isBlocked={args.isBlocked} isHidden={args.isHidden} isReported={args.isReported} + getSharedGroupNames={() => []} acceptConversation={action('acceptConversation')} blockAndReportSpam={action('blockAndReportSpam')} blockConversation={action('blockConversation')} diff --git a/ts/components/conversation/MessageRequestActions.dom.tsx b/ts/components/conversation/MessageRequestActions.dom.tsx index d7ee163160..e8ec0c5c67 100644 --- a/ts/components/conversation/MessageRequestActions.dom.tsx +++ b/ts/components/conversation/MessageRequestActions.dom.tsx @@ -12,11 +12,15 @@ import { import { I18n } from '../I18n.dom.js'; import type { LocalizerType } from '../../types/Util.std.js'; import { strictAssert } from '../../util/assert.std.js'; +import { + useSharedGroupNamesOnMount, + type GetSharedGroupNamesType, +} from '../../util/sharedGroupNames.dom.js'; export type Props = { i18n: LocalizerType; isHidden: boolean | null; - sharedGroupNames?: ReadonlyArray; + getSharedGroupNames: GetSharedGroupNamesType; } & Omit< MessageRequestActionsConfirmationProps, 'i18n' | 'state' | 'onChangeState' @@ -27,11 +31,11 @@ export function MessageRequestActions({ conversationId, conversationType, conversationName, + getSharedGroupNames, i18n, isBlocked, isHidden, isReported, - sharedGroupNames = [], acceptConversation, blockAndReportSpam, blockConversation, @@ -39,6 +43,10 @@ export function MessageRequestActions({ deleteConversation, }: Props): React.JSX.Element { const [mrState, setMrState] = React.useState(MessageRequestState.default); + const sharedGroupNames = useSharedGroupNamesOnMount( + conversationId, + getSharedGroupNames + ); const nameValue = conversationType === 'direct' ? conversationName : addedByName; diff --git a/ts/components/conversation/PhoneNumberDiscoveryNotification.dom.stories.tsx b/ts/components/conversation/PhoneNumberDiscoveryNotification.dom.stories.tsx index a3fca422dd..ed5b1d1e65 100644 --- a/ts/components/conversation/PhoneNumberDiscoveryNotification.dom.stories.tsx +++ b/ts/components/conversation/PhoneNumberDiscoveryNotification.dom.stories.tsx @@ -5,6 +5,7 @@ import * as React from 'react'; import type { Meta } from '@storybook/react'; import type { PropsType } from './PhoneNumberDiscoveryNotification.dom.js'; import { PhoneNumberDiscoveryNotification } from './PhoneNumberDiscoveryNotification.dom.js'; +import type { GetSharedGroupNamesType } from '../../util/sharedGroupNames.dom.js'; const { i18n } = window.SignalContext; @@ -12,9 +13,16 @@ export default { title: 'Components/Conversation/PhoneNumberDiscoveryNotification', } satisfies Meta; +const createMockGetSharedGroupNames = + (sharedGroupNames: ReadonlyArray): GetSharedGroupNamesType => + (_state, _conversationId) => + sharedGroupNames; + const createProps = (overrideProps: Partial = {}): PropsType => ({ - i18n, + conversationId: 'fake-conversation-id', conversationTitle: overrideProps.conversationTitle || 'John Fire', + getSharedGroupNames: createMockGetSharedGroupNames([]), + i18n, phoneNumber: '(555) 333-1111', }); @@ -26,7 +34,7 @@ export function WithSharedGroup(): React.JSX.Element { return ( ); } diff --git a/ts/components/conversation/PhoneNumberDiscoveryNotification.dom.tsx b/ts/components/conversation/PhoneNumberDiscoveryNotification.dom.tsx index 4beac7a592..e272c4afd9 100644 --- a/ts/components/conversation/PhoneNumberDiscoveryNotification.dom.tsx +++ b/ts/components/conversation/PhoneNumberDiscoveryNotification.dom.tsx @@ -7,13 +7,18 @@ import type { LocalizerType } from '../../types/Util.std.js'; import { SystemMessage } from './SystemMessage.dom.js'; import { Emojify } from './Emojify.dom.js'; import { getStringForPhoneNumberDiscovery } from '../../util/getStringForPhoneNumberDiscovery.std.js'; +import { + useSharedGroupNamesOnMount, + type GetSharedGroupNamesType, +} from '../../util/sharedGroupNames.dom.js'; export type PropsDataType = { + conversationId: string; conversationTitle: string; phoneNumber: string; - sharedGroup?: string; }; export type PropsType = PropsDataType & { + getSharedGroupNames: GetSharedGroupNamesType; i18n: LocalizerType; }; @@ -21,7 +26,19 @@ export type PropsType = PropsDataType & { export function PhoneNumberDiscoveryNotification( props: PropsType ): React.JSX.Element { - const { conversationTitle, i18n, sharedGroup, phoneNumber } = props; + const { + conversationId, + conversationTitle, + getSharedGroupNames, + i18n, + phoneNumber, + } = props; + + const sharedGroupNames = useSharedGroupNamesOnMount( + conversationId, + getSharedGroupNames + ); + const sharedGroup = sharedGroupNames[0]; const message = getStringForPhoneNumberDiscovery({ conversationTitle, diff --git a/ts/components/conversation/ReactionViewer.dom.tsx b/ts/components/conversation/ReactionViewer.dom.tsx index 6a8015ddbb..e3925f3ebb 100644 --- a/ts/components/conversation/ReactionViewer.dom.tsx +++ b/ts/components/conversation/ReactionViewer.dom.tsx @@ -46,7 +46,6 @@ export type Reaction = { | 'isMe' | 'phoneNumber' | 'profileName' - | 'sharedGroupNames' | 'title' >; }; @@ -272,7 +271,6 @@ export const ReactionViewer = React.forwardRef( avatarUrl={from.avatarUrl} badge={getPreferredBadge(from.badges)} conversationType="direct" - sharedGroupNames={from.sharedGroupNames} size={32} color={from.color} profileName={from.profileName} diff --git a/ts/components/conversation/StagedLinkPreview.dom.tsx b/ts/components/conversation/StagedLinkPreview.dom.tsx index 6f2931a18b..e81ae7d568 100644 --- a/ts/components/conversation/StagedLinkPreview.dom.tsx +++ b/ts/components/conversation/StagedLinkPreview.dom.tsx @@ -130,7 +130,6 @@ export function Thumbnail({ color={getColorForCallLink(getKeyFromCallLink(url))} conversationType="callLink" i18n={i18n} - sharedGroupNames={[]} size={64} title={title ?? i18n('icu:calling__call-link-default-title')} /> diff --git a/ts/components/conversation/Timeline.dom.stories.tsx b/ts/components/conversation/Timeline.dom.stories.tsx index ad113b3b21..9e25aaf7b8 100644 --- a/ts/components/conversation/Timeline.dom.stories.tsx +++ b/ts/components/conversation/Timeline.dom.stories.tsx @@ -29,9 +29,6 @@ export default { args: {}, } satisfies Meta; -// eslint-disable-next-line -const noop = () => {}; - function mockMessageTimelineItem( id: string, data: Partial @@ -280,7 +277,6 @@ const actions = () => ({ targetMessage: action('targetMessage'), scrollToOldestUnreadMention: action('scrollToOldestUnreadMention'), clearTargetedMessage: action('clearTargetedMessage'), - updateSharedGroups: action('updateSharedGroups'), endPoll: action('endPoll'), reactToMessage: action('reactToMessage'), @@ -363,6 +359,7 @@ const renderItem = ({ }) => ( undefined} + getSharedGroupNames={() => []} id="" isTargeted={false} isBlocked={false} @@ -423,7 +420,6 @@ const renderHeroRow = () => { title={getTitle()} startAvatarDownload={action('startAvatarDownload')} pendingAvatarDownload={false} - updateSharedGroups={noop} viewUserStories={action('viewUserStories')} toggleAboutContactModal={action('toggleAboutContactModal')} toggleProfileNameWarningModal={action('toggleProfileNameWarningModal')} diff --git a/ts/components/conversation/TimelineItem.dom.stories.tsx b/ts/components/conversation/TimelineItem.dom.stories.tsx index ed0520e2b4..c74011bbfd 100644 --- a/ts/components/conversation/TimelineItem.dom.stories.tsx +++ b/ts/components/conversation/TimelineItem.dom.stories.tsx @@ -39,6 +39,7 @@ const getDefaultProps = () => ({ containerWidthBreakpoint: WidthBreakpoint.Wide, conversationId: 'conversation-id', getPreferredBadge: () => undefined, + getSharedGroupNames: () => [], id: 'asdf', isNextItemCallingNotification: false, isPinned: false, diff --git a/ts/components/conversation/TimelineItem.dom.tsx b/ts/components/conversation/TimelineItem.dom.tsx index 2abba033e7..4d82a79ab6 100644 --- a/ts/components/conversation/TimelineItem.dom.tsx +++ b/ts/components/conversation/TimelineItem.dom.tsx @@ -5,6 +5,7 @@ import type { ReactNode, RefObject } from 'react'; import React, { memo } from 'react'; import type { LocalizerType, ThemeType } from '../../types/Util.std.js'; +import type { GetSharedGroupNamesType } from '../../util/sharedGroupNames.dom.js'; import type { InteractionModeType } from '../../state/ducks/conversations.preload.js'; import { TimelineDateHeader } from './TimelineDateHeader.dom.js'; @@ -200,6 +201,7 @@ export type TimelineItemType = ( type PropsLocalType = { containerElementRef: RefObject; conversationId: string; + getSharedGroupNames: GetSharedGroupNamesType; item?: TimelineItemType; id: string; interactivity: MessageInteractivity; @@ -243,6 +245,7 @@ export const TimelineItem = memo(function TimelineItem({ containerElementRef, conversationId, getPreferredBadge, + getSharedGroupNames, i18n, id, interactivity, @@ -416,6 +419,7 @@ export const TimelineItem = memo(function TimelineItem({ ); diff --git a/ts/components/conversation/TimelineMessage.dom.stories.tsx b/ts/components/conversation/TimelineMessage.dom.stories.tsx index fafb468df7..34d128bfec 100644 --- a/ts/components/conversation/TimelineMessage.dom.stories.tsx +++ b/ts/components/conversation/TimelineMessage.dom.stories.tsx @@ -781,7 +781,6 @@ Quote.args = { id: '', isMe: false, title: 'Quoter Dude', - sharedGroupNames: [], acceptedMessageRequest: true, badges: [], }, @@ -2006,7 +2005,6 @@ function getStableVoter(optionIndex: number, voterIndex: number) { name, phoneNumber: undefined, profileName: undefined, - sharedGroupNames: [], title: name, }; } @@ -2065,7 +2063,6 @@ function createMockPollWithVoteCounts( name: 'You', phoneNumber: undefined, profileName: undefined, - sharedGroupNames: [], title: 'You', }, }, @@ -2114,7 +2111,6 @@ function createMockPollWithVoters( name, phoneNumber: undefined, profileName: undefined, - sharedGroupNames: [], title: name, }, }; diff --git a/ts/components/conversation/TypingBubble.dom.stories.tsx b/ts/components/conversation/TypingBubble.dom.stories.tsx index 2ecd2caf78..20f3e1d4eb 100644 --- a/ts/components/conversation/TypingBubble.dom.stories.tsx +++ b/ts/components/conversation/TypingBubble.dom.stories.tsx @@ -34,7 +34,6 @@ const CONTACTS = times(10, index => { phoneNumber: '(202) 555-0001', profileName: `${letter} ${letter}`, isMe: false, - sharedGroupNames: [], title: `${letter} ${letter}`, }); }); diff --git a/ts/components/conversation/TypingBubble.dom.tsx b/ts/components/conversation/TypingBubble.dom.tsx index 204bdf18f7..b97d374034 100644 --- a/ts/components/conversation/TypingBubble.dom.tsx +++ b/ts/components/conversation/TypingBubble.dom.tsx @@ -27,7 +27,6 @@ type TypingContactType = Pick< | 'isMe' | 'phoneNumber' | 'profileName' - | 'sharedGroupNames' | 'title' >; @@ -137,7 +136,6 @@ function TypingBubbleAvatar({ profileName={contact.profileName} theme={theme} title={contact.title} - sharedGroupNames={contact.sharedGroupNames} size={28} /> diff --git a/ts/components/conversation/contactUtil.dom.tsx b/ts/components/conversation/contactUtil.dom.tsx index c28de42970..8048f851b8 100644 --- a/ts/components/conversation/contactUtil.dom.tsx +++ b/ts/components/conversation/contactUtil.dom.tsx @@ -37,7 +37,6 @@ export function renderAvatar({ conversationType="direct" i18n={i18n} title={title} - sharedGroupNames={[]} size={size} /> ); diff --git a/ts/components/conversation/conversation-details/AddGroupMembersModal/ChooseGroupMembersModal.dom.tsx b/ts/components/conversation/conversation-details/AddGroupMembersModal/ChooseGroupMembersModal.dom.tsx index 3f1277ac1c..2c41345ebd 100644 --- a/ts/components/conversation/conversation-details/AddGroupMembersModal/ChooseGroupMembersModal.dom.tsx +++ b/ts/components/conversation/conversation-details/AddGroupMembersModal/ChooseGroupMembersModal.dom.tsx @@ -423,7 +423,6 @@ export function ChooseGroupMembersModal({ id={contact.id} phoneNumber={contact.phoneNumber} profileName={contact.profileName} - sharedGroupNames={contact.sharedGroupNames} title={contact.title} onClickRemove={() => { removeSelectedContact(contact.id); diff --git a/ts/components/conversation/conversation-details/ConversationDetails.dom.stories.tsx b/ts/components/conversation/conversation-details/ConversationDetails.dom.stories.tsx index ea0f614856..da45a0cc7a 100644 --- a/ts/components/conversation/conversation-details/ConversationDetails.dom.stories.tsx +++ b/ts/components/conversation/conversation-details/ConversationDetails.dom.stories.tsx @@ -33,7 +33,6 @@ const conversation: ConversationType = getDefaultConversation({ title: 'Some Conversation', groupDescription: 'Hello World!', type: 'group', - sharedGroupNames: [], conversationColor: 'ultramarine' as const, }); diff --git a/ts/components/conversation/conversation-details/ConversationDetailsHeader.dom.tsx b/ts/components/conversation/conversation-details/ConversationDetailsHeader.dom.tsx index 871418b4fe..508d0e33a7 100644 --- a/ts/components/conversation/conversation-details/ConversationDetailsHeader.dom.tsx +++ b/ts/components/conversation/conversation-details/ConversationDetailsHeader.dom.tsx @@ -118,7 +118,6 @@ export function ConversationDetailsHeader({ } setActiveModal(ConversationDetailsHeaderActiveModal.ShowingBadges); }} - sharedGroupNames={[]} theme={theme} /> ); diff --git a/ts/components/conversation/conversation-details/GroupLinkManagement.dom.stories.tsx b/ts/components/conversation/conversation-details/GroupLinkManagement.dom.stories.tsx index 4889268211..654ff959fb 100644 --- a/ts/components/conversation/conversation-details/GroupLinkManagement.dom.stories.tsx +++ b/ts/components/conversation/conversation-details/GroupLinkManagement.dom.stories.tsx @@ -29,7 +29,6 @@ function getConversation( pendingMemberships: Array(16).fill({ member: getDefaultConversation({}) }), title: 'Some Conversation', type: 'group', - sharedGroupNames: [], groupLink, accessControlAddFromInviteLink: accessControlAddFromInviteLink !== undefined diff --git a/ts/components/conversation/conversation-details/GroupV2Permissions.dom.stories.tsx b/ts/components/conversation/conversation-details/GroupV2Permissions.dom.stories.tsx index c69d2d7fb1..0c4dffca21 100644 --- a/ts/components/conversation/conversation-details/GroupV2Permissions.dom.stories.tsx +++ b/ts/components/conversation/conversation-details/GroupV2Permissions.dom.stories.tsx @@ -22,7 +22,6 @@ const conversation: ConversationType = getDefaultConversation({ pendingMemberships: Array(16).fill({ member: getDefaultConversation({}) }), title: 'Some Conversation', type: 'group', - sharedGroupNames: [], announcementsOnlyReady: true, areWeAdmin: true, }); diff --git a/ts/components/conversation/conversation-details/PendingInvites.dom.stories.tsx b/ts/components/conversation/conversation-details/PendingInvites.dom.stories.tsx index 7fafa5d275..f0796d2c6c 100644 --- a/ts/components/conversation/conversation-details/PendingInvites.dom.stories.tsx +++ b/ts/components/conversation/conversation-details/PendingInvites.dom.stories.tsx @@ -39,7 +39,6 @@ const conversation: ConversationType = { sortedGroupMembers, title: 'Some Conversation', type: 'group', - sharedGroupNames: [], acknowledgedGroupNameCollisions: {}, storySendMode: StorySendMode.IfActive, }; diff --git a/ts/components/conversation/media-gallery/ContactListItem.dom.tsx b/ts/components/conversation/media-gallery/ContactListItem.dom.tsx index 548cad813d..202fd3f183 100644 --- a/ts/components/conversation/media-gallery/ContactListItem.dom.tsx +++ b/ts/components/conversation/media-gallery/ContactListItem.dom.tsx @@ -40,7 +40,6 @@ export function ContactListItem({ conversationType="direct" i18n={i18n} title={name} - sharedGroupNames={[]} size={AvatarSize.THIRTY_SIX} /> ); diff --git a/ts/components/conversation/poll-message/PollVotesModal.dom.tsx b/ts/components/conversation/poll-message/PollVotesModal.dom.tsx index e01d85b249..97094902d0 100644 --- a/ts/components/conversation/poll-message/PollVotesModal.dom.tsx +++ b/ts/components/conversation/poll-message/PollVotesModal.dom.tsx @@ -119,7 +119,6 @@ export function PollVotesModal({ noteToSelf={false} phoneNumber={vote.from.phoneNumber} profileName={vote.from.profileName} - sharedGroupNames={vote.from.sharedGroupNames} size={AvatarSize.THIRTY_SIX} title={vote.from.title} /> diff --git a/ts/components/conversationList/BaseConversationListItem.dom.tsx b/ts/components/conversationList/BaseConversationListItem.dom.tsx index fd3a986c31..a51b240fb9 100644 --- a/ts/components/conversationList/BaseConversationListItem.dom.tsx +++ b/ts/components/conversationList/BaseConversationListItem.dom.tsx @@ -77,7 +77,6 @@ type PropsType = { | 'markedUnread' | 'phoneNumber' | 'profileName' - | 'sharedGroupNames' | 'title' | 'serviceId' > & @@ -115,7 +114,6 @@ export const BaseConversationListItem: FunctionComponent = onMouseDown, phoneNumber, profileName, - sharedGroupNames, shouldShowSpinner, testId: overrideTestId, title, @@ -217,7 +215,6 @@ export const BaseConversationListItem: FunctionComponent = phoneNumber={phoneNumber} profileName={profileName} title={title} - sharedGroupNames={sharedGroupNames} size={avatarSize ?? AvatarSize.FORTY_EIGHT} // This is here to appease the type checker. {...(props.badge diff --git a/ts/components/conversationList/ContactCheckbox.dom.tsx b/ts/components/conversationList/ContactCheckbox.dom.tsx index 894e6676d6..51f1c224eb 100644 --- a/ts/components/conversationList/ContactCheckbox.dom.tsx +++ b/ts/components/conversationList/ContactCheckbox.dom.tsx @@ -34,7 +34,6 @@ export type PropsDataType = { | 'isMe' | 'phoneNumber' | 'profileName' - | 'sharedGroupNames' | 'title' | 'type' | 'serviceId' @@ -65,7 +64,6 @@ export const ContactCheckbox: FunctionComponent = React.memo( onClick, phoneNumber, profileName, - sharedGroupNames, theme, title, type, @@ -108,7 +106,6 @@ export const ContactCheckbox: FunctionComponent = React.memo( phoneNumber={phoneNumber} profileName={profileName} title={title} - sharedGroupNames={sharedGroupNames} size={AvatarSize.THIRTY_TWO} // appease the type checker. {...(badge ? { badge, theme } : { badge: undefined })} diff --git a/ts/components/conversationList/ContactListItem.dom.tsx b/ts/components/conversationList/ContactListItem.dom.tsx index 82bb123e22..9ae93b18eb 100644 --- a/ts/components/conversationList/ContactListItem.dom.tsx +++ b/ts/components/conversationList/ContactListItem.dom.tsx @@ -32,7 +32,6 @@ export type ContactListItemConversationType = Pick< | 'isMe' | 'phoneNumber' | 'profileName' - | 'sharedGroupNames' | 'systemGivenName' | 'systemFamilyName' | 'title' @@ -77,7 +76,6 @@ export const ContactListItem: FunctionComponent = React.memo( onBlock, phoneNumber, profileName, - sharedGroupNames, systemGivenName, systemFamilyName, theme, @@ -269,7 +267,6 @@ export const ContactListItem: FunctionComponent = React.memo( phoneNumber={phoneNumber} profileName={profileName} title={title} - sharedGroupNames={sharedGroupNames} size={AvatarSize.THIRTY_TWO} // This is here to appease the type checker. {...(badge ? { badge, theme } : { badge: undefined })} diff --git a/ts/components/conversationList/ConversationListItem.dom.tsx b/ts/components/conversationList/ConversationListItem.dom.tsx index ce22561d68..1e11c158f7 100644 --- a/ts/components/conversationList/ConversationListItem.dom.tsx +++ b/ts/components/conversationList/ConversationListItem.dom.tsx @@ -49,7 +49,6 @@ export type PropsData = Pick< | 'phoneNumber' | 'profileName' | 'removalStage' - | 'sharedGroupNames' | 'shouldShowDraft' | 'title' | 'type' @@ -100,7 +99,6 @@ export const ConversationListItem: FunctionComponent = React.memo( phoneNumber, profileName, removalStage, - sharedGroupNames, shouldShowDraft, theme, title, @@ -234,7 +232,6 @@ export const ConversationListItem: FunctionComponent = React.memo( onMouseDown={onMouseDownItem} phoneNumber={phoneNumber} profileName={profileName} - sharedGroupNames={sharedGroupNames} theme={theme} title={title} unreadCount={unreadCount} diff --git a/ts/components/conversationList/GroupListItem.dom.tsx b/ts/components/conversationList/GroupListItem.dom.tsx index 3535028df7..903f4e1f87 100644 --- a/ts/components/conversationList/GroupListItem.dom.tsx +++ b/ts/components/conversationList/GroupListItem.dom.tsx @@ -67,7 +67,6 @@ export function GroupListItem({ hasAvatar={group.hasAvatar} i18n={i18n} title={group.title} - sharedGroupNames={[]} size={AvatarSize.THIRTY_TWO} badge={undefined} /> diff --git a/ts/components/conversationList/MessageSearchResult.dom.tsx b/ts/components/conversationList/MessageSearchResult.dom.tsx index 48a7a92f09..b93f25fb9b 100644 --- a/ts/components/conversationList/MessageSearchResult.dom.tsx +++ b/ts/components/conversationList/MessageSearchResult.dom.tsx @@ -47,7 +47,6 @@ export type PropsDataType = { | 'isMe' | 'phoneNumber' | 'profileName' - | 'sharedGroupNames' | 'title' | 'type' >; @@ -200,7 +199,6 @@ export const MessageSearchResult: FunctionComponent = React.memo( onClick={onClickItem} phoneNumber={from.phoneNumber} profileName={from.profileName} - sharedGroupNames={from.sharedGroupNames} theme={theme} title={from.title} /> diff --git a/ts/components/conversationList/PhoneNumberCheckbox.dom.tsx b/ts/components/conversationList/PhoneNumberCheckbox.dom.tsx index 8631a9cb95..67ad933698 100644 --- a/ts/components/conversationList/PhoneNumberCheckbox.dom.tsx +++ b/ts/components/conversationList/PhoneNumberCheckbox.dom.tsx @@ -98,7 +98,6 @@ export const PhoneNumberCheckbox: FunctionComponent = React.memo( i18n={i18n} phoneNumber={phoneNumber.userInput} title={phoneNumber.userInput} - sharedGroupNames={[]} size={AvatarSize.THIRTY_TWO} badge={undefined} /> diff --git a/ts/components/conversationList/StartNewConversation.dom.tsx b/ts/components/conversationList/StartNewConversation.dom.tsx index c1871665f1..f0afe19f04 100644 --- a/ts/components/conversationList/StartNewConversation.dom.tsx +++ b/ts/components/conversationList/StartNewConversation.dom.tsx @@ -98,7 +98,6 @@ export const StartNewConversation: FunctionComponent = React.memo( title={phoneNumber.userInput} size={AvatarSize.THIRTY_TWO} badge={undefined} - sharedGroupNames={[]} /> } title={phoneNumber.userInput} diff --git a/ts/components/conversationList/UsernameCheckbox.dom.tsx b/ts/components/conversationList/UsernameCheckbox.dom.tsx index faa37f1d09..c47f530e54 100644 --- a/ts/components/conversationList/UsernameCheckbox.dom.tsx +++ b/ts/components/conversationList/UsernameCheckbox.dom.tsx @@ -71,7 +71,6 @@ export const UsernameCheckbox: FunctionComponent = React.memo( searchResult i18n={i18n} title={title} - sharedGroupNames={[]} size={AvatarSize.THIRTY_TWO} badge={undefined} /> diff --git a/ts/components/conversationList/UsernameSearchResultListItem.dom.tsx b/ts/components/conversationList/UsernameSearchResultListItem.dom.tsx index e5637c3fec..8f573b4619 100644 --- a/ts/components/conversationList/UsernameSearchResultListItem.dom.tsx +++ b/ts/components/conversationList/UsernameSearchResultListItem.dom.tsx @@ -67,7 +67,6 @@ export function UsernameSearchResultListItem({ title={username} size={AvatarSize.THIRTY_TWO} badge={undefined} - sharedGroupNames={[]} /> } title={username} diff --git a/ts/components/leftPane/LeftPaneChooseGroupMembersHelper.dom.tsx b/ts/components/leftPane/LeftPaneChooseGroupMembersHelper.dom.tsx index f6a26bde57..f2c3270dd4 100644 --- a/ts/components/leftPane/LeftPaneChooseGroupMembersHelper.dom.tsx +++ b/ts/components/leftPane/LeftPaneChooseGroupMembersHelper.dom.tsx @@ -225,7 +225,6 @@ export class LeftPaneChooseGroupMembersHelper extends LeftPaneHelper diff --git a/ts/components/preferences/PreferencesSelectChatsDialog.dom.tsx b/ts/components/preferences/PreferencesSelectChatsDialog.dom.tsx index f684de9a1a..3973eb7e2e 100644 --- a/ts/components/preferences/PreferencesSelectChatsDialog.dom.tsx +++ b/ts/components/preferences/PreferencesSelectChatsDialog.dom.tsx @@ -235,7 +235,6 @@ export function PreferencesSelectChatsDialog( isMe={conversation.isMe} phoneNumber={conversation.phoneNumber} profileName={conversation.profileName} - sharedGroupNames={conversation.sharedGroupNames} title={conversation.title} onClickRemove={handleToggleSelectedConversation} /> diff --git a/ts/model-types.d.ts b/ts/model-types.d.ts index 09b610c42f..8381c1b350 100644 --- a/ts/model-types.d.ts +++ b/ts/model-types.d.ts @@ -420,7 +420,6 @@ export type ConversationAttributesType = { */ sealedSender?: SEALED_SENDER; sentMessageCount?: number; - sharedGroupNames?: ReadonlyArray; voiceNotePlaybackRate?: number; id: string; diff --git a/ts/models/conversations.preload.ts b/ts/models/conversations.preload.ts index 62fadb8d32..1a8b77251c 100644 --- a/ts/models/conversations.preload.ts +++ b/ts/models/conversations.preload.ts @@ -350,8 +350,6 @@ export class ConversationModel { lastSuccessfulGroupFetch?: number; - throttledUpdateSharedGroups?: () => Promise; - #cachedIdenticon?: CachedIdenticon; public isFetchingUUID?: boolean; @@ -509,10 +507,6 @@ export class ConversationModel { this.throttledBumpTyping = throttle(this.bumpTyping, 300); this.throttledUpdateUnread = throttle(this.#updateUnread, 300); - this.throttledUpdateSharedGroups = throttle( - this.updateSharedGroups.bind(this), - FIVE_MINUTES - ); this.throttledFetchSMSOnlyUUID = throttle( this.fetchSMSOnlyUUID.bind(this), FIVE_MINUTES @@ -5021,22 +5015,6 @@ export class ConversationModel { ); } - // This is an expensive operation we use to populate the message request hero row. It - // shows groups the current user has in common with this potential new contact. - async updateSharedGroups(): Promise { - const sharedGroups = await this.#getSharedGroups(); - - if (sharedGroups == null) { - return; - } - - const sharedGroupNames = sharedGroups.map(conversation => - conversation.getTitle() - ); - - this.set({ sharedGroupNames }); - } - onChangeProfileKey(): void { if (isDirectConversation(this.attributes)) { drop(this.getProfiles()); diff --git a/ts/quill/mentions/completion.dom.tsx b/ts/quill/mentions/completion.dom.tsx index db2b777446..6e1170e3f7 100644 --- a/ts/quill/mentions/completion.dom.tsx +++ b/ts/quill/mentions/completion.dom.tsx @@ -300,7 +300,6 @@ export class MentionCompletion { conversationType="direct" hasAvatar={member.hasAvatar} i18n={this.options.i18n} - sharedGroupNames={member.sharedGroupNames} size={AvatarSize.TWENTY_EIGHT} theme={theme} title={member.title} diff --git a/ts/state/ducks/conversations.preload.ts b/ts/state/ducks/conversations.preload.ts index 8a1ca37692..b9928ede6f 100644 --- a/ts/state/ducks/conversations.preload.ts +++ b/ts/state/ducks/conversations.preload.ts @@ -434,7 +434,6 @@ export type ConversationType = ReadonlyDeep< draftPreview?: DraftPreviewType; draftTimestamp?: number; - sharedGroupNames: ReadonlyArray; groupDescription?: string; groupVersion?: 1 | 2; groupId?: string; @@ -1341,11 +1340,9 @@ export const actions = { toggleHideStories, toggleSelectMessage, toggleSelectMode, - updateConversationModelSharedGroups, updateGroupAttributes, updateLastMessage, updateNicknameAndNote, - updateSharedGroups, verifyConversationsStoppingSend, }; @@ -1643,20 +1640,6 @@ function removeMember( }; } -function updateSharedGroups(conversationId: string): NoopActionType { - const conversation = window.ConversationController.get(conversationId); - if (!conversation) { - throw new Error('updateSharedGroups: Conversation not found!'); - } - - void conversation.throttledUpdateSharedGroups?.(); - - return { - type: 'NOOP', - payload: null, - }; -} - function filterAvatarData( avatars: ReadonlyArray, data: AvatarDataType @@ -4760,21 +4743,6 @@ function toggleAdmin( }; } -function updateConversationModelSharedGroups( - conversationId: string -): ThunkAction { - return dispatch => { - const conversation = window.ConversationController.get(conversationId); - if (conversation && conversation.throttledUpdateSharedGroups) { - void conversation.throttledUpdateSharedGroups(); - } - dispatch({ - type: 'NOOP', - payload: null, - }); - }; -} - function showExpiredIncomingTapToViewToast(): ShowToastActionType { log.info( 'showExpiredIncomingTapToViewToastShowing expired tap-to-view toast for an incoming message' diff --git a/ts/state/selectors/conversations.dom.ts b/ts/state/selectors/conversations.dom.ts index e7c1bb9655..c439d94248 100644 --- a/ts/state/selectors/conversations.dom.ts +++ b/ts/state/selectors/conversations.dom.ts @@ -2,6 +2,7 @@ // SPDX-License-Identifier: AGPL-3.0-only import memoizee from 'memoizee'; +import { memoize } from '@indutny/sneequals'; import lodash from 'lodash'; import { createSelector } from 'reselect'; import type { StateType } from '../reducer.preload.js'; @@ -114,7 +115,6 @@ export const getPlaceholderContact = (): ConversationType => { type: 'direct', title: window.SignalContext.i18n('icu:unknownContact'), isMe: false, - sharedGroupNames: [], }; return placeholderContact; }; @@ -658,14 +658,44 @@ export const getHasContactSpoofingReview = createSelector( } ); -function isTrusted(conversation: ConversationType): boolean { +/** + * Builds a Set of service IDs that appear in any group's memberships. + * Used to efficiently check if a contact shares groups with us. + * Memoized with sneequals to only recompute when group memberships change. + */ +const getServiceIdsInGroups = memoize( + (conversationLookup: ConversationLookupType): Set => { + const serviceIds = new Set(); + for (const conversation of Object.values(conversationLookup)) { + if ( + conversation.type === 'group' && + !conversation.left && + conversation.memberships + ) { + for (const membership of conversation.memberships) { + serviceIds.add(membership.aci); + } + } + } + return serviceIds; + } +); + +function isTrusted( + conversation: ConversationType, + serviceIdsInGroups: Set +): boolean { if (conversation.type === 'group') { return true; } + const hasSharedGroups = + conversation.serviceId != null && + serviceIdsInGroups.has(conversation.serviceId); + return Boolean( isInSystemContacts(conversation) || - conversation.sharedGroupNames.length > 0 || + hasSharedGroups || conversation.profileSharing || conversation.isMe ); @@ -684,7 +714,10 @@ function hasDisplayInfo(conversation: ConversationType): boolean { ); } -function canComposeConversation(conversation: ConversationType): boolean { +function canComposeConversation( + conversation: ConversationType, + serviceIdsInGroups: Set +): boolean { return Boolean( !isSignalConversation(conversation) && !conversation.isBlocked && @@ -692,7 +725,7 @@ function canComposeConversation(conversation: ConversationType): boolean { ((isGroupV2(conversation) && !conversation.left) || !isConversationUnregistered(conversation)) && hasDisplayInfo(conversation) && - isTrusted(conversation) + isTrusted(conversation, serviceIdsInGroups) ); } @@ -793,31 +826,39 @@ export const getAllChatFoldersMutedStats: StateSelector => - Object.values(conversationLookup).filter( + (conversationLookup: ConversationLookupType): Array => { + const serviceIdsInGroups = getServiceIdsInGroups(conversationLookup); + return Object.values(conversationLookup).filter( conversation => - conversation.type === 'direct' && canComposeConversation(conversation) - ) + conversation.type === 'direct' && + canComposeConversation(conversation, serviceIdsInGroups) + ); + } ); export const getCandidateContactsForNewGroup = createSelector( getConversationLookup, - (conversationLookup: ConversationLookupType): Array => - Object.values(conversationLookup).filter( + (conversationLookup: ConversationLookupType): Array => { + const serviceIdsInGroups = getServiceIdsInGroups(conversationLookup); + return Object.values(conversationLookup).filter( conversation => conversation.type === 'direct' && !conversation.isMe && - canComposeConversation(conversation) - ) + canComposeConversation(conversation, serviceIdsInGroups) + ); + } ); export const getComposableGroups = createSelector( getConversationLookup, - (conversationLookup: ConversationLookupType): Array => - Object.values(conversationLookup).filter( + (conversationLookup: ConversationLookupType): Array => { + const serviceIdsInGroups = getServiceIdsInGroups(conversationLookup); + return Object.values(conversationLookup).filter( conversation => - conversation.type === 'group' && canComposeConversation(conversation) - ) + conversation.type === 'group' && + canComposeConversation(conversation, serviceIdsInGroups) + ); + } ); const getConversationIdsWithStories = createSelector( diff --git a/ts/state/selectors/message.preload.ts b/ts/state/selectors/message.preload.ts index 1abf3870bc..82d01bd8f7 100644 --- a/ts/state/selectors/message.preload.ts +++ b/ts/state/selectors/message.preload.ts @@ -182,12 +182,7 @@ const linkify = new LinkifyIt(); type FormattedContact = Partial & Pick< ConversationType, - | 'acceptedMessageRequest' - | 'id' - | 'isMe' - | 'sharedGroupNames' - | 'title' - | 'type' + 'acceptedMessageRequest' | 'id' | 'isMe' | 'title' | 'type' >; export type PropsForMessage = Omit; export type MessagePropsType = Omit< @@ -383,7 +378,6 @@ const getAuthorForMessage = ( name, phoneNumber, profileName, - sharedGroupNames, title, } = getContact(message, options); @@ -400,7 +394,6 @@ const getAuthorForMessage = ( name, phoneNumber, profileName, - sharedGroupNames, title, }; @@ -456,7 +449,6 @@ const getReactionsForMessage = ( name, phoneNumber, profileName, - sharedGroupNames, title, } = c; @@ -470,7 +462,6 @@ const getReactionsForMessage = ( name, phoneNumber, profileName, - sharedGroupNames, title, }; @@ -503,7 +494,6 @@ export type PollVoteWithUserType = { | 'name' | 'phoneNumber' | 'profileName' - | 'sharedGroupNames' | 'title' >; }; @@ -612,7 +602,6 @@ const getPollForMessage = ( name: voter.name, phoneNumber: voter.phoneNumber, profileName: voter.profileName, - sharedGroupNames: voter.sharedGroupNames, title: voter.title, }; @@ -1956,13 +1945,12 @@ export function getPropsForPhoneNumberDiscovery( const conversation = getConversation(message, conversationSelector); const conversationTitle = conversation.title; - const sharedGroup = conversation.sharedGroupNames[0]; const { e164 } = phoneNumberDiscovery; return { + conversationId: conversation.id, conversationTitle, phoneNumber: renderNumber(e164) ?? e164, - sharedGroup, }; } diff --git a/ts/state/selectors/stories.preload.ts b/ts/state/selectors/stories.preload.ts index 5acb0d611f..7835fd6662 100644 --- a/ts/state/selectors/stories.preload.ts +++ b/ts/state/selectors/stories.preload.ts @@ -139,7 +139,6 @@ function getAvatarData( | 'id' | 'name' | 'profileName' - | 'sharedGroupNames' | 'title' > { return pick(conversation, [ @@ -151,7 +150,6 @@ function getAvatarData( 'id', 'name', 'profileName', - 'sharedGroupNames', 'title', ]); } @@ -183,7 +181,6 @@ export function getStoryView( 'isMe', 'name', 'profileName', - 'sharedGroupNames', 'title', 'serviceId', ] @@ -281,7 +278,6 @@ export function getConversationStory( 'id', 'name', 'profileName', - 'sharedGroupNames', 'sortedGroupMembers', 'title', 'left', diff --git a/ts/state/smart/AboutContactModal.preload.tsx b/ts/state/smart/AboutContactModal.preload.tsx index fd62d67fc2..80d48c06c6 100644 --- a/ts/state/smart/AboutContactModal.preload.tsx +++ b/ts/state/smart/AboutContactModal.preload.tsx @@ -10,6 +10,7 @@ import { getConversationSelector, getPendingAvatarDownloadSelector, } from '../selectors/conversations.dom.js'; +import { useSharedGroupNamesOnMount } from '../../util/sharedGroupNames.dom.js'; import type { ConversationType } from '../ducks/conversations.preload.js'; import { useConversationsActions } from '../ducks/conversations.preload.js'; import { useGlobalModalActions } from '../ducks/globalModals.preload.js'; @@ -39,8 +40,9 @@ export const SmartAboutContactModal = memo(function SmartAboutContactModal() { const { aboutContactModalContactId: contactId } = globalModals; const getConversation = useSelector(getConversationSelector); const isPendingAvatarDownload = useSelector(getPendingAvatarDownloadSelector); + const sharedGroupNames = useSharedGroupNamesOnMount(contactId ?? ''); - const { startAvatarDownload, updateSharedGroups } = useConversationsActions(); + const { startAvatarDownload } = useConversationsActions(); const conversation = getConversation(contactId); const { id: conversationId } = conversation ?? {}; @@ -66,7 +68,7 @@ export const SmartAboutContactModal = memo(function SmartAboutContactModal() { ); diff --git a/ts/state/smart/CompositionArea.preload.tsx b/ts/state/smart/CompositionArea.preload.tsx index 94b4a479f4..85411361ef 100644 --- a/ts/state/smart/CompositionArea.preload.tsx +++ b/ts/state/smart/CompositionArea.preload.tsx @@ -31,6 +31,7 @@ import { getSelectedMessageIds, isMissingRequiredProfileSharing, } from '../selectors/conversations.dom.js'; +import { getSharedGroupNames } from '../../util/sharedGroupNames.dom.js'; import { getDefaultConversationColor, getEmojiSkinToneDefault, @@ -307,7 +308,7 @@ export const SmartCompositionArea = memo(function SmartCompositionArea({ blockConversation={blockConversation} reportSpam={reportSpam} deleteConversation={deleteConversation} - sharedGroupNames={conversation.sharedGroupNames} + getSharedGroupNames={getSharedGroupNames} // Signal Conversation isSignalConversation={isSignalConversation(conversation)} isMuted={isConversationMuted(conversation)} diff --git a/ts/state/smart/ContactModal.preload.tsx b/ts/state/smart/ContactModal.preload.tsx index 63f660ea3c..efe0053891 100644 --- a/ts/state/smart/ContactModal.preload.tsx +++ b/ts/state/smart/ContactModal.preload.tsx @@ -51,7 +51,6 @@ export const SmartContactModal = memo(function SmartContactModal() { const { removeMemberFromGroup, showConversation, - updateConversationModelSharedGroups, toggleAdmin, blockConversation, startAvatarDownload, @@ -102,7 +101,6 @@ export const SmartContactModal = memo(function SmartContactModal() { toggleAdmin={toggleAdmin} togglePip={togglePip} toggleSafetyNumberModal={toggleSafetyNumberModal} - updateConversationModelSharedGroups={updateConversationModelSharedGroups} viewUserStories={viewUserStories} /> ); diff --git a/ts/state/smart/ContactSpoofingReviewDialog.preload.tsx b/ts/state/smart/ContactSpoofingReviewDialog.preload.tsx index 39bfc1a5bb..280b866831 100644 --- a/ts/state/smart/ContactSpoofingReviewDialog.preload.tsx +++ b/ts/state/smart/ContactSpoofingReviewDialog.preload.tsx @@ -2,7 +2,7 @@ // SPDX-License-Identifier: AGPL-3.0-only import React, { memo, useCallback } from 'react'; -import { useSelector } from 'react-redux'; +import { useSelector, useStore } from 'react-redux'; import lodash from 'lodash'; import type { StateType } from '../reducer.preload.js'; import { ContactSpoofingReviewDialog } from '../../components/conversation/ContactSpoofingReviewDialog.dom.js'; @@ -12,6 +12,7 @@ import { getConversationByServiceIdSelector, getSafeConversationWithSameTitle, } from '../selectors/conversations.dom.js'; +import { getSharedGroupNames } from '../../util/sharedGroupNames.dom.js'; import { getOwn } from '../../util/getOwn.std.js'; import { assertDev } from '../../util/assert.std.js'; import { ContactSpoofingType } from '../../util/contactSpoofing.std.js'; @@ -45,7 +46,6 @@ export const SmartContactSpoofingReviewDialog = memo( blockConversation, deleteConversation, removeMember, - updateSharedGroups, } = useConversationsActions(); const { showContactModal, toggleSignalConnectionsModal } = useGlobalModalActions(); @@ -57,6 +57,14 @@ export const SmartContactSpoofingReviewDialog = memo( ); const conversation = getConversation(conversationId); + const store = useStore(); + const getSharedGroupNamesForId = useCallback( + (convId: string) => { + return getSharedGroupNames(store.getState(), convId); + }, + [store] + ); + // Just binding the options argument const safeConversationSelector = useCallback( (state: StateType) => { @@ -78,7 +86,6 @@ export const SmartContactSpoofingReviewDialog = memo( getPreferredBadge, i18n, removeMember, - updateSharedGroups, showContactModal, toggleSignalConnectionsModal, theme, @@ -100,6 +107,7 @@ export const SmartContactSpoofingReviewDialog = memo( conversation: collision, isSignalConnection: isSignalConnection(collision), oldName: getOwn(previouslyAcknowledgedTitlesById, collision.id), + sharedGroupNames: getSharedGroupNamesForId(collision.id), })) ); @@ -127,10 +135,12 @@ export const SmartContactSpoofingReviewDialog = memo( const possiblyUnsafe = { conversation: possiblyUnsafeConvo, isSignalConnection: isSignalConnection(possiblyUnsafeConvo), + sharedGroupNames: getSharedGroupNamesForId(possiblyUnsafeConvo.id), }; const safe = { conversation: safeConvo, isSignalConnection: isSignalConnection(safeConvo), + sharedGroupNames: getSharedGroupNamesForId(safeConvo.id), }; return ( diff --git a/ts/state/smart/ConversationHeader.preload.tsx b/ts/state/smart/ConversationHeader.preload.tsx index c806c65438..2a49d27023 100644 --- a/ts/state/smart/ConversationHeader.preload.tsx +++ b/ts/state/smart/ConversationHeader.preload.tsx @@ -340,7 +340,6 @@ export const SmartConversationHeader = memo(function SmartConversationHeader({ onViewUserStories={onViewUserStories} outgoingCallButtonStyle={outgoingCallButtonStyle} setLocalDeleteWarningShown={setLocalDeleteWarningShown} - sharedGroupNames={conversation.sharedGroupNames} theme={theme} contactSpoofingWarning={contactSpoofingWarning} renderCollidingAvatars={renderCollidingAvatars} diff --git a/ts/state/smart/HeroRow.preload.tsx b/ts/state/smart/HeroRow.preload.tsx index 6d64e57f87..47f7ac949c 100644 --- a/ts/state/smart/HeroRow.preload.tsx +++ b/ts/state/smart/HeroRow.preload.tsx @@ -13,6 +13,7 @@ import { getConversationSelector, getPendingAvatarDownloadSelector, } from '../selectors/conversations.dom.js'; +import { useSharedGroupNamesOnMount } from '../../util/sharedGroupNames.dom.js'; import { type ConversationType, useConversationsActions, @@ -55,6 +56,7 @@ export const SmartHeroRow = memo(function SmartHeroRow({ getConversationByServiceIdSelector ); const isPendingAvatarDownload = useSelector(getPendingAvatarDownloadSelector); + const sharedGroupNames = useSharedGroupNamesOnMount(id); const conversation = conversationSelector(id); if (conversation == null) { throw new Error(`Did not find conversation ${id} in state!`); @@ -70,7 +72,7 @@ export const SmartHeroRow = memo(function SmartHeroRow({ const isSignalConversationValue = isSignalConversation(conversation); const fromOrAddedByTrustedContact = isFromOrAddedByTrustedContact(conversation); - const { pushPanelForConversation, startAvatarDownload, updateSharedGroups } = + const { pushPanelForConversation, startAvatarDownload } = useConversationsActions(); const { toggleAboutContactModal, toggleProfileNameWarningModal } = useGlobalModalActions(); @@ -92,7 +94,6 @@ export const SmartHeroRow = memo(function SmartHeroRow({ nicknameFamilyName, phoneNumber, profileName, - sharedGroupNames, title, type, } = conversation; @@ -134,7 +135,6 @@ export const SmartHeroRow = memo(function SmartHeroRow({ title={title} toggleAboutContactModal={toggleAboutContactModal} toggleProfileNameWarningModal={toggleProfileNameWarningModal} - updateSharedGroups={updateSharedGroups} viewUserStories={viewUserStories} /> ); diff --git a/ts/state/smart/TimelineItem.preload.tsx b/ts/state/smart/TimelineItem.preload.tsx index aec19438ab..e1a6804ac2 100644 --- a/ts/state/smart/TimelineItem.preload.tsx +++ b/ts/state/smart/TimelineItem.preload.tsx @@ -25,6 +25,7 @@ import { getTargetedMessage, getTargetedMessageSource, } from '../selectors/conversations.dom.js'; +import { getSharedGroupNames } from '../../util/sharedGroupNames.dom.js'; import { useTimelineItem } from '../selectors/timeline.preload.js'; import { areMessagesInSameGroup, @@ -202,6 +203,7 @@ export const SmartTimelineItem = memo(function SmartTimelineItem( containerWidthBreakpoint={containerWidthBreakpoint} conversationId={conversationId} getPreferredBadge={getPreferredBadge} + getSharedGroupNames={getSharedGroupNames} isNextItemCallingNotification={isNextItemCallingNotification} isTargeted={isTargeted} renderAudioAttachment={renderAudioAttachment} diff --git a/ts/test-electron/routineProfileRefresh_test.preload.ts b/ts/test-electron/routineProfileRefresh_test.preload.ts index 46e198a455..3b0ac0d4f4 100644 --- a/ts/test-electron/routineProfileRefresh_test.preload.ts +++ b/ts/test-electron/routineProfileRefresh_test.preload.ts @@ -58,7 +58,6 @@ describe('routineProfileRefresh', () => { quotedMessageId: null, sealedSender: 1, sentMessageCount: 1, - sharedGroupNames: [], timestamp: Date.now(), type: 'private', serviceId: generateAci(), diff --git a/ts/test-electron/state/ducks/stories_test.preload.ts b/ts/test-electron/state/ducks/stories_test.preload.ts index d041c028e8..d5640d5314 100644 --- a/ts/test-electron/state/ducks/stories_test.preload.ts +++ b/ts/test-electron/state/ducks/stories_test.preload.ts @@ -80,7 +80,6 @@ describe('both/state/ducks/stories', () => { id: conversationId, serviceId, isMe: false, - sharedGroupNames: [], title: title || casual.username, type: 'direct' as const, }; diff --git a/ts/test-electron/state/selectors/messages_test.preload.ts b/ts/test-electron/state/selectors/messages_test.preload.ts index 9c99b02bd2..769d1364e8 100644 --- a/ts/test-electron/state/selectors/messages_test.preload.ts +++ b/ts/test-electron/state/selectors/messages_test.preload.ts @@ -272,7 +272,6 @@ describe('state/selectors/messages', () => { type: 'direct', title: 'Test conversation', isMe: false, - sharedGroupNames: [], acceptedMessageRequest: true, badges: [], }; diff --git a/ts/test-electron/util/encryptProfileData_test.preload.ts b/ts/test-electron/util/encryptProfileData_test.preload.ts index 26b2bd15fe..5a0b682111 100644 --- a/ts/test-electron/util/encryptProfileData_test.preload.ts +++ b/ts/test-electron/util/encryptProfileData_test.preload.ts @@ -33,7 +33,6 @@ describe('encryptProfileData', () => { badges: [], id: '', isMe: true, - sharedGroupNames: [], title: '', type: 'direct' as const, }; diff --git a/ts/test-helpers/fakeCallLink.std.ts b/ts/test-helpers/fakeCallLink.std.ts index a8cf47886b..b932dc57f6 100644 --- a/ts/test-helpers/fakeCallLink.std.ts +++ b/ts/test-helpers/fakeCallLink.std.ts @@ -84,7 +84,6 @@ export function getDefaultCallLinkConversation( type: 'callLink', isMe: false, title, - sharedGroupNames: [], acceptedMessageRequest: true, badges: [], }; diff --git a/ts/test-helpers/getDefaultConversation.std.ts b/ts/test-helpers/getDefaultConversation.std.ts index 2c5852e503..9ed21fb7f8 100644 --- a/ts/test-helpers/getDefaultConversation.std.ts +++ b/ts/test-helpers/getDefaultConversation.std.ts @@ -42,7 +42,6 @@ export function getDefaultConversation( isMe: false, lastUpdated: casual.unix_time, markedUnread: Boolean(overrideProps.markedUnread), - sharedGroupNames: [], title: `${firstName} ${lastName}`, titleNoDefault: `${firstName} ${lastName}`, titleShortNoDefault: firstName, @@ -91,7 +90,6 @@ export function getDefaultGroup( markedUnread: Boolean(overrideProps.markedUnread), membersCount: memberships.length, memberships, - sharedGroupNames: [], title: casual.title, serviceId: generateAci(), acknowledgedGroupNameCollisions: {}, diff --git a/ts/test-mock/messaging/expire_timer_version_test.node.ts b/ts/test-mock/messaging/expire_timer_version_test.node.ts index e46a99b4ec..fe67da52d2 100644 --- a/ts/test-mock/messaging/expire_timer_version_test.node.ts +++ b/ts/test-mock/messaging/expire_timer_version_test.node.ts @@ -27,6 +27,30 @@ const IdentifierType = Proto.ManifestRecord.Identifier.Type; const DAY = 24 * 3600; +function isProfileKeyUpdate(flags: number | null | undefined): boolean { + if (flags == null) { + return false; + } + // eslint-disable-next-line no-bitwise + return (flags & Proto.DataMessage.Flags.PROFILE_KEY_UPDATE) !== 0; +} + +async function waitForNonProfileKeyUpdateMessage( + device: PrimaryDevice +): Promise<{ body: string; dataMessage: Proto.IDataMessage }> { + const maxAttempts = 5; + for (let i = 0; i < maxAttempts; i += 1) { + // eslint-disable-next-line no-await-in-loop + const message = await device.waitForMessage(); + if (isProfileKeyUpdate(message.dataMessage.flags)) { + debug('Skipping profile key update'); + continue; + } + return message; + } + throw new Error(`No message with body after ${maxAttempts} attempts`); +} + describe('messaging/expireTimerVersion', function (this: Mocha.Suite) { this.timeout(durations.MINUTE); @@ -255,7 +279,8 @@ describe('messaging/expireTimerVersion', function (this: Mocha.Suite) { } debug('Getting message to contact'); - const { body, dataMessage } = await stranger.waitForMessage(); + const { body, dataMessage } = + await waitForNonProfileKeyUpdateMessage(stranger); assert.strictEqual(body, 'Hello'); assert.strictEqual(dataMessage.expireTimer, scenario.finalTimer); diff --git a/ts/test-node/state/ducks/donations_test.preload.ts b/ts/test-node/state/ducks/donations_test.preload.ts index a4b4ce3b20..83151c6d5d 100644 --- a/ts/test-node/state/ducks/donations_test.preload.ts +++ b/ts/test-node/state/ducks/donations_test.preload.ts @@ -76,7 +76,6 @@ describe('donations duck', () => { title: 'Me', acceptedMessageRequest: true, isMe: true, - sharedGroupNames: [], }); const createRootState = (me: ConversationType): StateType => { diff --git a/ts/types/Stories.std.ts b/ts/types/Stories.std.ts index 2b02c9b516..7e67a26341 100644 --- a/ts/types/Stories.std.ts +++ b/ts/types/Stories.std.ts @@ -23,7 +23,6 @@ export type ReplyType = { | 'isMe' | 'name' | 'profileName' - | 'sharedGroupNames' | 'title' >; body?: string; @@ -54,7 +53,6 @@ export type ConversationStoryType = { | 'id' | 'name' | 'profileName' - | 'sharedGroupNames' | 'sortedGroupMembers' | 'title' | 'left' @@ -94,7 +92,6 @@ export type StoryViewType = { | 'isMe' | 'name' | 'profileName' - | 'sharedGroupNames' | 'title' | 'serviceId' >; diff --git a/ts/util/callLinks.std.ts b/ts/util/callLinks.std.ts index 095505c4f9..2762069f8a 100644 --- a/ts/util/callLinks.std.ts +++ b/ts/util/callLinks.std.ts @@ -69,7 +69,6 @@ export function callLinkToConversation( color: getColorForCallLink(rootKey), isMe: false, title: name || i18n('icu:calling__call-link-default-title'), - sharedGroupNames: [], acceptedMessageRequest: true, badges: [], }; @@ -84,7 +83,6 @@ export function getPlaceholderCallLinkConversation( type: 'callLink', isMe: false, title: i18n('icu:calling__call-link-default-title'), - sharedGroupNames: [], acceptedMessageRequest: true, badges: [], }; diff --git a/ts/util/findAndFormatContact.preload.ts b/ts/util/findAndFormatContact.preload.ts index 25fcf84fdc..ca36852cd5 100644 --- a/ts/util/findAndFormatContact.preload.ts +++ b/ts/util/findAndFormatContact.preload.ts @@ -11,7 +11,6 @@ const PLACEHOLDER_CONTACT: ConversationType = { badges: [], id: PLACEHOLDER_CONTACT_ID, isMe: false, - sharedGroupNames: [], title: window.SignalContext.i18n('icu:unknownContact'), type: 'direct', }; @@ -42,7 +41,6 @@ export function findAndFormatContact(identifier?: string): ConversationType { id: 'phone-only', isMe: false, phoneNumber, - sharedGroupNames: [], title: phoneNumber, type: 'direct', }; diff --git a/ts/util/getConversation.preload.ts b/ts/util/getConversation.preload.ts index 7477cea36d..c248a62166 100644 --- a/ts/util/getConversation.preload.ts +++ b/ts/util/getConversation.preload.ts @@ -262,14 +262,12 @@ export function getConversation(model: ConversationModel): ConversationType { ...(isDirectConversation(attributes) ? { type: 'direct' as const, - sharedGroupNames: attributes.sharedGroupNames || EMPTY_ARRAY, } : { type: 'group' as const, acknowledgedGroupNameCollisions: attributes.acknowledgedGroupNameCollisions || EMPTY_GROUP_COLLISIONS, - sharedGroupNames: EMPTY_ARRAY, storySendMode: attributes.storySendMode ?? StorySendMode.IfActive, }), voiceNotePlaybackRate: attributes.voiceNotePlaybackRate, diff --git a/ts/util/sharedGroupNames.dom.ts b/ts/util/sharedGroupNames.dom.ts new file mode 100644 index 0000000000..d93b2be7ba --- /dev/null +++ b/ts/util/sharedGroupNames.dom.ts @@ -0,0 +1,87 @@ +// Copyright 2026 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +import { useState } from 'react'; +import { useStore } from 'react-redux'; +import type { StateType } from '../state/reducer.preload.js'; +import type { ConversationType } from '../state/ducks/conversations.preload.js'; +import { + getConversationLookup, + getAllComposableConversations, +} from '../state/selectors/conversations.dom.js'; + +const EMPTY_ARRAY: ReadonlyArray = []; + +function getServiceIdForConversation( + state: StateType, + conversationId: string +): string | null { + const conversation = getConversationLookup(state)[conversationId]; + if (!conversation || conversation.type !== 'direct') { + return null; + } + return conversation.serviceId ?? null; +} + +function findSharedGroups( + allConversations: ReadonlyArray, + serviceId: string +): ReadonlyArray { + return allConversations.filter( + conv => + conv.type === 'group' && conv.memberships?.some(m => m.aci === serviceId) + ); +} + +/** + * Returns shared group names sorted by recency. + */ +export function getSharedGroupNames( + state: StateType, + conversationId: string +): ReadonlyArray { + const serviceId = getServiceIdForConversation(state, conversationId); + if (!serviceId) { + return EMPTY_ARRAY; + } + + const groups = findSharedGroups( + getAllComposableConversations(state), + serviceId + ); + if (groups.length === 0) { + return EMPTY_ARRAY; + } + + return [...groups] + .sort((a, b) => (b.timestamp ?? 0) - (a.timestamp ?? 0)) + .map(g => g.title ?? ''); +} + +/** + * Type for the selector function. Exported so smart components can pass + * the selector down to presentational components that need shared group + * names but don't have direct Redux access. + */ +export type GetSharedGroupNamesType = typeof getSharedGroupNames; + +/** + * React hook that fetches shared group names ONCE on mount. + * Does not subscribe to Redux - value is stale after mount. + * Use for performance when live updates aren't needed. + * + * @param conversationId - The conversation to get shared groups for + * @param getSharedGroupNamesFn - Passed from smart components via props; + * allows presentational components to use this selector without Redux access + */ +export function useSharedGroupNamesOnMount( + conversationId: string, + getSharedGroupNamesFn?: GetSharedGroupNamesType +): ReadonlyArray { + const store = useStore(); + const [names] = useState(() => { + const selector = getSharedGroupNamesFn ?? getSharedGroupNames; + return selector(store.getState(), conversationId); + }); + return names; +} diff --git a/ts/util/shouldRespondWithProfileKey.dom.ts b/ts/util/shouldRespondWithProfileKey.dom.ts index 6ce93858bb..0bd7c950c0 100644 --- a/ts/util/shouldRespondWithProfileKey.dom.ts +++ b/ts/util/shouldRespondWithProfileKey.dom.ts @@ -2,22 +2,22 @@ // SPDX-License-Identifier: AGPL-3.0-only import type { ConversationModel } from '../models/conversations.preload.js'; +import { isInSystemContacts } from './isInSystemContacts.std.js'; +import { getSharedGroupNames } from './sharedGroupNames.dom.js'; import { isMe } from './whatTypeOfConversation.dom.js'; -export async function shouldRespondWithProfileKey( +export function shouldRespondWithProfileKey( sender: ConversationModel -): Promise { - if (isMe(sender.attributes) || !sender.getAccepted() || sender.isBlocked()) { +): boolean { + if (isMe(sender.attributes) || sender.isBlocked()) { return false; } - // We do message check in an attempt to avoid a database lookup. If someone was EVER in - // a shared group with us, we should've shared our profile key with them in the past, - // so we should respond with a profile key now. - if (sender.get('sharedGroupNames')?.length) { + if (isInSystemContacts(sender.attributes) || sender.get('profileSharing')) { return true; } - await sender.updateSharedGroups(); - return Boolean(sender.get('sharedGroupNames')?.length); + const state = window.reduxStore.getState(); + const sharedGroupNames = getSharedGroupNames(state, sender.id); + return sharedGroupNames.length > 0; }