diff --git a/ts/models/messages.ts b/ts/models/messages.ts index 3aec6d294..15fcc645f 100644 --- a/ts/models/messages.ts +++ b/ts/models/messages.ts @@ -99,6 +99,7 @@ import { getActiveCall, } from '../state/selectors/calling'; import { getAccountSelector } from '../state/selectors/accounts'; +import { getContactNameColorSelector } from '../state/selectors/conversations'; import { MessageReceipts, MessageReceiptType, @@ -378,6 +379,14 @@ export class MessageModel extends window.Backbone.Model { const accountSelector = getAccountSelector(state); return accountSelector(identifier); }, + contactNameColorSelector: ( + conversationId: string, + contactId: string + ) => { + const state = window.reduxStore.getState(); + const contactNameColorSelector = getContactNameColorSelector(state); + return contactNameColorSelector(conversationId, contactId); + }, }), errors, contacts, diff --git a/ts/state/selectors/conversations.ts b/ts/state/selectors/conversations.ts index b21e43277..e150ecf4f 100644 --- a/ts/state/selectors/conversations.ts +++ b/ts/state/selectors/conversations.ts @@ -680,6 +680,71 @@ export const getCachedSelectorForMessage = createSelector( } ); +const getCachedConversationMemberColorsSelector = createSelector( + getConversationSelector, + getUserConversationId, + ( + conversationSelector: GetConversationByIdType, + ourConversationId: string + ) => { + return memoizee( + (conversationId: string) => { + const contactNameColors: Map = new Map(); + const { + sortedGroupMembers = [], + type, + id: theirId, + } = conversationSelector(conversationId); + + if (type === 'direct') { + contactNameColors.set(ourConversationId, ContactNameColors[0]); + contactNameColors.set(theirId, ContactNameColors[0]); + return contactNameColors; + } + + [...sortedGroupMembers] + .sort((left, right) => + String(left.uuid) > String(right.uuid) ? 1 : -1 + ) + .forEach((member, i) => { + contactNameColors.set( + member.id, + ContactNameColors[i % ContactNameColors.length] + ); + }); + + return contactNameColors; + }, + { max: 100 } + ); + } +); + +export type ContactNameColorSelectorType = ( + conversationId: string, + contactId: string +) => ContactNameColorType; + +export const getContactNameColorSelector = createSelector( + getCachedConversationMemberColorsSelector, + conversationMemberColorsSelector => { + return ( + conversationId: string, + contactId: string + ): ContactNameColorType => { + const contactNameColors = conversationMemberColorsSelector( + conversationId + ); + const color = contactNameColors.get(contactId); + if (!color) { + window.log.warn(`No color generated for contact ${contactId}`); + return ContactNameColors[0]; + } + return color; + }; + } +); + type GetMessageByIdType = (id: string) => TimelineItemType | undefined; export const getMessageSelector = createSelector( getCachedSelectorForMessage, @@ -693,6 +758,7 @@ export const getMessageSelector = createSelector( getCallSelector, getActiveCall, getAccountSelector, + getContactNameColorSelector, ( messageSelector: typeof getPropsForBubble, messageLookup: MessageLookupType, @@ -704,7 +770,8 @@ export const getMessageSelector = createSelector( ourConversationId: string, callSelector: CallSelectorType, activeCall: undefined | CallStateType, - accountSelector: AccountSelectorType + accountSelector: AccountSelectorType, + contactNameColorSelector: ContactNameColorSelectorType ): GetMessageByIdType => { return (id: string) => { const message = messageLookup[id]; @@ -720,6 +787,7 @@ export const getMessageSelector = createSelector( regionCode, selectedMessageId: selectedMessage?.id, selectedMessageCounter: selectedMessage?.counter, + contactNameColorSelector, callSelector, activeCall, accountSelector, @@ -838,54 +906,6 @@ export const getInvitedContactsForNewlyCreatedGroup = createSelector( ) ); -const getCachedConversationMemberColorsSelector = createSelector( - getConversationSelector, - (conversationSelector: GetConversationByIdType) => { - return memoizee( - (conversationId: string) => { - const contactNameColors: Map = new Map(); - const { sortedGroupMembers = [] } = conversationSelector( - conversationId - ); - - [...sortedGroupMembers] - .sort((left, right) => - String(left.uuid) > String(right.uuid) ? 1 : -1 - ) - .forEach((member, i) => { - contactNameColors.set( - member.id, - ContactNameColors[i % ContactNameColors.length] - ); - }); - - return contactNameColors; - }, - { max: 100 } - ); - } -); - -export const getContactNameColorSelector = createSelector( - getCachedConversationMemberColorsSelector, - conversationMemberColorsSelector => { - return ( - conversationId: string, - contactId: string - ): ContactNameColorType => { - const contactNameColors = conversationMemberColorsSelector( - conversationId - ); - const color = contactNameColors.get(contactId); - if (!color) { - window.log.warn(`No color generated for contact ${contactId}`); - return ContactNameColors[0]; - } - return color; - }; - } -); - export const getConversationsWithCustomColorSelector = createSelector( getAllConversations, conversations => { diff --git a/ts/state/selectors/message.ts b/ts/state/selectors/message.ts index 18ece6691..762d2cb94 100644 --- a/ts/state/selectors/message.ts +++ b/ts/state/selectors/message.ts @@ -55,10 +55,12 @@ import { isMoreRecentThan } from '../../util/timestamp'; import { ConversationType } from '../ducks/conversations'; +import { AccountSelectorType } from './accounts'; import { CallSelectorType, CallStateType } from './calling'; import { GetConversationByIdType, isMissingRequiredProfileSharing, + ContactNameColorSelectorType, } from './conversations'; import { SendStatus, @@ -100,7 +102,8 @@ export type GetPropsForBubbleOptions = Readonly<{ regionCode: string; callSelector: CallSelectorType; activeCall?: CallStateType; - accountSelector: (identifier?: string) => boolean; + accountSelector: AccountSelectorType; + contactNameColorSelector: ContactNameColorSelectorType; }>; export function isIncoming( @@ -450,10 +453,13 @@ export type GetPropsForMessageOptions = Pick< GetPropsForBubbleOptions, | 'conversationSelector' | 'ourConversationId' + | 'ourUuid' + | 'ourNumber' | 'selectedMessageId' | 'selectedMessageCounter' | 'regionCode' | 'accountSelector' + | 'contactNameColorSelector' >; type ShallowPropsType = Pick< @@ -462,6 +468,7 @@ type ShallowPropsType = Pick< | 'canDownload' | 'canReply' | 'contact' + | 'contactNameColor' | 'conversationColor' | 'conversationId' | 'conversationType' @@ -497,12 +504,15 @@ const getShallowPropsForMessage = createSelectorCreator(memoizeByRoot, isEqual)( accountSelector, conversationSelector, ourConversationId, + ourNumber, + ourUuid, regionCode, selectedMessageId, selectedMessageCounter, + contactNameColorSelector, }: GetPropsForMessageOptions ): ShallowPropsType => { - const { expireTimer, expirationStartTimestamp } = message; + const { expireTimer, expirationStartTimestamp, conversationId } = message; const expirationLength = expireTimer ? expireTimer * 1000 : undefined; const expirationTimestamp = expirationStartTimestamp && expirationLength @@ -522,14 +532,26 @@ const getShallowPropsForMessage = createSelectorCreator(memoizeByRoot, isEqual)( {} ).emoji; + const author = getContact(message, { + conversationSelector, + ourConversationId, + ourNumber, + ourUuid, + }); + const contactNameColor = contactNameColorSelector( + conversationId, + author.id + ); + return { canDeleteForEveryone: canDeleteForEveryone(message), canDownload: canDownload(message, conversationSelector), canReply: canReply(message, ourConversationId, conversationSelector), contact: getPropsForEmbeddedContact(message, regionCode, accountSelector), + contactNameColor, conversationColor: conversation?.conversationColor ?? ConversationColors[0], - conversationId: message.conversationId, + conversationId, conversationType: isGroup ? 'group' : 'direct', customColor: conversation?.customColor, deletedForEveryone: message.deletedForEveryone || false, diff --git a/ts/state/smart/TimelineItem.tsx b/ts/state/smart/TimelineItem.tsx index 148d4f909..4358e98c3 100644 --- a/ts/state/smart/TimelineItem.tsx +++ b/ts/state/smart/TimelineItem.tsx @@ -10,7 +10,6 @@ import { StateType } from '../reducer'; import { TimelineItem } from '../../components/conversation/TimelineItem'; import { getIntl, getInteractionMode, getTheme } from '../selectors/user'; import { - getContactNameColorSelector, getConversationSelector, getMessageSelector, getSelectedMessage, @@ -44,14 +43,6 @@ const mapStateToProps = (state: StateType, props: ExternalProps) => { const messageSelector = getMessageSelector(state); const item = messageSelector(id); - if (item?.type === 'message' && item.data.conversationType === 'group') { - const { author } = item.data; - item.data.contactNameColor = getContactNameColorSelector(state)( - conversationId, - author.id - ); - } - const selectedMessage = getSelectedMessage(state); const isSelected = Boolean(selectedMessage && id === selectedMessage.id); diff --git a/ts/test-both/state/selectors/conversations_test.ts b/ts/test-both/state/selectors/conversations_test.ts index ba28a7c5d..1a516da9f 100644 --- a/ts/test-both/state/selectors/conversations_test.ts +++ b/ts/test-both/state/selectors/conversations_test.ts @@ -1737,6 +1737,7 @@ describe('both/state/selectors/conversations', () => { it('returns the right color order sorted by UUID ASC', () => { const group = makeConversation('group'); + group.type = 'group'; group.sortedGroupMembers = [ makeConversationWithUuid('zyx'), makeConversationWithUuid('vut'), @@ -1766,5 +1767,28 @@ describe('both/state/selectors/conversations', () => { assert.equal(contactNameColorSelector('group', 'vut'), '330'); assert.equal(contactNameColorSelector('group', 'zyx'), '230'); }); + + it('returns the right colors for direct conversation', () => { + const direct = makeConversation('theirId'); + const emptyState = getEmptyRootState(); + const state = { + ...emptyState, + user: { + ...emptyState.user, + ourConversationId: 'us', + }, + conversations: { + ...getEmptyState(), + conversationLookup: { + direct, + }, + }, + }; + + const contactNameColorSelector = getContactNameColorSelector(state); + + assert.equal(contactNameColorSelector('direct', 'theirId'), '200'); + assert.equal(contactNameColorSelector('direct', 'us'), '200'); + }); }); });