diff --git a/ts/components/conversation/SessionLastSeenIndicator.tsx b/ts/components/conversation/SessionLastSeenIndicator.tsx
index ca41d3e44..7578288c1 100644
--- a/ts/components/conversation/SessionLastSeenIndicator.tsx
+++ b/ts/components/conversation/SessionLastSeenIndicator.tsx
@@ -1,4 +1,4 @@
-import React, { useContext, useLayoutEffect, useState } from 'react';
+import React, { useContext, useLayoutEffect } from 'react';
import { useSelector } from 'react-redux';
import styled from 'styled-components';
import { getQuotedMessageToAnimate } from '../../state/selectors/conversations';
@@ -35,19 +35,28 @@ const LastSeenText = styled.div`
color: var(--color-last-seen-indicator);
`;
-export const SessionLastSeenIndicator = (props: { messageId: string }) => {
+export const SessionLastSeenIndicator = (props: {
+ messageId: string;
+ didScroll: boolean;
+ setDidScroll: (scroll: boolean) => void;
+}) => {
// if this unread-indicator is not unique it's going to cause issues
- const [didScroll, setDidScroll] = useState(false);
const quotedMessageToAnimate = useSelector(getQuotedMessageToAnimate);
-
const scrollToLoadedMessage = useContext(ScrollToLoadedMessageContext);
- // if this unread-indicator is rendered,
- // we want to scroll here only if the conversation was not opened to a specific message
+ const { messageId, didScroll, setDidScroll } = props;
+
+ /**
+ * If this unread-indicator is rendered, we want to scroll here only if:
+ * 1. the conversation was not opened to a specific message (quoted message)
+ * 2. we already scrolled to this unread banner once for this convo https://github.com/oxen-io/session-desktop/issues/2308
+ *
+ * To achieve 2. we store the didScroll state in the parent and track the last rendered conversation in it.
+ */
useLayoutEffect(() => {
if (!quotedMessageToAnimate && !didScroll) {
- scrollToLoadedMessage(props.messageId, 'unread-indicator');
+ scrollToLoadedMessage(messageId, 'unread-indicator');
setDidScroll(true);
} else if (quotedMessageToAnimate) {
setDidScroll(true);
diff --git a/ts/components/conversation/SessionMessagesList.tsx b/ts/components/conversation/SessionMessagesList.tsx
index 34a114680..6feb5d4dd 100644
--- a/ts/components/conversation/SessionMessagesList.tsx
+++ b/ts/components/conversation/SessionMessagesList.tsx
@@ -1,4 +1,4 @@
-import React, { useLayoutEffect } from 'react';
+import React, { useLayoutEffect, useState } from 'react';
import { useSelector } from 'react-redux';
// tslint:disable-next-line: no-submodule-imports
import useKey from 'react-use/lib/useKey';
@@ -15,6 +15,7 @@ import {
import {
getOldBottomMessageId,
getOldTopMessageId,
+ getSelectedConversationKey,
getSortedMessagesTypesOfSelectedConversation,
} from '../../state/selectors/conversations';
import { GroupUpdateMessage } from './message/message-item/GroupUpdateMessage';
@@ -32,6 +33,8 @@ function isNotTextboxEvent(e: KeyboardEvent) {
return (e?.target as any)?.type === undefined;
}
+let previousRenderedConvo: string | undefined;
+
export const SessionMessagesList = (props: {
scrollAfterLoadMore: (
messageIdToScrollTo: string,
@@ -43,6 +46,9 @@ export const SessionMessagesList = (props: {
onEndPressed: () => void;
}) => {
const messagesProps = useSelector(getSortedMessagesTypesOfSelectedConversation);
+ const convoKey = useSelector(getSelectedConversationKey);
+
+ const [didScroll, setDidScroll] = useState(false);
const oldTopMessageId = useSelector(getOldTopMessageId);
const oldBottomMessageId = useSelector(getOldBottomMessageId);
@@ -84,12 +90,22 @@ export const SessionMessagesList = (props: {
}
});
+ if (didScroll && previousRenderedConvo !== convoKey) {
+ setDidScroll(false);
+ previousRenderedConvo = convoKey;
+ }
+
return (
<>
{messagesProps.map(messageProps => {
const messageId = messageProps.message.props.messageId;
const unreadIndicator = messageProps.showUnreadIndicator ? (
-
+
) : null;
const dateBreak =
@@ -100,24 +116,22 @@ export const SessionMessagesList = (props: {
messageId={messageId}
/>
) : null;
+
+ const componentToMerge = [dateBreak, unreadIndicator];
if (messageProps.message?.messageType === 'group-notification') {
const msgProps = messageProps.message.props as PropsForGroupUpdate;
- return [, dateBreak, unreadIndicator];
+ return [, ...componentToMerge];
}
if (messageProps.message?.messageType === 'group-invitation') {
const msgProps = messageProps.message.props as PropsForGroupInvitation;
- return [, dateBreak, unreadIndicator];
+ return [, ...componentToMerge];
}
if (messageProps.message?.messageType === 'message-request-response') {
const msgProps = messageProps.message.props as PropsForMessageRequestResponse;
- return [
- ,
- dateBreak,
- unreadIndicator,
- ];
+ return [, ...componentToMerge];
}
if (messageProps.message?.messageType === 'data-extraction') {
@@ -125,28 +139,27 @@ export const SessionMessagesList = (props: {
return [
,
- dateBreak,
- unreadIndicator,
+ ...componentToMerge,
];
}
if (messageProps.message?.messageType === 'timer-notification') {
const msgProps = messageProps.message.props as PropsForExpirationTimer;
- return [, dateBreak, unreadIndicator];
+ return [, ...componentToMerge];
}
if (messageProps.message?.messageType === 'call-notification') {
const msgProps = messageProps.message.props as PropsForCallNotification;
- return [, dateBreak, unreadIndicator];
+ return [, ...componentToMerge];
}
if (!messageProps) {
return null;
}
- return [, dateBreak, unreadIndicator];
+ return [, ...componentToMerge];
})}
>
);
diff --git a/ts/state/ducks/conversations.ts b/ts/state/ducks/conversations.ts
index cf10d7b39..178fe8618 100644
--- a/ts/state/ducks/conversations.ts
+++ b/ts/state/ducks/conversations.ts
@@ -366,6 +366,7 @@ type FetchedTopMessageResults = {
conversationKey: string;
messagesProps: Array;
oldTopMessageId: string | null;
+ newMostRecentMessageIdInConversation: string | null;
} | null;
export const fetchTopMessagesForConversation = createAsyncThunk(
@@ -379,6 +380,7 @@ export const fetchTopMessagesForConversation = createAsyncThunk(
}): Promise => {
// no need to load more top if we are already at the top
const oldestMessage = await Data.getOldestMessageInConversation(conversationKey);
+ const mostRecentMessage = await Data.getLastMessageInConversation(conversationKey);
if (!oldestMessage || oldestMessage.id === oldTopMessageId) {
window.log.info('fetchTopMessagesForConversation: we are already at the top');
@@ -393,6 +395,7 @@ export const fetchTopMessagesForConversation = createAsyncThunk(
conversationKey,
messagesProps,
oldTopMessageId,
+ newMostRecentMessageIdInConversation: mostRecentMessage?.id || null,
};
}
);
@@ -845,7 +848,12 @@ const conversationsSlice = createSlice({
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;
+ const {
+ messagesProps,
+ conversationKey,
+ oldTopMessageId,
+ newMostRecentMessageIdInConversation,
+ } = action.payload;
// double check that this update is for the shown convo
if (conversationKey === state.selectedConversation) {
return {
@@ -853,6 +861,7 @@ const conversationsSlice = createSlice({
oldTopMessageId,
messages: messagesProps,
areMoreMessagesBeingFetched: false,
+ mostRecentMessageId: newMostRecentMessageIdInConversation,
};
}
return state;
diff --git a/ts/state/selectors/conversations.ts b/ts/state/selectors/conversations.ts
index e3f0d8081..7ddfb3b0f 100644
--- a/ts/state/selectors/conversations.ts
+++ b/ts/state/selectors/conversations.ts
@@ -172,11 +172,12 @@ export const hasSelectedConversationIncomingMessages = createSelector(
}
);
-const getFirstUnreadMessageId = createSelector(getConversations, (state: ConversationsStateType):
- | string
- | undefined => {
- return state.firstUnreadMessageId;
-});
+export const getFirstUnreadMessageId = createSelector(
+ getConversations,
+ (state: ConversationsStateType): string | undefined => {
+ return state.firstUnreadMessageId;
+ }
+);
export const getConversationHasUnread = createSelector(getFirstUnreadMessageId, unreadId => {
return Boolean(unreadId);
@@ -215,10 +216,11 @@ export const getSortedMessagesTypesOfSelectedConversation = createSelector(
? messageTimestamp
: undefined;
+ const common = { showUnreadIndicator: isFirstUnread, showDateBreak };
+
if (msg.propsForDataExtractionNotification) {
return {
- showUnreadIndicator: isFirstUnread,
- showDateBreak,
+ ...common,
message: {
messageType: 'data-extraction',
props: { ...msg.propsForDataExtractionNotification, messageId: msg.propsForMessage.id },
@@ -228,8 +230,7 @@ export const getSortedMessagesTypesOfSelectedConversation = createSelector(
if (msg.propsForMessageRequestResponse) {
return {
- showUnreadIndicator: isFirstUnread,
- showDateBreak,
+ ...common,
message: {
messageType: 'message-request-response',
props: { ...msg.propsForMessageRequestResponse, messageId: msg.propsForMessage.id },
@@ -239,8 +240,7 @@ export const getSortedMessagesTypesOfSelectedConversation = createSelector(
if (msg.propsForGroupInvitation) {
return {
- showUnreadIndicator: isFirstUnread,
- showDateBreak,
+ ...common,
message: {
messageType: 'group-invitation',
props: { ...msg.propsForGroupInvitation, messageId: msg.propsForMessage.id },
@@ -250,8 +250,7 @@ export const getSortedMessagesTypesOfSelectedConversation = createSelector(
if (msg.propsForGroupUpdateMessage) {
return {
- showUnreadIndicator: isFirstUnread,
- showDateBreak,
+ ...common,
message: {
messageType: 'group-notification',
props: { ...msg.propsForGroupUpdateMessage, messageId: msg.propsForMessage.id },
@@ -261,8 +260,7 @@ export const getSortedMessagesTypesOfSelectedConversation = createSelector(
if (msg.propsForTimerNotification) {
return {
- showUnreadIndicator: isFirstUnread,
- showDateBreak,
+ ...common,
message: {
messageType: 'timer-notification',
props: { ...msg.propsForTimerNotification, messageId: msg.propsForMessage.id },
@@ -272,8 +270,7 @@ export const getSortedMessagesTypesOfSelectedConversation = createSelector(
if (msg.propsForCallNotification) {
return {
- showUnreadIndicator: isFirstUnread,
- showDateBreak,
+ ...common,
message: {
messageType: 'call-notification',
props: {