fix: do not scroll back to unread banner when deleting msgs

Relates #2308
This commit is contained in:
Audric Ackermann 2022-08-25 13:32:58 +10:00
parent 56ee7fe7ac
commit b638733090
4 changed files with 67 additions and 39 deletions

View File

@ -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);

View File

@ -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 ? (
<SessionLastSeenIndicator key={`unread-indicator-${messageId}`} messageId={messageId} />
<SessionLastSeenIndicator
key={'unread-indicator'}
messageId={messageId}
didScroll={didScroll}
setDidScroll={setDidScroll}
/>
) : 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 [<GroupUpdateMessage key={messageId} {...msgProps} />, dateBreak, unreadIndicator];
return [<GroupUpdateMessage key={messageId} {...msgProps} />, ...componentToMerge];
}
if (messageProps.message?.messageType === 'group-invitation') {
const msgProps = messageProps.message.props as PropsForGroupInvitation;
return [<GroupInvitation key={messageId} {...msgProps} />, dateBreak, unreadIndicator];
return [<GroupInvitation key={messageId} {...msgProps} />, ...componentToMerge];
}
if (messageProps.message?.messageType === 'message-request-response') {
const msgProps = messageProps.message.props as PropsForMessageRequestResponse;
return [
<MessageRequestResponse key={messageId} {...msgProps} />,
dateBreak,
unreadIndicator,
];
return [<MessageRequestResponse key={messageId} {...msgProps} />, ...componentToMerge];
}
if (messageProps.message?.messageType === 'data-extraction') {
@ -125,28 +139,27 @@ export const SessionMessagesList = (props: {
return [
<DataExtractionNotification key={messageId} {...msgProps} />,
dateBreak,
unreadIndicator,
...componentToMerge,
];
}
if (messageProps.message?.messageType === 'timer-notification') {
const msgProps = messageProps.message.props as PropsForExpirationTimer;
return [<TimerNotification key={messageId} {...msgProps} />, dateBreak, unreadIndicator];
return [<TimerNotification key={messageId} {...msgProps} />, ...componentToMerge];
}
if (messageProps.message?.messageType === 'call-notification') {
const msgProps = messageProps.message.props as PropsForCallNotification;
return [<CallNotification key={messageId} {...msgProps} />, dateBreak, unreadIndicator];
return [<CallNotification key={messageId} {...msgProps} />, ...componentToMerge];
}
if (!messageProps) {
return null;
}
return [<Message messageId={messageId} key={messageId} />, dateBreak, unreadIndicator];
return [<Message messageId={messageId} key={messageId} />, ...componentToMerge];
})}
</>
);

View File

@ -366,6 +366,7 @@ type FetchedTopMessageResults = {
conversationKey: string;
messagesProps: Array<MessageModelPropsWithoutConvoProps>;
oldTopMessageId: string | null;
newMostRecentMessageIdInConversation: string | null;
} | null;
export const fetchTopMessagesForConversation = createAsyncThunk(
@ -379,6 +380,7 @@ export const fetchTopMessagesForConversation = createAsyncThunk(
}): Promise<FetchedTopMessageResults> => {
// 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;

View File

@ -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: {