improve performance by not loading all unread messages

pull/1753/head
audric 4 years ago
parent 4f5072ee65
commit 6d34a60f94

@ -432,11 +432,11 @@ class MessageInner extends React.PureComponent<Props, State> {
const userName = authorName || authorProfileName || authorPhoneNumber; const userName = authorName || authorProfileName || authorPhoneNumber;
if (!firstMessageOfSeries) { if (!firstMessageOfSeries) {
return <div style={{ marginInlineEnd: '60px' }} />; return <div style={{ marginInlineEnd: '60px' }} key={`msg-avatar-${authorPhoneNumber}`} />;
} }
return ( return (
<div className="module-message__author-avatar"> <div className="module-message__author-avatar" key={`msg-avatar-${authorPhoneNumber}`}>
<Avatar <Avatar
avatarPath={authorAvatarPath} avatarPath={authorAvatarPath}
name={userName} name={userName}
@ -600,9 +600,12 @@ class MessageInner extends React.PureComponent<Props, State> {
if (inView === true && shouldMarkReadWhenVisible && window.isFocused()) { if (inView === true && shouldMarkReadWhenVisible && window.isFocused()) {
const found = await getMessageById(id); const found = await getMessageById(id);
// mark the message as read. if (found && Boolean(found.get('unread'))) {
// this will trigger the expire timer. console.warn('marking as read: ', found.id);
void found?.markRead(Date.now()); // 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<Props, State> {
className={classNames(divClasses)} className={classNames(divClasses)}
onChange={onVisible} onChange={onVisible}
onContextMenu={this.handleContextMenu} onContextMenu={this.handleContextMenu}
key={`readable-message-${this.props.id}`}
> >
{this.renderAvatar()} {this.renderAvatar()}
<div <div

@ -12,8 +12,8 @@ const SessionToastContainerPrivate = () => {
closeOnClick={true} closeOnClick={true}
rtl={false} rtl={false}
pauseOnFocusLoss={false} pauseOnFocusLoss={false}
draggable={true} draggable={false}
pauseOnHover={true} pauseOnHover={false}
transition={Slide} transition={Slide}
limit={5} limit={5}
/> />

@ -255,16 +255,12 @@ export class SessionConversation extends React.Component<Props, State> {
if (!selectedConversation) { if (!selectedConversation) {
return; return;
} }
const conversationModel = getConversationController().get(selectedConversationKey);
const unreadCount = await conversationModel.getUnreadCount(); // lets load only 50 messages and let the user scroll up if he needs more context
const messagesToFetch = Math.max(
Constants.CONVERSATION.DEFAULT_MESSAGE_FETCH_COUNT,
unreadCount
);
(window.inboxStore?.dispatch as any)( (window.inboxStore?.dispatch as any)(
fetchMessagesForConversation({ fetchMessagesForConversation({
conversationKey: selectedConversationKey, conversationKey: selectedConversationKey,
count: messagesToFetch, count: 30, // first page
}) })
); );
} }

@ -1,17 +1,11 @@
import React from 'react'; import React from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
interface LastSeenProps { const LastSeenBarContainer = styled.div`
show: boolean; padding-bottom: 35px;
}
const LastSeenBarContainer = styled.div<LastSeenProps>`
padding-bottom: ${props => (props.show ? '35px' : 0)};
margin-inline-start: 28px; margin-inline-start: 28px;
padding-top: ${props => (props.show ? '28px' : 0)}; padding-top: 28px;
transition: ${props => props.theme.common.animations.defaultDuration};
overflow: hidden; overflow: hidden;
height: ${props => (props.show ? 'auto' : 0)};
`; `;
const LastSeenBar = styled.div` const LastSeenBar = styled.div`
@ -31,11 +25,11 @@ const LastSeenText = styled.div`
color: ${props => props.theme.colors.lastSeenIndicatorTextColor}; color: ${props => props.theme.colors.lastSeenIndicatorTextColor};
`; `;
export const SessionLastSeenIndicator = ({ show }: { show: boolean }) => { export const SessionLastSeenIndicator = () => {
const { i18n } = window; const { i18n } = window;
const text = i18n('unreadMessages'); const text = i18n('unreadMessages');
return ( return (
<LastSeenBarContainer show={show}> <LastSeenBarContainer>
<LastSeenBar> <LastSeenBar>
<LastSeenText>{text}</LastSeenText> <LastSeenText>{text}</LastSeenText>
</LastSeenBar> </LastSeenBar>

@ -60,9 +60,12 @@ type Props = SessionMessageListProps & {
animateQuotedMessageId: string | undefined; animateQuotedMessageId: string | undefined;
}; };
const UnreadIndicator = (props: { messageId: string; show: boolean }) => ( const UnreadIndicator = (props: { messageId: string; show: boolean }) => {
<SessionLastSeenIndicator show={props.show} key={`unread-indicator-${props.messageId}`} /> if (!props.show) {
); return null;
}
return <SessionLastSeenIndicator key={`unread-indicator-${props.messageId}`} />;
};
const GroupUpdateItem = (props: { const GroupUpdateItem = (props: {
messageId: string; messageId: string;
@ -258,14 +261,12 @@ class SessionMessagesListInner extends React.Component<Props> {
private scrollOffsetBottomPx: number = Number.MAX_VALUE; private scrollOffsetBottomPx: number = Number.MAX_VALUE;
private ignoreScrollEvents: boolean; private ignoreScrollEvents: boolean;
private timeoutResetQuotedScroll: NodeJS.Timeout | null = null; private timeoutResetQuotedScroll: NodeJS.Timeout | null = null;
private debouncedHandleScroll: any;
public constructor(props: Props) { public constructor(props: Props) {
super(props); super(props);
autoBind(this); autoBind(this);
this.ignoreScrollEvents = true; this.ignoreScrollEvents = true;
this.debouncedHandleScroll = _.throttle(this.handleScroll, 500);
} }
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -274,7 +275,7 @@ class SessionMessagesListInner extends React.Component<Props> {
public componentDidMount() { public componentDidMount() {
// Pause thread to wait for rendering to complete // Pause thread to wait for rendering to complete
setTimeout(this.scrollToUnread, 0); setTimeout(this.initialMessageLoadingPosition, 0);
} }
public componentWillUnmount() { public componentWillUnmount() {
@ -294,7 +295,7 @@ class SessionMessagesListInner extends React.Component<Props> {
this.scrollOffsetBottomPx = Number.MAX_VALUE; this.scrollOffsetBottomPx = Number.MAX_VALUE;
this.ignoreScrollEvents = true; this.ignoreScrollEvents = true;
this.setupTimeoutResetQuotedHighlightedMessage(true); this.setupTimeoutResetQuotedHighlightedMessage(true);
this.scrollToUnread(); this.initialMessageLoadingPosition();
} else { } else {
// if we got new message for this convo, and we are scrolled to bottom // if we got new message for this convo, and we are scrolled to bottom
if (isSameConvo && messageLengthChanged) { if (isSameConvo && messageLengthChanged) {
@ -333,7 +334,7 @@ class SessionMessagesListInner extends React.Component<Props> {
return ( return (
<div <div
className="messages-container" className="messages-container"
onScroll={this.debouncedHandleScroll} onScroll={this.handleScroll}
ref={this.props.messageContainerRef} ref={this.props.messageContainerRef}
> >
<TypingBubble <TypingBubble
@ -448,9 +449,9 @@ class SessionMessagesListInner extends React.Component<Props> {
} }
// Fetch more messages when nearing the top of the message list // 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 { messagesProps } = this.props;
const numMessages = messagesProps.length + Constants.CONVERSATION.DEFAULT_MESSAGE_FETCH_COUNT; const numMessages = messagesProps.length + Constants.CONVERSATION.DEFAULT_MESSAGE_FETCH_COUNT;
const oldLen = messagesProps.length; const oldLen = messagesProps.length;
@ -465,23 +466,20 @@ class SessionMessagesListInner extends React.Component<Props> {
} }
} }
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; const { messagesProps, conversation } = this.props;
if (!conversation) { if (!conversation) {
return; return;
} }
if (conversation.unreadCount > 0) { if (conversation.unreadCount > 0 && messagesProps.length) {
let message; // just scroll to the middle of the loaded messages list. so the user can chosse to go up or down from there
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 (message) { const middle = messagesProps.length / 2;
this.scrollToMessage(message.propsForMessage.id); messagesProps[middle].propsForMessage.id;
} this.scrollToMessage(messagesProps[middle].propsForMessage.id);
} }
if (this.ignoreScrollEvents && messagesProps.length > 0) { if (this.ignoreScrollEvents && messagesProps.length > 0) {
@ -507,6 +505,7 @@ class SessionMessagesListInner extends React.Component<Props> {
if (clearOnly) { if (clearOnly) {
return; return;
} }
if (this.props.animateQuotedMessageId !== undefined) { if (this.props.animateQuotedMessageId !== undefined) {
this.timeoutResetQuotedScroll = global.setTimeout(() => { this.timeoutResetQuotedScroll = global.setTimeout(() => {
window.inboxStore?.dispatch(quotedMessageToAnimate(undefined)); window.inboxStore?.dispatch(quotedMessageToAnimate(undefined));
@ -517,14 +516,14 @@ class SessionMessagesListInner extends React.Component<Props> {
private scrollToMessage(messageId: string, smooth: boolean = false) { private scrollToMessage(messageId: string, smooth: boolean = false) {
const messageElementDom = document.getElementById(messageId); const messageElementDom = document.getElementById(messageId);
messageElementDom?.scrollIntoView({ messageElementDom?.scrollIntoView({
behavior: smooth ? 'smooth' : 'auto', behavior: 'auto',
block: 'center', block: 'center',
}); });
// we consider that a `smooth` set to true, means it's a quoted message, so highlight this message on the UI // we consider that a `smooth` set to true, means it's a quoted message, so highlight this message on the UI
if (smooth) { if (smooth) {
window.inboxStore?.dispatch(quotedMessageToAnimate(messageId)); window.inboxStore?.dispatch(quotedMessageToAnimate(messageId));
this.setupTimeoutResetQuotedHighlightedMessage; this.setupTimeoutResetQuotedHighlightedMessage();
} }
const messageContainer = this.props.messageContainerRef.current; const messageContainer = this.props.messageContainerRef.current;

@ -1,4 +1,4 @@
import { useEffect, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import { getDecryptedMediaUrl } from '../session/crypto/DecryptedAttachmentsManager'; import { getDecryptedMediaUrl } from '../session/crypto/DecryptedAttachmentsManager';
@ -7,11 +7,11 @@ export const useEncryptedFileFetch = (url: string, contentType: string) => {
const [urlToLoad, setUrlToLoad] = useState(''); const [urlToLoad, setUrlToLoad] = useState('');
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
let isCancelled = false; const mountedRef = useRef(true);
async function fetchUrl() { async function fetchUrl() {
const decryptedUrl = await getDecryptedMediaUrl(url, contentType); const decryptedUrl = await getDecryptedMediaUrl(url, contentType);
if (!isCancelled) { if (mountedRef.current) {
setUrlToLoad(decryptedUrl); setUrlToLoad(decryptedUrl);
setLoading(false); setLoading(false);
@ -21,7 +21,7 @@ export const useEncryptedFileFetch = (url: string, contentType: string) => {
useEffect(() => { useEffect(() => {
void fetchUrl(); void fetchUrl();
() => (isCancelled = true); () => (mountedRef.current = false);
}, [url]); }, [url]);
return { urlToLoad, loading }; return { urlToLoad, loading };

@ -194,7 +194,7 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
trailing: true, trailing: true,
leading: true, leading: true,
}); });
this.triggerUIRefresh = _.throttle(this.triggerUIRefresh, 300, { this.triggerUIRefresh = _.throttle(this.triggerUIRefresh, 1000, {
trailing: true, trailing: true,
}); });
this.throttledNotify = _.debounce(this.notify, 500, { maxWait: 1000, trailing: true }); this.throttledNotify = _.debounce(this.notify, 500, { maxWait: 1000, trailing: true });

@ -1088,7 +1088,7 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
public async markRead(readAt: number) { public async markRead(readAt: number) {
this.markReadNoCommit(readAt); this.markReadNoCommit(readAt);
this.getConversation()?.markRead(this.attributes.received_at); // this.getConversation()?.markRead(this.attributes.received_at);
await this.commit(); await this.commit();
} }

@ -246,13 +246,13 @@ export class ConversationController {
this.conversations.remove(conversation); this.conversations.remove(conversation);
if (window?.inboxStore) { if (window?.inboxStore) {
window.inboxStore?.dispatch(conversationActions.conversationRemoved(conversation.id));
window.inboxStore?.dispatch( window.inboxStore?.dispatch(
conversationActions.conversationChanged({ conversationActions.conversationChanged({
id: conversation.id, id: conversation.id,
data: conversation.getProps(), data: conversation.getProps(),
}) })
); );
window.inboxStore?.dispatch(conversationActions.conversationRemoved(conversation.id));
} }
window.log.info(`deleteContact !isPrivate, convo removed from store: ${id}`); window.log.info(`deleteContact !isPrivate, convo removed from store: ${id}`);
} }

@ -368,9 +368,11 @@ export const fetchMessagesForConversation = createAsyncThunk(
count: number; count: number;
}): Promise<FetchedMessageResults> => { }): Promise<FetchedMessageResults> => {
const beforeTimestamp = Date.now(); const beforeTimestamp = Date.now();
console.time('fetchMessagesForConversation');
const messagesProps = await getMessages(conversationKey, count); const messagesProps = await getMessages(conversationKey, count);
const firstUnreadIndex = getFirstMessageUnreadIndex(messagesProps); const firstUnreadIndex = getFirstMessageUnreadIndex(messagesProps);
const afterTimestamp = Date.now(); const afterTimestamp = Date.now();
console.timeEnd('fetchMessagesForConversation');
const time = afterTimestamp - beforeTimestamp; const time = afterTimestamp - beforeTimestamp;
window?.log?.info(`Loading ${messagesProps.length} messages took ${time}ms to load.`); window?.log?.info(`Loading ${messagesProps.length} messages took ${time}ms to load.`);
@ -648,19 +650,14 @@ const conversationsSlice = createSlice({
}; };
}, },
conversationRemoved( conversationRemoved(state: ConversationsStateType, action: PayloadAction<string>) {
state: ConversationsStateType, const { payload: conversationId } = action;
action: PayloadAction<{
id: string;
}>
) {
const { payload } = action;
const { id } = payload;
const { conversationLookup, selectedConversation } = state; const { conversationLookup, selectedConversation } = state;
return { return {
...state, ...state,
conversationLookup: omit(conversationLookup, [id]), conversationLookup: omit(conversationLookup, [conversationId]),
selectedConversation: selectedConversation === id ? undefined : selectedConversation, selectedConversation:
selectedConversation === conversationId ? undefined : selectedConversation,
}; };
}, },

@ -32,7 +32,7 @@ export class FindMember {
if (thisConvo.isPublic()) { if (thisConvo.isPublic()) {
const publicMembers = (await window.inboxStore?.getState() const publicMembers = (await window.inboxStore?.getState()
.mentionsInput) as MentionsMembersType; .mentionsInput) as MentionsMembersType;
const memberConversations = publicMembers const memberConversations = (publicMembers || [])
.map(publicMember => getConversationController().get(publicMember.authorPhoneNumber)) .map(publicMember => getConversationController().get(publicMember.authorPhoneNumber))
.filter((c: any) => !!c); .filter((c: any) => !!c);
groupMembers = memberConversations; groupMembers = memberConversations;

Loading…
Cancel
Save