diff --git a/package.json b/package.json
index eba0d1e02..2e2869638 100644
--- a/package.json
+++ b/package.json
@@ -107,6 +107,7 @@
"react-toastify": "^6.0.9",
"react-use": "^17.2.1",
"react-virtualized": "9.22.3",
+ "react-window-infinite-loader": "^1.0.7",
"read-last-lines": "1.3.0",
"redux": "4.0.1",
"redux-logger": "3.0.6",
diff --git a/ts/components/session/conversation/SessionMessagesList.tsx b/ts/components/session/conversation/SessionMessagesList.tsx
index 23e77590c..a23816317 100644
--- a/ts/components/session/conversation/SessionMessagesList.tsx
+++ b/ts/components/session/conversation/SessionMessagesList.tsx
@@ -7,7 +7,6 @@ import { SessionScrollButton } from '../SessionScrollButton';
import { Constants } from '../../../session';
import _ from 'lodash';
import { contextMenu } from 'react-contexify';
-import { AttachmentType, AttachmentTypeWithPath } from '../../../types/Attachment';
import { GroupNotification } from '../../conversation/GroupNotification';
import { GroupInvitation } from '../../conversation/GroupInvitation';
import {
@@ -15,7 +14,6 @@ import {
PropsForExpirationTimer,
PropsForGroupInvitation,
PropsForGroupUpdate,
- PropsForMessage,
ReduxConversationType,
SortedMessageModelProps,
} from '../../../state/ducks/conversations';
@@ -39,10 +37,8 @@ import {
getMessagesOfSelectedConversation,
getSelectedConversation,
getSelectedConversationKey,
- getSelectedMessageIds,
isMessageSelectionMode,
} from '../../../state/selectors/conversations';
-import { saveAttachmentToDisk } from '../../../util/attachmentsUtil';
interface State {
showScrollButton: boolean;
@@ -164,6 +160,91 @@ const GenericMessageItem = (props: {
);
};
+const MessageList = ({ hasNextPage: boolean, isNextPageLoading, list, loadNextPage }) => {
+ const messagesProps = useSelector(getMessagesOfSelectedConversation);
+ let playableMessageIndex = 0;
+
+ return (
+ <>
+ {messagesProps.map((messageProps: SortedMessageModelProps) => {
+ const timerProps = messageProps.propsForTimerNotification;
+ const propsForGroupInvitation = messageProps.propsForGroupInvitation;
+ const propsForDataExtractionNotification = messageProps.propsForDataExtractionNotification;
+
+ const groupNotificationProps = messageProps.propsForGroupNotification;
+
+ // IF we found the first unread message
+ // AND we are not scrolled all the way to the bottom
+ // THEN, show the unread banner for the current message
+ const showUnreadIndicator = Boolean(messageProps.firstUnread);
+ console.warn('&& this.getScrollOffsetBottomPx() !== 0');
+
+ if (groupNotificationProps) {
+ return (
+
+ );
+ }
+
+ if (propsForGroupInvitation) {
+ return (
+
+ );
+ }
+
+ if (propsForDataExtractionNotification) {
+ return (
+
+ );
+ }
+
+ if (timerProps) {
+ return (
+
+ );
+ }
+
+ if (!messageProps) {
+ return;
+ }
+
+ playableMessageIndex++;
+
+ // firstMessageOfSeries tells us to render the avatar only for the first message
+ // in a series of messages from the same user
+ return (
+
+ );
+ })}
+ >
+ );
+};
+
class SessionMessagesListInner extends React.Component {
private scrollOffsetBottomPx: number = Number.MAX_VALUE;
private ignoreScrollEvents: boolean;
@@ -264,7 +345,7 @@ class SessionMessagesListInner extends React.Component {
key="typing-bubble"
/>
- {this.renderMessages()}
+
{
);
}
- private renderMessages() {
- const { messagesProps } = this.props;
- let playableMessageIndex = 0;
-
- return (
- <>
- {messagesProps.map((messageProps: SortedMessageModelProps) => {
- const timerProps = messageProps.propsForTimerNotification;
- const propsForGroupInvitation = messageProps.propsForGroupInvitation;
- const propsForDataExtractionNotification =
- messageProps.propsForDataExtractionNotification;
-
- const groupNotificationProps = messageProps.propsForGroupNotification;
-
- // IF we found the first unread message
- // AND we are not scrolled all the way to the bottom
- // THEN, show the unread banner for the current message
- const showUnreadIndicator =
- Boolean(messageProps.firstUnread) && this.getScrollOffsetBottomPx() !== 0;
-
- if (groupNotificationProps) {
- return (
-
- );
- }
-
- if (propsForGroupInvitation) {
- return (
-
- );
- }
-
- if (propsForDataExtractionNotification) {
- return (
-
- );
- }
-
- if (timerProps) {
- return (
-
- );
- }
-
- if (!messageProps) {
- return;
- }
-
- playableMessageIndex++;
-
- // firstMessageOfSeries tells us to render the avatar only for the first message
- // in a series of messages from the same user
- return (
-
- );
- })}
- >
- );
- }
-
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// ~~~~~~~~~~~~~ MESSAGE HANDLING ~~~~~~~~~~~~~
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/ts/state/ducks/conversations.ts b/ts/state/ducks/conversations.ts
index 962989bfe..98c2b828d 100644
--- a/ts/state/ducks/conversations.ts
+++ b/ts/state/ducks/conversations.ts
@@ -238,6 +238,7 @@ export type ConversationsStateType = {
selectedMessageIds: Array;
lightBox?: LightBoxOptions;
quotedMessage?: ReplyingToMessageProps;
+ areMoreMessagesBeingFetched: boolean;
};
async function getMessages(
@@ -392,6 +393,7 @@ function getEmptyState(): ConversationsStateType {
messageDetailProps: undefined,
showRightPanel: false,
selectedMessageIds: [],
+ areMoreMessagesBeingFetched: false,
};
}
@@ -742,11 +744,34 @@ const conversationsSlice = createSlice({
return {
...state,
messages: messagesProps,
+ areMoreMessagesBeingFetched: false,
};
}
return state;
}
);
+ builder.addCase(
+ fetchMessagesForConversation.fulfilled,
+ (state: ConversationsStateType, action: any) => {
+ // this is called once the messages are loaded from the db for the currently selected conversation
+ const { messagesProps, conversationKey } = action.payload as FetchedMessageResults;
+ // double check that this update is for the shown convo
+ if (conversationKey === state.selectedConversation) {
+ return {
+ ...state,
+ messages: messagesProps,
+ areMoreMessagesBeingFetched: false,
+ };
+ }
+ return state;
+ }
+ );
+ builder.addCase(fetchMessagesForConversation.pending, (state: ConversationsStateType) => {
+ state.areMoreMessagesBeingFetched = true;
+ });
+ builder.addCase(fetchMessagesForConversation.rejected, (state: ConversationsStateType) => {
+ state.areMoreMessagesBeingFetched = false;
+ });
},
});
diff --git a/ts/state/selectors/conversations.ts b/ts/state/selectors/conversations.ts
index 3b04be7b8..c130815da 100644
--- a/ts/state/selectors/conversations.ts
+++ b/ts/state/selectors/conversations.ts
@@ -19,6 +19,7 @@ import {
} from '../../components/conversation/ConversationHeader';
import { LightBoxOptions } from '../../components/session/conversation/SessionConversation';
import { ReplyingToMessageProps } from '../../components/session/conversation/SessionCompositionBox';
+import { createSlice } from '@reduxjs/toolkit';
export const getConversations = (state: StateType): ConversationsStateType => state.conversations;
@@ -295,3 +296,8 @@ export const getQuotedMessage = createSelector(
getConversations,
(state: ConversationsStateType): ReplyingToMessageProps | undefined => state.quotedMessage
);
+
+
+export const areMoreMessagesLoading = createSlice(getConversations,
+ (state: ConversationsStateType): boolean => state.
+);
\ No newline at end of file
diff --git a/yarn.lock b/yarn.lock
index 456a3404b..7f71618c5 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -7931,6 +7931,11 @@ react-virtualized@9.22.3:
prop-types "^15.7.2"
react-lifecycles-compat "^3.0.4"
+react-window-infinite-loader@^1.0.7:
+ version "1.0.7"
+ resolved "https://registry.yarnpkg.com/react-window-infinite-loader/-/react-window-infinite-loader-1.0.7.tgz#958ef1a689d20dce122ef377583acd987760aee8"
+ integrity sha512-wg3LWkUpG21lhv+cZvNy+p0+vtclZw+9nP2vO6T9PKT50EN1cUq37Dq6FzcM38h/c2domE0gsUhb6jHXtGogAA==
+
react@^16.13.1:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e"