diff --git a/ts/components/conversation/message/message-item/ReadableMessage.tsx b/ts/components/conversation/message/message-item/ReadableMessage.tsx index a47aa12bb..eba13a26b 100644 --- a/ts/components/conversation/message/message-item/ReadableMessage.tsx +++ b/ts/components/conversation/message/message-item/ReadableMessage.tsx @@ -11,9 +11,8 @@ import { showScrollToBottomButton, } from '../../../../state/ducks/conversations'; import { - areMoreBottomMessagesBeingFetched, - areMoreTopMessagesBeingFetched, - getFirstUnreadMessageId, + areMoreMessagesBeingFetched, + getConversationHasUnread, getLoadedMessagesLength, getMostRecentMessageId, getOldestMessageId, @@ -68,9 +67,8 @@ export const ReadableMessage = (props: ReadableMessageProps) => { const mostRecentMessageId = useSelector(getMostRecentMessageId); const oldestMessageId = useSelector(getOldestMessageId); const youngestMessageId = useSelector(getYoungestMessageId); - const fetchingTopMore = useSelector(areMoreTopMessagesBeingFetched); - const fetchingBottomMore = useSelector(areMoreBottomMessagesBeingFetched); - const conversationHasUnread = Boolean(useSelector(getFirstUnreadMessageId)); + const fetchingMoreInProgress = useSelector(areMoreMessagesBeingFetched); + const conversationHasUnread = useSelector(getConversationHasUnread); const shouldMarkReadWhenVisible = isUnread; const [didScroll, setDidScroll] = useState(false); @@ -118,7 +116,7 @@ export const ReadableMessage = (props: ReadableMessageProps) => { inView === true && isAppFocused && oldestMessageId === messageId && - !fetchingTopMore && + !fetchingMoreInProgress && selectedConversationKey ) { debouncedTriggerLoadMoreTop(selectedConversationKey, oldestMessageId); @@ -128,7 +126,7 @@ export const ReadableMessage = (props: ReadableMessageProps) => { inView === true && isAppFocused && youngestMessageId === messageId && - !fetchingBottomMore && + !fetchingMoreInProgress && selectedConversationKey ) { debouncedTriggerLoadMoreBottom(selectedConversationKey, youngestMessageId); @@ -155,8 +153,7 @@ export const ReadableMessage = (props: ReadableMessageProps) => { selectedConversationKey, mostRecentMessageId, oldestMessageId, - fetchingTopMore, - fetchingBottomMore, + fetchingMoreInProgress, isAppFocused, loadedMessagesLength, receivedAt, diff --git a/ts/data/data.ts b/ts/data/data.ts index 4b1cf634d..2304fe512 100644 --- a/ts/data/data.ts +++ b/ts/data/data.ts @@ -800,6 +800,11 @@ export async function getLastMessagesByConversation( return new MessageCollection(messages); } +export async function getLastMessageIdInConversation(conversationId: string) { + const collection = await getLastMessagesByConversation(conversationId, 1, true); + return collection.models.length ? collection.models[0].id : null; +} + export async function getLastMessageInConversation(conversationId: string) { const messages = await channels.getLastMessagesByConversation(conversationId, 1); for (const message of messages) { diff --git a/ts/receiver/queuedJob.ts b/ts/receiver/queuedJob.ts index e4e6bcd89..c3b74e668 100644 --- a/ts/receiver/queuedJob.ts +++ b/ts/receiver/queuedJob.ts @@ -349,8 +349,8 @@ export async function handleMessageJob( ); } + // save the message model to the db and it save the messageId generated to our copy const id = await messageModel.commit(); - messageModel.set({ id }); // Note that this can save the message again, if jobs were queued. We need to diff --git a/ts/state/ducks/conversations.ts b/ts/state/ducks/conversations.ts index e0fc22a04..cfaf5d214 100644 --- a/ts/state/ducks/conversations.ts +++ b/ts/state/ducks/conversations.ts @@ -2,6 +2,7 @@ import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit'; import { getConversationController } from '../../session/conversations'; import { getFirstUnreadMessageIdInConversation, + getLastMessageIdInConversation, getLastMessageInConversation, getMessagesByConversation, getOldestMessageInConversation, @@ -277,8 +278,7 @@ export type ConversationsStateType = { selectedMessageIds: Array; lightBox?: LightBoxOptions; quotedMessage?: ReplyingToMessageProps; - areMoreTopMessagesBeingFetched: boolean; - areMoreBottomMessagesBeingFetched: boolean; + areMoreMessagesBeingFetched: boolean; /** * oldTopMessageId should only be set when, as the user scroll up we trigger a load of more top messages. @@ -298,6 +298,12 @@ export type ConversationsStateType = { */ oldBottomMessageId: string | null; + /** + * Contains the most recent message id for this conversation. + * This is the one at the bottom, if the most recent page of the conversation was loaded + */ + mostRecentMessageId: string | null; + showScrollButton: boolean; animateQuotedMessageId?: string; shouldHighlightMessage: boolean; @@ -424,14 +430,14 @@ export function getEmptyConversationState(): ConversationsStateType { messageDetailProps: undefined, showRightPanel: false, selectedMessageIds: [], - areMoreTopMessagesBeingFetched: false, - areMoreBottomMessagesBeingFetched: false, + areMoreMessagesBeingFetched: false, // top or bottom showScrollButton: false, mentionMembers: [], firstUnreadMessageId: undefined, oldTopMessageId: null, oldBottomMessageId: null, shouldHighlightMessage: false, + mostRecentMessageId: null, }; } @@ -739,6 +745,7 @@ const conversationsSlice = createSlice({ action: PayloadAction<{ conversationKey: string; firstUnreadIdOnOpen: string | undefined; + mostRecentMessageIdOnOpen: string | null; initialMessages: Array; }> ) { @@ -748,25 +755,26 @@ const conversationsSlice = createSlice({ return { conversationLookup: state.conversationLookup, - + mostRecentMessageId: action.payload.mostRecentMessageIdOnOpen, selectedConversation: action.payload.conversationKey, - areMoreTopMessagesBeingFetched: false, - areMoreBottomMessagesBeingFetched: false, + firstUnreadMessageId: action.payload.firstUnreadIdOnOpen, messages: action.payload.initialMessages, + + areMoreMessagesBeingFetched: false, showRightPanel: false, selectedMessageIds: [], + lightBox: undefined, messageDetailProps: undefined, quotedMessage: undefined, nextMessageToPlay: undefined, - showScrollButton: false, + showScrollButton: Boolean(action.payload.firstUnreadIdOnOpen), animateQuotedMessageId: undefined, shouldHighlightMessage: false, oldTopMessageId: null, oldBottomMessageId: null, mentionMembers: [], - firstUnreadMessageId: action.payload.firstUnreadIdOnOpen, }; }, openConversationToSpecificMessage( @@ -775,14 +783,16 @@ const conversationsSlice = createSlice({ conversationKey: string; messageIdToNavigateTo: string; shouldHighlightMessage: boolean; + mostRecentMessageIdOnOpen: string | null; + initialMessages: Array; }> ) { return { ...state, selectedConversation: action.payload.conversationKey, - areMoreTopMessagesBeingFetched: false, - areMoreBottomMessagesBeingFetched: false, + mostRecentMessageIdOnOpen: action.payload.mostRecentMessageIdOnOpen, + areMoreMessagesBeingFetched: false, messages: action.payload.initialMessages, showScrollButton: true, animateQuotedMessageId: action.payload.messageIdToNavigateTo, @@ -845,9 +855,12 @@ const conversationsSlice = createSlice({ // Add reducers for additional action types here, and handle loading state as needed builder.addCase( fetchTopMessagesForConversation.fulfilled, - (state: ConversationsStateType, action: PayloadAction) => { + ( + state: ConversationsStateType, + action: PayloadAction + ): ConversationsStateType => { if (!action.payload) { - return state; + return { ...state, areMoreMessagesBeingFetched: false }; } // this is called once the messages are loaded from the db for the currently selected conversation const { messagesProps, conversationKey, oldTopMessageId } = action.payload; @@ -857,23 +870,34 @@ const conversationsSlice = createSlice({ ...state, oldTopMessageId, messages: messagesProps, - areMoreTopMessagesBeingFetched: false, + areMoreMessagesBeingFetched: false, }; } return state; } ); - builder.addCase(fetchTopMessagesForConversation.pending, (state: ConversationsStateType) => { - state.areMoreTopMessagesBeingFetched = true; - }); - builder.addCase(fetchTopMessagesForConversation.rejected, (state: ConversationsStateType) => { - state.areMoreTopMessagesBeingFetched = false; - }); + builder.addCase( + fetchTopMessagesForConversation.pending, + (state: ConversationsStateType): ConversationsStateType => { + state.areMoreMessagesBeingFetched = true; + return state; + } + ); + builder.addCase( + fetchTopMessagesForConversation.rejected, + (state: ConversationsStateType): ConversationsStateType => { + state.areMoreMessagesBeingFetched = false; + return state; + } + ); builder.addCase( fetchBottomMessagesForConversation.fulfilled, - (state: ConversationsStateType, action: PayloadAction) => { + ( + state: ConversationsStateType, + action: PayloadAction + ): ConversationsStateType => { if (!action.payload) { - return state; + return { ...state, areMoreMessagesBeingFetched: false }; } // this is called once the messages are loaded from the db for the currently selected conversation const { messagesProps, conversationKey, oldBottomMessageId } = action.payload; @@ -883,19 +907,24 @@ const conversationsSlice = createSlice({ ...state, oldBottomMessageId, messages: messagesProps, - areMoreBottomMessagesBeingFetched: false, + areMoreMessagesBeingFetched: false, }; } return state; } ); - builder.addCase(fetchBottomMessagesForConversation.pending, (state: ConversationsStateType) => { - state.areMoreBottomMessagesBeingFetched = true; - }); + builder.addCase( + fetchBottomMessagesForConversation.pending, + (state: ConversationsStateType): ConversationsStateType => { + state.areMoreMessagesBeingFetched = true; + return state; + } + ); builder.addCase( fetchBottomMessagesForConversation.rejected, - (state: ConversationsStateType) => { - state.areMoreBottomMessagesBeingFetched = false; + (state: ConversationsStateType): ConversationsStateType => { + state.areMoreMessagesBeingFetched = false; + return state; } ); }, @@ -963,6 +992,7 @@ export async function openConversationWithMessages(args: { }) { const { conversationKey, messageId } = args; const firstUnreadIdOnOpen = await getFirstUnreadMessageIdInConversation(conversationKey); + const mostRecentMessageIdOnOpen = await getLastMessageIdInConversation(conversationKey); const initialMessages = await getMessages({ conversationKey, @@ -973,6 +1003,7 @@ export async function openConversationWithMessages(args: { actions.openConversationExternal({ conversationKey, firstUnreadIdOnOpen, + mostRecentMessageIdOnOpen, initialMessages, }) ); @@ -990,10 +1021,14 @@ export async function openConversationToSpecificMessage(args: { messageId: messageIdToNavigateTo, }); + const mostRecentMessageIdOnOpen = await getLastMessageIdInConversation(conversationKey); + + // we do not care about the firstunread message id when opening to a specific message window.inboxStore?.dispatch( actions.openConversationToSpecificMessage({ conversationKey, messageIdToNavigateTo, + mostRecentMessageIdOnOpen, shouldHighlightMessage, initialMessages: messagesAroundThisMessage, }) diff --git a/ts/state/selectors/conversations.ts b/ts/state/selectors/conversations.ts index 2ea9b0fa4..6bfd3859f 100644 --- a/ts/state/selectors/conversations.ts +++ b/ts/state/selectors/conversations.ts @@ -160,12 +160,15 @@ export const getSortedMessagesOfSelectedConversation = createSelector( } ); -export const getFirstUnreadMessageId = createSelector( - getConversations, - (state: ConversationsStateType): string | undefined => { - return state.firstUnreadMessageId; - } -); +const getFirstUnreadMessageId = createSelector(getConversations, (state: ConversationsStateType): + | string + | undefined => { + return state.firstUnreadMessageId; +}); + +export const getConversationHasUnread = createSelector(getFirstUnreadMessageId, unreadId => { + return Boolean(unreadId); +}); export type MessagePropsType = | 'group-notification' @@ -592,14 +595,9 @@ export const getQuotedMessage = createSelector( (state: ConversationsStateType): ReplyingToMessageProps | undefined => state.quotedMessage ); -export const areMoreTopMessagesBeingFetched = createSelector( +export const areMoreMessagesBeingFetched = createSelector( getConversations, - (state: ConversationsStateType): boolean => state.areMoreTopMessagesBeingFetched || false -); - -export const areMoreBottomMessagesBeingFetched = createSelector( - getConversations, - (state: ConversationsStateType): boolean => state.areMoreBottomMessagesBeingFetched || false + (state: ConversationsStateType): boolean => state.areMoreMessagesBeingFetched || false ); export const getShowScrollButton = createSelector( @@ -685,10 +683,14 @@ function sortMessages( return messagesSorted; } +/** + * This returns the most recent message id in the database. This is not the most recent message shown, + * but the most recent one, which could still not be loaded. + */ export const getMostRecentMessageId = createSelector( - getSortedMessagesOfSelectedConversation, - (messages: Array): string | undefined => { - return messages.length ? messages[0].propsForMessage.id : undefined; + getConversations, + (state: ConversationsStateType): string | null => { + return state.mostRecentMessageId; } );