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;
if (!firstMessageOfSeries) {
return <div style={{ marginInlineEnd: '60px' }} />;
return <div style={{ marginInlineEnd: '60px' }} key={`msg-avatar-${authorPhoneNumber}`} />;
}
return (
<div className="module-message__author-avatar">
<div className="module-message__author-avatar" key={`msg-avatar-${authorPhoneNumber}`}>
<Avatar
avatarPath={authorAvatarPath}
name={userName}
@ -600,9 +600,12 @@ class MessageInner extends React.PureComponent<Props, State> {
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<Props, State> {
className={classNames(divClasses)}
onChange={onVisible}
onContextMenu={this.handleContextMenu}
key={`readable-message-${this.props.id}`}
>
{this.renderAvatar()}
<div

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

@ -255,16 +255,12 @@ export class SessionConversation extends React.Component<Props, State> {
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
})
);
}

@ -1,17 +1,11 @@
import React from 'react';
import styled from 'styled-components';
interface LastSeenProps {
show: boolean;
}
const LastSeenBarContainer = styled.div<LastSeenProps>`
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 (
<LastSeenBarContainer show={show}>
<LastSeenBarContainer>
<LastSeenBar>
<LastSeenText>{text}</LastSeenText>
</LastSeenBar>

@ -60,9 +60,12 @@ type Props = SessionMessageListProps & {
animateQuotedMessageId: string | undefined;
};
const UnreadIndicator = (props: { messageId: string; show: boolean }) => (
<SessionLastSeenIndicator show={props.show} key={`unread-indicator-${props.messageId}`} />
);
const UnreadIndicator = (props: { messageId: string; show: boolean }) => {
if (!props.show) {
return null;
}
return <SessionLastSeenIndicator key={`unread-indicator-${props.messageId}`} />;
};
const GroupUpdateItem = (props: {
messageId: string;
@ -258,14 +261,12 @@ class SessionMessagesListInner extends React.Component<Props> {
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<Props> {
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<Props> {
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<Props> {
return (
<div
className="messages-container"
onScroll={this.debouncedHandleScroll}
onScroll={this.handleScroll}
ref={this.props.messageContainerRef}
>
<TypingBubble
@ -448,9 +449,9 @@ class SessionMessagesListInner extends React.Component<Props> {
}
// 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<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;
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<Props> {
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<Props> {
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;

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

@ -194,7 +194,7 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
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 });

@ -1088,7 +1088,7 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
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();
}

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

@ -368,9 +368,11 @@ export const fetchMessagesForConversation = createAsyncThunk(
count: number;
}): Promise<FetchedMessageResults> => {
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<string>) {
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,
};
},

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

Loading…
Cancel
Save