From 90489133327c5cd5df0d9dd55d3991fc9f74338a Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Thu, 7 Jan 2021 10:51:20 +1100 Subject: [PATCH] split conversation reducer for convo logic also - fix ordering of messages for open groups, when they are added - fix the way the firstMessageOfSeries for just created clsoed groups --- .../conversation/SessionMessagesList.tsx | 1 + .../conversations/ConversationController.ts | 593 +++++++++--------- ts/state/ducks/conversations.ts | 241 ++++--- ts/state/smart/SessionConversation.tsx | 1 - 4 files changed, 449 insertions(+), 387 deletions(-) diff --git a/ts/components/session/conversation/SessionMessagesList.tsx b/ts/components/session/conversation/SessionMessagesList.tsx index 9f766850b..adcae6bdd 100644 --- a/ts/components/session/conversation/SessionMessagesList.tsx +++ b/ts/components/session/conversation/SessionMessagesList.tsx @@ -325,6 +325,7 @@ export class SessionMessagesList extends React.Component { messageProps.i18n = window.i18n; messageProps.selected = selected; messageProps.firstMessageOfSeries = firstMessageOfSeries; + messageProps.multiSelectMode = multiSelectMode; messageProps.onSelectMessage = this.props.selectMessage; messageProps.onDeleteMessage = this.props.deleteMessage; diff --git a/ts/session/conversations/ConversationController.ts b/ts/session/conversations/ConversationController.ts index ed741f7af..1b2738be5 100644 --- a/ts/session/conversations/ConversationController.ts +++ b/ts/session/conversations/ConversationController.ts @@ -1,336 +1,339 @@ import { - ConversationAttributes, - ConversationModel, + ConversationAttributes, + ConversationModel, } from '../../../js/models/conversations'; import { BlockedNumberController } from '../../util'; // It's not only data from the db which is stored on the MessageController entries, we could fetch this again. What we cannot fetch from the db and which is stored here is all listeners a particular messages is linked to for instance. We will be able to get rid of this once we don't use backbone models at all export class ConversationController { - private static instance: ConversationController | null; - private readonly conversations: any; - private _initialFetchComplete: boolean = false; - private _initialPromise?: Promise; - - private constructor() { - this.conversations = new window.Whisper.ConversationCollection(); + private static instance: ConversationController | null; + private readonly conversations: any; + private _initialFetchComplete: boolean = false; + private _initialPromise?: Promise; + + private constructor() { + this.conversations = new window.Whisper.ConversationCollection(); + } + + public static getInstance() { + if (ConversationController.instance) { + return ConversationController.instance; } - - public static getInstance() { - if (ConversationController.instance) { - return ConversationController.instance; - } - ConversationController.instance = new ConversationController(); - return ConversationController.instance; + ConversationController.instance = new ConversationController(); + return ConversationController.instance; + } + + public get(id: string): ConversationModel { + if (!this._initialFetchComplete) { + throw new Error( + 'ConversationController.get() needs complete initial fetch' + ); } - public get(id: string): ConversationModel { - if (!this._initialFetchComplete) { - throw new Error( - 'ConversationController.get() needs complete initial fetch' - ); - } + return this.conversations.get(id); + } - return this.conversations.get(id); + public getOrThrow(id: string) { + if (!this._initialFetchComplete) { + throw new Error( + 'ConversationController.get() needs complete initial fetch' + ); } - public getOrThrow(id: string) { - if (!this._initialFetchComplete) { - throw new Error( - 'ConversationController.get() needs complete initial fetch' - ); - } + const convo = this.conversations.get(id); - const convo = this.conversations.get(id); + if (convo) { + return convo; + } + throw new Error( + `Conversation ${id} does not exist on ConversationController.get()` + ); + } + // Needed for some model setup which happens during the initial fetch() call below + public getUnsafe(id: string) { + return this.conversations.get(id); + } + + public dangerouslyCreateAndAdd(attributes: ConversationAttributes) { + return this.conversations.add(attributes); + } + + public getOrCreate(id: string, type: string) { + if (typeof id !== 'string') { + throw new TypeError("'id' must be a string"); + } - if (convo) { - return convo; - } - throw new Error( - `Conversation ${id} does not exist on ConversationController.get()` - ); + if (type !== 'private' && type !== 'group') { + throw new TypeError( + `'type' must be 'private' or 'group'; got: '${type}'` + ); } - // Needed for some model setup which happens during the initial fetch() call below - public getUnsafe(id: string) { - return this.conversations.get(id); + + if (!this._initialFetchComplete) { + throw new Error( + 'ConversationController.get() needs complete initial fetch' + ); } - public dangerouslyCreateAndAdd(attributes: ConversationAttributes) { - return this.conversations.add(attributes); + let conversation = this.conversations.get(id); + if (conversation) { + return conversation; } - public getOrCreate(id: string, type: string) { - if (typeof id !== 'string') { - throw new TypeError("'id' must be a string"); - } - - if (type !== 'private' && type !== 'group') { - throw new TypeError( - `'type' must be 'private' or 'group'; got: '${type}'` - ); - } - - if (!this._initialFetchComplete) { - throw new Error( - 'ConversationController.get() needs complete initial fetch' - ); - } - - let conversation = this.conversations.get(id); - if (conversation) { - return conversation; - } - - conversation = this.conversations.add({ - id, - type, - version: 2, - } as any); - - const create = async () => { - if (!conversation.isValid()) { - const validationError = conversation.validationError || {}; - window.log.error( - 'Contact is not valid. Not saving, but adding to collection:', - conversation.idForLogging(), - validationError.stack - ); - - return conversation; - } - - try { - await window.Signal.Data.saveConversation(conversation.attributes, { - Conversation: window.Whisper.Conversation, - }); - } catch (error) { - window.log.error( - 'Conversation save failed! ', - id, - type, - 'Error:', - error && error.stack ? error.stack : error - ); - throw error; - } - - return conversation; - }; - - conversation.initialPromise = create(); - conversation.initialPromise.then(async () => { - if (!conversation.isPublic() && !conversation.isRss()) { - await Promise.all([ - conversation.updateProfileAvatar(), - // NOTE: we request snodes updating the cache, but ignore the result - window.SnodePool.getSnodesFor(id), - ]); - } - if (window.inboxStore) { - conversation.on('change', this.updateReduxConvoChanged); - window.inboxStore.dispatch( - window.actionsCreators.conversationAdded( - conversation.id, - conversation.getProps() - ) - ); - } - }); + conversation = this.conversations.add({ + id, + type, + version: 2, + } as any); + + const create = async () => { + if (!conversation.isValid()) { + const validationError = conversation.validationError || {}; + window.log.error( + 'Contact is not valid. Not saving, but adding to collection:', + conversation.idForLogging(), + validationError.stack + ); return conversation; - } + } - public getContactProfileNameOrShortenedPubKey(pubKey: string): string { - const conversation = ConversationController.getInstance().get(pubKey); - if (!conversation) { - return pubKey; - } - return conversation.getContactProfileNameOrShortenedPubKey(); + try { + await window.Signal.Data.saveConversation(conversation.attributes, { + Conversation: window.Whisper.Conversation, + }); + } catch (error) { + window.log.error( + 'Conversation save failed! ', + id, + type, + 'Error:', + error && error.stack ? error.stack : error + ); + throw error; + } + + return conversation; + }; + + conversation.initialPromise = create(); + conversation.initialPromise.then(async () => { + if (window.inboxStore) { + conversation.on('change', this.updateReduxConvoChanged); + window.inboxStore.dispatch( + window.actionsCreators.conversationAdded( + conversation.id, + conversation.getProps() + ) + ); + } + if (!conversation.isPublic() && !conversation.isRss()) { + await Promise.all([ + conversation.updateProfileAvatar(), + // NOTE: we request snodes updating the cache, but ignore the result + window.SnodePool.getSnodesFor(id), + ]); + } + }); + + return conversation; + } + + public getContactProfileNameOrShortenedPubKey(pubKey: string): string { + const conversation = ConversationController.getInstance().get(pubKey); + if (!conversation) { + return pubKey; } + return conversation.getContactProfileNameOrShortenedPubKey(); + } - public getContactProfileNameOrFullPubKey(pubKey: string): string { - const conversation = this.conversations.get(pubKey); - if (!conversation) { - return pubKey; - } - return conversation.getContactProfileNameOrFullPubKey(); + public getContactProfileNameOrFullPubKey(pubKey: string): string { + const conversation = this.conversations.get(pubKey); + if (!conversation) { + return pubKey; } + return conversation.getContactProfileNameOrFullPubKey(); + } - public isMediumGroup(hexEncodedGroupPublicKey: string): boolean { - const convo = this.conversations.get(hexEncodedGroupPublicKey); - if (convo) { - return convo.isMediumGroup(); - } - return false; + public isMediumGroup(hexEncodedGroupPublicKey: string): boolean { + const convo = this.conversations.get(hexEncodedGroupPublicKey); + if (convo) { + return convo.isMediumGroup(); } - - public async getOrCreateAndWait(id: any, type: string) { - const initialPromise = this._initialPromise !== undefined ? this._initialPromise : Promise.resolve(); - return initialPromise.then(() => { - if (!id) { - return Promise.reject( - new Error('getOrCreateAndWait: invalid id passed.') - ); - } - const pubkey = id && id.key ? id.key : id; - const conversation = this.getOrCreate(pubkey, type); - - if (conversation) { - return conversation.initialPromise.then(() => conversation); - } - - return Promise.reject( - new Error('getOrCreateAndWait: did not get conversation') - ); - }); + return false; + } + + public async getOrCreateAndWait(id: any, type: string) { + const initialPromise = + this._initialPromise !== undefined + ? this._initialPromise + : Promise.resolve(); + return initialPromise.then(() => { + if (!id) { + return Promise.reject( + new Error('getOrCreateAndWait: invalid id passed.') + ); + } + const pubkey = id && id.key ? id.key : id; + const conversation = this.getOrCreate(pubkey, type); + + if (conversation) { + return conversation.initialPromise.then(() => conversation); + } + + return Promise.reject( + new Error('getOrCreateAndWait: did not get conversation') + ); + }); + } + + public async getAllGroupsInvolvingId(id: String) { + const groups = await window.Signal.Data.getAllGroupsInvolvingId(id, { + ConversationCollection: window.Whisper.ConversationCollection, + }); + return groups.map((group: any) => this.conversations.add(group)); + } + + public async deleteContact(id: string) { + if (typeof id !== 'string') { + throw new TypeError("'id' must be a string"); } - public async getAllGroupsInvolvingId(id: String) { - const groups = await window.Signal.Data.getAllGroupsInvolvingId(id, { - ConversationCollection: window.Whisper.ConversationCollection, - }); - return groups.map((group: any) => this.conversations.add(group)); + if (!this._initialFetchComplete) { + throw new Error( + 'ConversationController.get() needs complete initial fetch' + ); } - public async deleteContact(id: string) { - if (typeof id !== 'string') { - throw new TypeError("'id' must be a string"); - } - - if (!this._initialFetchComplete) { - throw new Error( - 'ConversationController.get() needs complete initial fetch' - ); - } - - const conversation = this.conversations.get(id); - if (!conversation) { - return; - } - - // Close group leaving - if (conversation.isClosedGroup()) { - await conversation.leaveGroup(); - } else if (conversation.isPublic()) { - const channelAPI = await conversation.getPublicSendData(); - if (channelAPI === null) { - window.log.warn(`Could not get API for public conversation ${id}`); - } else { - channelAPI.serverAPI.partChannel(channelAPI.channelId); - } - } else if (conversation.isPrivate()) { - const deviceIds = await window.textsecure.storage.protocol.getDeviceIds( - id - ); - await Promise.all( - deviceIds.map((deviceId: string) => { - const address = new window.libsignal.SignalProtocolAddress( - id, - deviceId - ); - const sessionCipher = new window.libsignal.SessionCipher( - window.textsecure.storage.protocol, - address - ); - return sessionCipher.deleteAllSessionsForDevice(); - }) - ); - } - - await conversation.destroyMessages(); - - await window.Signal.Data.removeConversation(id, { - Conversation: window.Whisper.Conversation, - }); - conversation.off('change', this.updateReduxConvoChanged); - this.conversations.remove(conversation); - if (window.inboxStore) { - window.inboxStore.dispatch( - window.actionsCreators.conversationRemoved(conversation.id) - ); - } + const conversation = this.conversations.get(id); + if (!conversation) { + return; } - public getConversations(): Array { - return Array.from(this.conversations.models.values()); + // Close group leaving + if (conversation.isClosedGroup()) { + await conversation.leaveGroup(); + } else if (conversation.isPublic()) { + const channelAPI = await conversation.getPublicSendData(); + if (channelAPI === null) { + window.log.warn(`Could not get API for public conversation ${id}`); + } else { + channelAPI.serverAPI.partChannel(channelAPI.channelId); + } + } else if (conversation.isPrivate()) { + const deviceIds = await window.textsecure.storage.protocol.getDeviceIds( + id + ); + await Promise.all( + deviceIds.map((deviceId: string) => { + const address = new window.libsignal.SignalProtocolAddress( + id, + deviceId + ); + const sessionCipher = new window.libsignal.SessionCipher( + window.textsecure.storage.protocol, + address + ); + return sessionCipher.deleteAllSessionsForDevice(); + }) + ); } - public async load() { - window.log.info('ConversationController: starting initial fetch'); - - if (this.conversations.length) { - throw new Error('ConversationController: Already loaded!'); - } - - const load = async () => { - try { - const collection = await window.Signal.Data.getAllConversations({ - ConversationCollection: window.Whisper.ConversationCollection, - }); - - this.conversations.add(collection.models); - - this._initialFetchComplete = true; - const promises: any = []; - this.conversations.forEach((conversation: ConversationModel) => { - if (!conversation.get('lastMessage')) { - // tslint:disable-next-line: no-void-expression - promises.push(conversation.updateLastMessage()); - } - - promises.concat([ - conversation.updateProfileName(), - conversation.updateProfileAvatar(), - ]); - }); - this.conversations.forEach((conversation: ConversationModel) => { - // register for change event on each conversation, and forward to redux - conversation.on('change', this.updateReduxConvoChanged); - }); - await Promise.all(promises); - - // Remove any unused images - window.profileImages.removeImagesNotInArray( - this.conversations.map((c: any) => c.id) - ); - window.log.info('ConversationController: done with initial fetch'); - } catch (error) { - window.log.error( - 'ConversationController: initial fetch failed', - error && error.stack ? error.stack : error - ); - throw error; - } - }; - await BlockedNumberController.load(); - - this._initialPromise = load(); - - return this._initialPromise; + await conversation.destroyMessages(); + + await window.Signal.Data.removeConversation(id, { + Conversation: window.Whisper.Conversation, + }); + conversation.off('change', this.updateReduxConvoChanged); + this.conversations.remove(conversation); + if (window.inboxStore) { + window.inboxStore.dispatch( + window.actionsCreators.conversationRemoved(conversation.id) + ); } + } - public loadPromise() { - return this._initialPromise; - } - public reset() { - this._initialPromise = Promise.resolve(); - this._initialFetchComplete = false; - if (window.inboxStore) { - this.conversations.forEach((convo: ConversationModel) => - convo.off('change', this.updateReduxConvoChanged) - ); - - window.inboxStore.dispatch( - window.actionsCreators.removeAllConversations() - ); - } - this.conversations.reset([]); + public getConversations(): Array { + return Array.from(this.conversations.models.values()); + } + + public async load() { + window.log.info('ConversationController: starting initial fetch'); + + if (this.conversations.length) { + throw new Error('ConversationController: Already loaded!'); } - private updateReduxConvoChanged(convo: ConversationModel) { - if (window.inboxStore) { - window.inboxStore.dispatch( - window.actionsCreators.conversationChanged(convo.id, convo.getProps()) - ); - } + const load = async () => { + try { + const collection = await window.Signal.Data.getAllConversations({ + ConversationCollection: window.Whisper.ConversationCollection, + }); + + this.conversations.add(collection.models); + + this._initialFetchComplete = true; + const promises: any = []; + this.conversations.forEach((conversation: ConversationModel) => { + if (!conversation.get('lastMessage')) { + // tslint:disable-next-line: no-void-expression + promises.push(conversation.updateLastMessage()); + } + + promises.concat([ + conversation.updateProfileName(), + conversation.updateProfileAvatar(), + ]); + }); + this.conversations.forEach((conversation: ConversationModel) => { + // register for change event on each conversation, and forward to redux + conversation.on('change', this.updateReduxConvoChanged); + }); + await Promise.all(promises); + + // Remove any unused images + window.profileImages.removeImagesNotInArray( + this.conversations.map((c: any) => c.id) + ); + window.log.info('ConversationController: done with initial fetch'); + } catch (error) { + window.log.error( + 'ConversationController: initial fetch failed', + error && error.stack ? error.stack : error + ); + throw error; + } + }; + await BlockedNumberController.load(); + + this._initialPromise = load(); + + return this._initialPromise; + } + + public loadPromise() { + return this._initialPromise; + } + public reset() { + this._initialPromise = Promise.resolve(); + this._initialFetchComplete = false; + if (window.inboxStore) { + this.conversations.forEach((convo: ConversationModel) => + convo.off('change', this.updateReduxConvoChanged) + ); + + window.inboxStore.dispatch( + window.actionsCreators.removeAllConversations() + ); + } + this.conversations.reset([]); + } + + private updateReduxConvoChanged(convo: ConversationModel) { + if (window.inboxStore) { + window.inboxStore.dispatch( + window.actionsCreators.conversationChanged(convo.id, convo.getProps()) + ); } + } } diff --git a/ts/state/ducks/conversations.ts b/ts/state/ducks/conversations.ts index 844c46bb7..d49e28c2e 100644 --- a/ts/state/ducks/conversations.ts +++ b/ts/state/ducks/conversations.ts @@ -4,6 +4,7 @@ import { Constants } from '../../session'; import { createAsyncThunk } from '@reduxjs/toolkit'; import { MessageModel } from '../../../js/models/messages'; import { ConversationController } from '../../session/conversations'; +import { StateType } from '../reducer'; // State @@ -122,11 +123,18 @@ async function getMessages( // Set first member of series here. const messageModels = messageSet.models; + const isPublic = conversation.isPublic(); + + const sortedMessage = sortMessages(messageModels, isPublic); + // no need to do that `firstMessageOfSeries` on a private chat if (conversation.isPrivate()) { - return messageModels; + return sortedMessage; } + return updateFirstMessageOfSeries(sortedMessage); +} +const updateFirstMessageOfSeries = (messageModels: Array) => { // messages are got from the more recent to the oldest, so we need to check if // the next messages in the list is still the same author. // The message is the first of the series if the next message is not from the same author @@ -138,13 +146,13 @@ async function getMessages( i < messageModels.length - 1 ? messageModels[i + 1].propsForMessage?.authorPhoneNumber : undefined; - if (i > 0 && currentSender === nextSender) { + if (i >= 0 && currentSender === nextSender) { firstMessageOfSeries = false; } messageModels[i].firstMessageOfSeries = firstMessageOfSeries; } return messageModels; -} +}; const fetchMessagesForConversation = createAsyncThunk( 'messages/fetchByConversationKey', @@ -422,6 +430,138 @@ function getEmptyState(): ConversationsStateType { }; } +function sortMessages( + messages: Array, + isPublic: boolean +): Array { + // we order by serverTimestamp for public convos + if (isPublic) { + return messages.sort( + (a: any, b: any) => + b.attributes.serverTimestamp - a.attributes.serverTimestamp + ); + } + return messages.sort( + (a: any, b: any) => b.attributes.timestamp - a.attributes.timestamp + ); +} + +function handleMessageAdded( + state: ConversationsStateType, + action: MessageAddedActionType +) { + const { messages } = state; + const { conversationKey, messageModel } = action.payload; + if (conversationKey === state.selectedConversation) { + const addedMessage = _.pick( + messageModel as any, + toPickFromMessageModel + ) as MessageTypeInConvo; + const messagesWithNewMessage = [...messages, addedMessage]; + const convo = state.conversationLookup[state.selectedConversation]; + const isPublic = convo?.isPublic || false; + + if (convo) { + const sortedMessage = sortMessages(messagesWithNewMessage, isPublic); + const updatedWithFirstMessageOfSeries = updateFirstMessageOfSeries( + sortedMessage + ); + + return { + ...state, + messages: updatedWithFirstMessageOfSeries, + }; + } + } + return state; +} + +function handleMessageChanged( + state: ConversationsStateType, + action: MessageChangedActionType +) { + const messageInStoreIndex = state?.messages?.findIndex( + m => m.id === action.payload.id + ); + if (messageInStoreIndex >= 0) { + const changedMessage = _.pick( + action.payload as any, + toPickFromMessageModel + ) as MessageTypeInConvo; + // we cannot edit the array directly, so slice the first part, insert our edited message, and slice the second part + const editedMessages = [ + ...state.messages.slice(0, messageInStoreIndex), + changedMessage, + ...state.messages.slice(messageInStoreIndex + 1), + ]; + + // reorder the messages depending on the timestamp (we might have an updated serverTimestamp now) + const updatedWithFirstMessageOfSeries = updateFirstMessageOfSeries( + editedMessages + ); + + return { + ...state, + messages: updatedWithFirstMessageOfSeries, + }; + } + + return state; +} + +function handleMessageExpiredOrDeleted( + state: ConversationsStateType, + action: MessageDeletedActionType | MessageExpiredActionType +) { + const { conversationKey, messageId } = action.payload; + if (conversationKey === state.selectedConversation) { + // search if we find this message id. + // we might have not loaded yet, so this case might not happen + const messageInStoreIndex = state?.messages.findIndex( + m => m.id === messageId + ); + if (messageInStoreIndex >= 0) { + // we cannot edit the array directly, so slice the first part, and slice the second part, + // keeping the index removed out + const editedMessages = [ + ...state.messages.slice(0, messageInStoreIndex), + ...state.messages.slice(messageInStoreIndex + 1), + ]; + + const updatedWithFirstMessageOfSeries = updateFirstMessageOfSeries( + editedMessages + ); + + // FIXME two other thing we have to do: + // * update the last message text if the message deleted was the last one + // * update the unread count of the convo if the message was the one counted as an unread + + return { + ...state, + messages: updatedWithFirstMessageOfSeries, + }; + } + + return state; + } + return state; +} + +function handleConversationReset( + state: ConversationsStateType, + action: ConversationResetActionType +) { + const { conversationKey } = action.payload; + if (conversationKey === state.selectedConversation) { + // just empty the list of messages + return { + ...state, + messages: [], + }; + } + return state; +} + // tslint:disable: cyclomatic-complexity // tslint:disable: max-func-body-length export function reducer( @@ -492,6 +632,7 @@ export function reducer( const { id } = payload; const oldSelectedConversation = state.selectedConversation; const newSelectedConversation = id; + if (newSelectedConversation !== oldSelectedConversation) { // empty the message list return { @@ -505,6 +646,8 @@ export function reducer( selectedConversation: id, }; } + + // this is called once the messages are loaded from the db for the currently selected conversation if (action.type === fetchMessagesForConversation.fulfilled.type) { const { messages, conversationKey } = action.payload as any; // double check that this update is for the shown convo @@ -521,102 +664,18 @@ export function reducer( } if (action.type === 'MESSAGE_CHANGED') { - const messageInStoreIndex = state?.messages?.findIndex( - m => m.id === action.payload.id - ); - if (messageInStoreIndex >= 0) { - const changedMessage = _.pick( - action.payload as any, - toPickFromMessageModel - ) as MessageTypeInConvo; - // we cannot edit the array directly, so slice the first part, insert our edited message, and slice the second part - const editedMessages = [ - ...state.messages.slice(0, messageInStoreIndex), - changedMessage, - ...state.messages.slice(messageInStoreIndex + 1), - ]; - return { - ...state, - messages: editedMessages, - }; - } - - return state; + return handleMessageChanged(state, action); } if (action.type === 'MESSAGE_ADDED') { - const { conversationKey, messageModel } = action.payload; - if (conversationKey === state.selectedConversation) { - const { messages } = state; - const addedMessage = _.pick( - messageModel as any, - toPickFromMessageModel - ) as MessageTypeInConvo; - const messagesWithNewMessage = [...messages, addedMessage]; - const convo = state.conversationLookup[state.selectedConversation]; - const isPublic = convo?.isPublic; - - if (convo && isPublic) { - return { - ...state, - messages: messagesWithNewMessage.sort( - (a: any, b: any) => - b.attributes.serverTimestamp - a.attributes.serverTimestamp - ), - }; - } - if (convo) { - return { - ...state, - messages: messagesWithNewMessage.sort( - (a, b) => b.attributes.timestamp - a.attributes.timestamp - ), - }; - } - } - return state; + return handleMessageAdded(state, action); } if (action.type === 'MESSAGE_EXPIRED' || action.type === 'MESSAGE_DELETED') { - const { conversationKey, messageId } = action.payload; - if (conversationKey === state.selectedConversation) { - // search if we find this message id. - // we might have not loaded yet, so this case might not happen - const messageInStoreIndex = state?.messages.findIndex( - m => m.id === messageId - ); - if (messageInStoreIndex >= 0) { - // we cannot edit the array directly, so slice the first part, and slice the second part, - // keeping the index removed out - const editedMessages = [ - ...state.messages.slice(0, messageInStoreIndex), - ...state.messages.slice(messageInStoreIndex + 1), - ]; - - // FIXME two other thing we have to do: - // * update the last message text if the message deleted was the last one - // * update the unread count of the convo if the message was one one counted as an unread - - return { - ...state, - messages: editedMessages, - }; - } - - return state; - } - return state; + return handleMessageExpiredOrDeleted(state, action); } if (action.type === 'CONVERSATION_RESET') { - const { conversationKey } = action.payload; - if (conversationKey === state.selectedConversation) { - // just empty the list of messages - return { - ...state, - messages: [], - }; - } - return state; + return handleConversationReset(state, action); } return state; diff --git a/ts/state/smart/SessionConversation.tsx b/ts/state/smart/SessionConversation.tsx index 70e29db4f..3a43e6bf6 100644 --- a/ts/state/smart/SessionConversation.tsx +++ b/ts/state/smart/SessionConversation.tsx @@ -9,7 +9,6 @@ const mapStateToProps = (state: StateType) => { (conversationKey && state.conversations.conversationLookup[conversationKey]) || null; - return { conversation, conversationKey,