diff --git a/ts/components/conversation/Message.tsx b/ts/components/conversation/Message.tsx index 92c7fa752..126ad67b4 100644 --- a/ts/components/conversation/Message.tsx +++ b/ts/components/conversation/Message.tsx @@ -432,11 +432,11 @@ class MessageInner extends React.PureComponent { const userName = authorName || authorProfileName || authorPhoneNumber; if (!firstMessageOfSeries) { - return
; + return
; } return ( -
+
{ if (inView === true && shouldMarkReadWhenVisible && window.isFocused()) { const found = await getMessageById(id); - // mark the message as read. - // this will trigger the expire timer. - void found?.markRead(Date.now()); + if (found && Boolean(found.get('unread'))) { + console.warn('marking as read: ', found.id); + // mark the message as read. + // this will trigger the expire timer. + void found?.markRead(Date.now()); + } } }; @@ -612,6 +615,7 @@ class MessageInner extends React.PureComponent { className={classNames(divClasses)} onChange={onVisible} onContextMenu={this.handleContextMenu} + key={`readable-message-${this.props.id}`} > {this.renderAvatar()}
{ closeOnClick={true} rtl={false} pauseOnFocusLoss={false} - draggable={true} - pauseOnHover={true} + draggable={false} + pauseOnHover={false} transition={Slide} limit={5} /> diff --git a/ts/components/session/conversation/SessionConversation.tsx b/ts/components/session/conversation/SessionConversation.tsx index ddd21e265..8933c884c 100644 --- a/ts/components/session/conversation/SessionConversation.tsx +++ b/ts/components/session/conversation/SessionConversation.tsx @@ -255,16 +255,12 @@ export class SessionConversation extends React.Component { if (!selectedConversation) { return; } - const conversationModel = getConversationController().get(selectedConversationKey); - const unreadCount = await conversationModel.getUnreadCount(); - const messagesToFetch = Math.max( - Constants.CONVERSATION.DEFAULT_MESSAGE_FETCH_COUNT, - unreadCount - ); + + // lets load only 50 messages and let the user scroll up if he needs more context (window.inboxStore?.dispatch as any)( fetchMessagesForConversation({ conversationKey: selectedConversationKey, - count: messagesToFetch, + count: 30, // first page }) ); } diff --git a/ts/components/session/conversation/SessionLastSeenIndicator.tsx b/ts/components/session/conversation/SessionLastSeenIndicator.tsx index 9cb4eb735..6f6076205 100644 --- a/ts/components/session/conversation/SessionLastSeenIndicator.tsx +++ b/ts/components/session/conversation/SessionLastSeenIndicator.tsx @@ -1,17 +1,11 @@ import React from 'react'; import styled from 'styled-components'; -interface LastSeenProps { - show: boolean; -} - -const LastSeenBarContainer = styled.div` - padding-bottom: ${props => (props.show ? '35px' : 0)}; +const LastSeenBarContainer = styled.div` + padding-bottom: 35px; margin-inline-start: 28px; - padding-top: ${props => (props.show ? '28px' : 0)}; - transition: ${props => props.theme.common.animations.defaultDuration}; + padding-top: 28px; overflow: hidden; - height: ${props => (props.show ? 'auto' : 0)}; `; const LastSeenBar = styled.div` @@ -31,11 +25,11 @@ const LastSeenText = styled.div` color: ${props => props.theme.colors.lastSeenIndicatorTextColor}; `; -export const SessionLastSeenIndicator = ({ show }: { show: boolean }) => { +export const SessionLastSeenIndicator = () => { const { i18n } = window; const text = i18n('unreadMessages'); return ( - + {text} diff --git a/ts/components/session/conversation/SessionMessagesList.tsx b/ts/components/session/conversation/SessionMessagesList.tsx index 53860fc5d..3a62957c3 100644 --- a/ts/components/session/conversation/SessionMessagesList.tsx +++ b/ts/components/session/conversation/SessionMessagesList.tsx @@ -60,9 +60,12 @@ type Props = SessionMessageListProps & { animateQuotedMessageId: string | undefined; }; -const UnreadIndicator = (props: { messageId: string; show: boolean }) => ( - -); +const UnreadIndicator = (props: { messageId: string; show: boolean }) => { + if (!props.show) { + return null; + } + return ; +}; const GroupUpdateItem = (props: { messageId: string; @@ -258,14 +261,12 @@ class SessionMessagesListInner extends React.Component { private scrollOffsetBottomPx: number = Number.MAX_VALUE; private ignoreScrollEvents: boolean; private timeoutResetQuotedScroll: NodeJS.Timeout | null = null; - private debouncedHandleScroll: any; public constructor(props: Props) { super(props); autoBind(this); this.ignoreScrollEvents = true; - this.debouncedHandleScroll = _.throttle(this.handleScroll, 500); } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -274,7 +275,7 @@ class SessionMessagesListInner extends React.Component { public componentDidMount() { // Pause thread to wait for rendering to complete - setTimeout(this.scrollToUnread, 0); + setTimeout(this.initialMessageLoadingPosition, 0); } public componentWillUnmount() { @@ -294,7 +295,7 @@ class SessionMessagesListInner extends React.Component { this.scrollOffsetBottomPx = Number.MAX_VALUE; this.ignoreScrollEvents = true; this.setupTimeoutResetQuotedHighlightedMessage(true); - this.scrollToUnread(); + this.initialMessageLoadingPosition(); } else { // if we got new message for this convo, and we are scrolled to bottom if (isSameConvo && messageLengthChanged) { @@ -333,7 +334,7 @@ class SessionMessagesListInner extends React.Component { return (
{ } // Fetch more messages when nearing the top of the message list - const shouldFetchMoreMessages = scrollTop <= Constants.UI.MESSAGE_CONTAINER_BUFFER_OFFSET_PX; + const shouldFetchMoreMessagesTop = scrollTop <= Constants.UI.MESSAGE_CONTAINER_BUFFER_OFFSET_PX; - if (shouldFetchMoreMessages) { + if (shouldFetchMoreMessagesTop) { const { messagesProps } = this.props; const numMessages = messagesProps.length + Constants.CONVERSATION.DEFAULT_MESSAGE_FETCH_COUNT; const oldLen = messagesProps.length; @@ -465,23 +466,20 @@ class SessionMessagesListInner extends React.Component { } } - private scrollToUnread() { + /** + * Position the list to the middle of the loaded list if the conversation has unread messages and we have some messages loaded + */ + private initialMessageLoadingPosition() { const { messagesProps, conversation } = this.props; if (!conversation) { return; } - if (conversation.unreadCount > 0) { - let message; - if (messagesProps.length > conversation.unreadCount) { - // if we have enough message to show one more message, show one more to include the unread banner - message = messagesProps[conversation.unreadCount - 1]; - } else { - message = messagesProps[conversation.unreadCount - 1]; - } + if (conversation.unreadCount > 0 && messagesProps.length) { + // just scroll to the middle of the loaded messages list. so the user can chosse to go up or down from there - if (message) { - this.scrollToMessage(message.propsForMessage.id); - } + const middle = messagesProps.length / 2; + messagesProps[middle].propsForMessage.id; + this.scrollToMessage(messagesProps[middle].propsForMessage.id); } if (this.ignoreScrollEvents && messagesProps.length > 0) { @@ -507,6 +505,7 @@ class SessionMessagesListInner extends React.Component { if (clearOnly) { return; } + if (this.props.animateQuotedMessageId !== undefined) { this.timeoutResetQuotedScroll = global.setTimeout(() => { window.inboxStore?.dispatch(quotedMessageToAnimate(undefined)); @@ -517,14 +516,14 @@ class SessionMessagesListInner extends React.Component { private scrollToMessage(messageId: string, smooth: boolean = false) { const messageElementDom = document.getElementById(messageId); messageElementDom?.scrollIntoView({ - behavior: smooth ? 'smooth' : 'auto', + behavior: 'auto', block: 'center', }); // we consider that a `smooth` set to true, means it's a quoted message, so highlight this message on the UI if (smooth) { window.inboxStore?.dispatch(quotedMessageToAnimate(messageId)); - this.setupTimeoutResetQuotedHighlightedMessage; + this.setupTimeoutResetQuotedHighlightedMessage(); } const messageContainer = this.props.messageContainerRef.current; diff --git a/ts/hooks/useEncryptedFileFetch.ts b/ts/hooks/useEncryptedFileFetch.ts index 89a54cc8c..6c212429b 100644 --- a/ts/hooks/useEncryptedFileFetch.ts +++ b/ts/hooks/useEncryptedFileFetch.ts @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { getDecryptedMediaUrl } from '../session/crypto/DecryptedAttachmentsManager'; @@ -7,11 +7,11 @@ export const useEncryptedFileFetch = (url: string, contentType: string) => { const [urlToLoad, setUrlToLoad] = useState(''); const [loading, setLoading] = useState(true); - let isCancelled = false; + const mountedRef = useRef(true); async function fetchUrl() { const decryptedUrl = await getDecryptedMediaUrl(url, contentType); - if (!isCancelled) { + if (mountedRef.current) { setUrlToLoad(decryptedUrl); setLoading(false); @@ -21,7 +21,7 @@ export const useEncryptedFileFetch = (url: string, contentType: string) => { useEffect(() => { void fetchUrl(); - () => (isCancelled = true); + () => (mountedRef.current = false); }, [url]); return { urlToLoad, loading }; diff --git a/ts/models/conversation.ts b/ts/models/conversation.ts index c45ede6bb..558d70251 100644 --- a/ts/models/conversation.ts +++ b/ts/models/conversation.ts @@ -194,7 +194,7 @@ export class ConversationModel extends Backbone.Model { trailing: true, leading: true, }); - this.triggerUIRefresh = _.throttle(this.triggerUIRefresh, 300, { + this.triggerUIRefresh = _.throttle(this.triggerUIRefresh, 1000, { trailing: true, }); this.throttledNotify = _.debounce(this.notify, 500, { maxWait: 1000, trailing: true }); diff --git a/ts/models/message.ts b/ts/models/message.ts index 349ee1f46..2adb2479f 100644 --- a/ts/models/message.ts +++ b/ts/models/message.ts @@ -1088,7 +1088,7 @@ export class MessageModel extends Backbone.Model { public async markRead(readAt: number) { this.markReadNoCommit(readAt); - this.getConversation()?.markRead(this.attributes.received_at); + // this.getConversation()?.markRead(this.attributes.received_at); await this.commit(); } diff --git a/ts/session/conversations/ConversationController.ts b/ts/session/conversations/ConversationController.ts index 82804d8cf..88c238a00 100644 --- a/ts/session/conversations/ConversationController.ts +++ b/ts/session/conversations/ConversationController.ts @@ -246,13 +246,13 @@ export class ConversationController { this.conversations.remove(conversation); if (window?.inboxStore) { - window.inboxStore?.dispatch(conversationActions.conversationRemoved(conversation.id)); window.inboxStore?.dispatch( conversationActions.conversationChanged({ id: conversation.id, data: conversation.getProps(), }) ); + window.inboxStore?.dispatch(conversationActions.conversationRemoved(conversation.id)); } window.log.info(`deleteContact !isPrivate, convo removed from store: ${id}`); } diff --git a/ts/state/ducks/conversations.ts b/ts/state/ducks/conversations.ts index d6c58c008..a519c5138 100644 --- a/ts/state/ducks/conversations.ts +++ b/ts/state/ducks/conversations.ts @@ -368,9 +368,11 @@ export const fetchMessagesForConversation = createAsyncThunk( count: number; }): Promise => { const beforeTimestamp = Date.now(); + console.time('fetchMessagesForConversation'); const messagesProps = await getMessages(conversationKey, count); const firstUnreadIndex = getFirstMessageUnreadIndex(messagesProps); const afterTimestamp = Date.now(); + console.timeEnd('fetchMessagesForConversation'); const time = afterTimestamp - beforeTimestamp; window?.log?.info(`Loading ${messagesProps.length} messages took ${time}ms to load.`); @@ -648,19 +650,14 @@ const conversationsSlice = createSlice({ }; }, - conversationRemoved( - state: ConversationsStateType, - action: PayloadAction<{ - id: string; - }> - ) { - const { payload } = action; - const { id } = payload; + conversationRemoved(state: ConversationsStateType, action: PayloadAction) { + const { payload: conversationId } = action; const { conversationLookup, selectedConversation } = state; return { ...state, - conversationLookup: omit(conversationLookup, [id]), - selectedConversation: selectedConversation === id ? undefined : selectedConversation, + conversationLookup: omit(conversationLookup, [conversationId]), + selectedConversation: + selectedConversation === conversationId ? undefined : selectedConversation, }; }, diff --git a/ts/util/findMember.ts b/ts/util/findMember.ts index f883e2a42..931d70846 100644 --- a/ts/util/findMember.ts +++ b/ts/util/findMember.ts @@ -32,7 +32,7 @@ export class FindMember { if (thisConvo.isPublic()) { const publicMembers = (await window.inboxStore?.getState() .mentionsInput) as MentionsMembersType; - const memberConversations = publicMembers + const memberConversations = (publicMembers || []) .map(publicMember => getConversationController().get(publicMember.authorPhoneNumber)) .filter((c: any) => !!c); groupMembers = memberConversations;