diff --git a/ts/components/SendStoryModal.tsx b/ts/components/SendStoryModal.tsx index 0e21931d5..18db37a35 100644 --- a/ts/components/SendStoryModal.tsx +++ b/ts/components/SendStoryModal.tsx @@ -8,6 +8,7 @@ import { SearchInput } from './SearchInput'; import { filterAndSortConversationsByRecent } from '../util/filterAndSortConversations'; import type { ConversationType } from '../state/ducks/conversations'; +import type { ConversationWithStoriesType } from '../state/selectors/conversations'; import type { LocalizerType } from '../types/Util'; import type { PreferredBadgeSelectorType } from '../state/selectors/badges'; import type { PropsType as StoriesSettingsModalPropsType } from './StoriesSettingsModal'; @@ -46,7 +47,7 @@ export type PropsType = { distributionLists: Array; getPreferredBadge: PreferredBadgeSelectorType; groupConversations: Array; - groupStories: Array; + groupStories: Array; hasFirstStoryPostExperience: boolean; ourConversationId: string | undefined; i18n: LocalizerType; @@ -712,6 +713,7 @@ export function SendStoryModal({ isMe sharedGroupNames={me.sharedGroupNames} size={AvatarSize.THIRTY_TWO} + storyRing={undefined} title={me.title} /> ) : ( @@ -759,7 +761,7 @@ export function SendStoryModal({ ); }; - const renderGroup = (group: ConversationType) => { + const renderGroup = (group: ConversationWithStoriesType) => { return ( diff --git a/ts/components/StoriesSettingsModal.tsx b/ts/components/StoriesSettingsModal.tsx index 987bff6b0..72badd5d9 100644 --- a/ts/components/StoriesSettingsModal.tsx +++ b/ts/components/StoriesSettingsModal.tsx @@ -8,6 +8,7 @@ import Measure from 'react-measure'; import { noop } from 'lodash'; import type { ConversationType } from '../state/ducks/conversations'; +import type { ConversationWithStoriesType } from '../state/selectors/conversations'; import type { LocalizerType } from '../types/Util'; import type { PreferredBadgeSelectorType } from '../state/selectors/badges'; import type { Row } from './ConversationList'; @@ -43,7 +44,7 @@ import { strictAssert } from '../util/assert'; export type PropsType = { candidateConversations: Array; distributionLists: Array; - groupStories: Array; + groupStories: Array; signalConnections: Array; getPreferredBadge: PreferredBadgeSelectorType; hideStoriesSettings: () => unknown; diff --git a/ts/state/selectors/conversations.ts b/ts/state/selectors/conversations.ts index 524c9dc02..72f945c53 100644 --- a/ts/state/selectors/conversations.ts +++ b/ts/state/selectors/conversations.ts @@ -58,6 +58,12 @@ import { isSignalConversation } from '../../util/isSignalConversation'; import { reduce } from '../../util/iterables'; import { getConversationTitleForPanelType } from '../../util/getConversationTitleForPanelType'; import type { PanelRenderType } from '../../types/Panels'; +import type { HasStories } from '../../types/Stories'; +import { getHasStoriesSelector } from './stories2'; + +export type ConversationWithStoriesType = ConversationType & { + hasStories?: HasStories; +}; let placeholderContact: ConversationType; export const getPlaceholderContact = (): ConversationType => { @@ -575,15 +581,22 @@ export const selectMostRecentActiveStoryTimestampByGroupOrDistributionList = export const getGroupStories = createSelector( getConversationLookup, getConversationIdsWithStories, + getHasStoriesSelector, ( conversationLookup: ConversationLookupType, - conversationIdsWithStories: Set - ): Array => { - return Object.values(conversationLookup).filter( - conversation => - isGroupInStoryMode(conversation, conversationIdsWithStories) && - !conversation.left - ); + conversationIdsWithStories: Set, + hasStoriesSelector + ): Array => { + return Object.values(conversationLookup) + .filter( + conversation => + isGroupInStoryMode(conversation, conversationIdsWithStories) && + !conversation.left + ) + .map(conversation => ({ + ...conversation, + hasStories: hasStoriesSelector(conversation.id), + })); } ); diff --git a/ts/state/selectors/stories.ts b/ts/state/selectors/stories.ts index 998d57ae2..779f34c0e 100644 --- a/ts/state/selectors/stories.ts +++ b/ts/state/selectors/stories.ts @@ -22,11 +22,7 @@ import type { StoriesStateType, AddStoryData, } from '../ducks/stories'; -import { - HasStories, - MY_STORY_ID, - ResolvedSendStatus, -} from '../../types/Stories'; +import { MY_STORY_ID, ResolvedSendStatus } from '../../types/Stories'; import { ReadStatus } from '../../messages/MessageReadStatus'; import { SendStatus } from '../../messages/MessageSendState'; import { canReply } from './message'; @@ -38,7 +34,6 @@ import { } from './conversations'; import { getUserConversationId } from './user'; import { getDistributionListSelector } from './storyDistributionLists'; -import { getStoriesEnabled } from './items'; import { calculateExpirationTimestamp } from '../../util/expirationTimer'; import { getMessageIdForLogging } from '../../util/idForLogging'; import * as log from '../../logging/log'; @@ -499,32 +494,6 @@ export const getStoriesNotificationCount = createSelector( } ); -export const getHasStoriesSelector = createSelector( - getStoriesEnabled, - getStoriesState, - (isEnabled, { stories }) => - (conversationId?: string): HasStories | undefined => { - if (!isEnabled || !conversationId) { - return; - } - - const conversationStories = stories.filter( - story => story.conversationId === conversationId - ); - - if (!conversationStories.length) { - return; - } - - return conversationStories.some( - story => - story.readStatus === ReadStatus.Unread && !story.deletedForEveryone - ) - ? HasStories.Unread - : HasStories.Read; - } -); - export const getStoryByIdSelector = createSelector( getStoriesState, getUserConversationId, diff --git a/ts/state/selectors/stories2.ts b/ts/state/selectors/stories2.ts new file mode 100644 index 000000000..916856740 --- /dev/null +++ b/ts/state/selectors/stories2.ts @@ -0,0 +1,40 @@ +// Copyright 2023 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +import { createSelector } from 'reselect'; +import { ReadStatus } from '../../messages/MessageReadStatus'; +import { HasStories } from '../../types/Stories'; +import { getStoriesEnabled } from './items'; + +import type { StateType } from '../reducer'; +import type { StoriesStateType } from '../ducks/stories'; + +const getStoriesState = (state: StateType): StoriesStateType => state.stories; + +// This exists solely to avoid circular import dependencies since it is required +// by the conversations selector. +export const getHasStoriesSelector = createSelector( + getStoriesEnabled, + getStoriesState, + (isEnabled, { stories }) => + (conversationId?: string): HasStories | undefined => { + if (!isEnabled || !conversationId) { + return; + } + + const conversationStories = stories.filter( + story => story.conversationId === conversationId + ); + + if (!conversationStories.length) { + return; + } + + return conversationStories.some( + story => + story.readStatus === ReadStatus.Unread && !story.deletedForEveryone + ) + ? HasStories.Unread + : HasStories.Read; + } +); diff --git a/ts/state/smart/ContactModal.tsx b/ts/state/smart/ContactModal.tsx index c6fb15f79..0ca384646 100644 --- a/ts/state/smart/ContactModal.tsx +++ b/ts/state/smart/ContactModal.tsx @@ -11,7 +11,7 @@ import { getAreWeASubscriber } from '../selectors/items'; import { getIntl, getTheme } from '../selectors/user'; import { getBadgesSelector } from '../selectors/badges'; import { getConversationSelector } from '../selectors/conversations'; -import { getHasStoriesSelector } from '../selectors/stories'; +import { getHasStoriesSelector } from '../selectors/stories2'; const mapStateToProps = (state: StateType): PropsDataType => { const { contactId, conversationId } = diff --git a/ts/state/smart/ConversationHeader.tsx b/ts/state/smart/ConversationHeader.tsx index 73be65a7a..ec6f92cb5 100644 --- a/ts/state/smart/ConversationHeader.tsx +++ b/ts/state/smart/ConversationHeader.tsx @@ -18,7 +18,7 @@ import { import { CallMode } from '../../types/Calling'; import { getActiveCall, isAnybodyElseInGroupCall } from '../ducks/calling'; import { getConversationCallMode } from '../ducks/conversations'; -import { getHasStoriesSelector } from '../selectors/stories'; +import { getHasStoriesSelector } from '../selectors/stories2'; import { getOwn } from '../../util/getOwn'; import { getUserACI, getIntl, getTheme } from '../selectors/user'; import { isConversationSMSOnly } from '../../util/isConversationSMSOnly'; diff --git a/ts/state/smart/HeroRow.tsx b/ts/state/smart/HeroRow.tsx index 3e9dad04f..cc5174a4d 100644 --- a/ts/state/smart/HeroRow.tsx +++ b/ts/state/smart/HeroRow.tsx @@ -9,7 +9,7 @@ import { ConversationHero } from '../../components/conversation/ConversationHero import type { StateType } from '../reducer'; import { getPreferredBadgeSelector } from '../selectors/badges'; import { getIntl, getTheme } from '../selectors/user'; -import { getHasStoriesSelector } from '../selectors/stories'; +import { getHasStoriesSelector } from '../selectors/stories2'; import { isSignalConversation } from '../../util/isSignalConversation'; type ExternalProps = {