diff --git a/preload.js b/preload.js index c16e6e5f3..0c0b2ad4a 100644 --- a/preload.js +++ b/preload.js @@ -73,6 +73,10 @@ window.CONSTANTS = { DEFAULT_MEDIA_FETCH_COUNT: 50, DEFAULT_DOCUMENTS_FETCH_COUNT: 150, DEFAULT_MESSAGE_FETCH_COUNT: 30, + // Pixels (scroll) from the top of the top of message container + // at which more messages should be loaded + MESSAGE_CONTAINER_BUFFER_OFFSET_PX: 300, + MESSAGE_FETCH_INTERVAL: 1, }; window.versionInfo = { diff --git a/ts/components/session/SessionConversation.tsx b/ts/components/session/SessionConversation.tsx index 474cc8ab8..82bd62ccd 100644 --- a/ts/components/session/SessionConversation.tsx +++ b/ts/components/session/SessionConversation.tsx @@ -24,10 +24,7 @@ interface State { conversationKey: string; unreadCount: number; messages: Array; - // Scroll position as percentage of message-list - scrollPositionPc: number; - // Scroll position in pixels - scrollPositionPx: number; + isScrolledToBottom: boolean; doneInitialScroll: boolean; messageFetchTimestamp: number; } @@ -51,12 +48,12 @@ export class SessionConversation extends React.Component { conversationKey, unreadCount, messages: [], + isScrolledToBottom: !unreadCount, doneInitialScroll: false, - scrollPositionPc: 0, - scrollPositionPx: 0, messageFetchTimestamp: 0, }; + this.handleScroll = this.handleScroll.bind(this); this.scrollToUnread = this.scrollToUnread.bind(this); this.scrollToBottom = this.scrollToBottom.bind(this); @@ -68,50 +65,41 @@ export class SessionConversation extends React.Component { } public async componentWillMount() { - const { conversationKey } = this.state; - await this.getMessages(conversationKey); + await this.getMessages(); // Inside a setTimeout to simultate onready() setTimeout(() => { - this.scrollToBottom(true); + this.scrollToUnread(); }, 0); setTimeout(() => { this.setState({ doneInitialScroll: true, }); }, 100); + } + public componentDidUpdate(){ + // Keep scrolled to bottom unless user scrolls up + if (this.state.isScrolledToBottom){ + this.scrollToBottom(); + } } public async componentWillReceiveProps() { - const { conversationKey } = this.state; const timestamp = this.getTimestamp(); // If we have pulled messages in the last second, don't bother rescanning // This avoids getting messages on every re-render. if (timestamp > this.state.messageFetchTimestamp) { - await this.getMessages(conversationKey); - } else{ - console.log(`[vince][info] Messages recieved in last second, stream`); + await this.getMessages(); } } render() { - console.log('[vince] SessionConversation was just rerendered!'); - console.log(`[vince] These are SessionConversation props: `, this.props); - // const headerProps = this.props.getHeaderProps; const { messages, conversationKey, doneInitialScroll } = this.state; const loading = !doneInitialScroll || messages.length === 0; - console.log(`[vince] Loading: `, loading); - console.log(`[vince] My conversation key is: `, conversationKey); - - - // TMEPORARY SOLUTION TO GETTING CONVERSATION UNTIL - // SessionConversationStack is created - - // Get conversation by Key (NOT cid) const conversation = this.props.conversations.conversationLookup[conversationKey] return ( @@ -133,13 +121,12 @@ export class SessionConversation extends React.Component { )} -
+
{this.renderMessages()}
- - +
@@ -153,6 +140,9 @@ export class SessionConversation extends React.Component { public renderMessages() { const { messages } = this.state; + // IF MESSAGE IS THE TOP OF UNREAD, THEN INSERT AN UNREAD BANNER + + return ( <>{ messages.map((message: any) => { @@ -228,34 +218,7 @@ export class SessionConversation extends React.Component { } - public async getMessages(conversationKey: string){ - const msgCount = window.CONSTANTS.DEFAULT_MESSAGE_FETCH_COUNT + this.state.unreadCount; - - const messageSet = await window.Signal.Data.getMessagesByConversation( - conversationKey, - { msgCount, MessageCollection: window.Whisper.MessageCollection }, - ); - - // Set first member of series here. - const messageModels = messageSet.models; - let messages = []; - let previousSender; - for (let i = 0; i < messageModels.length; i++){ - // Handle firstMessageOfSeries for conditional avatar rendering - let firstMessageOfSeries = true; - if (i > 0 && previousSender === messageModels[i].authorPhoneNumber){ - firstMessageOfSeries = false; - } - - messages.push({...messageModels[i], firstMessageOfSeries}); - previousSender = messageModels[i].authorPhoneNumber; - } - - const messageFetchTimestamp = this.getTimestamp(); - - console.log(`[vince][messages] Messages Set`, messageModels); - this.setState({ messages, messageFetchTimestamp }); - } + public renderMessage(messageProps: any, firstMessageOfSeries: boolean, quoteProps?: any) { @@ -321,7 +284,7 @@ export class SessionConversation extends React.Component { /> ); } - + public renderFriendRequest(friendRequestProps: any){ return ( { ); } + public async getMessages(numMessages?: number, fetchInterval = window.CONSTANTS.MESSAGE_FETCH_INTERVAL){ + const { conversationKey, messageFetchTimestamp } = this.state; + const timestamp = this.getTimestamp(); + + // If we have pulled messages in the last interval, don't bother rescanning + // This avoids getting messages on every re-render. + if (timestamp >= messageFetchTimestamp + fetchInterval) { + return; + } + + const msgCount = numMessages || window.CONSTANTS.DEFAULT_MESSAGE_FETCH_COUNT + this.state.unreadCount; + const messageSet = await window.Signal.Data.getMessagesByConversation( + conversationKey, + { limit: msgCount, MessageCollection: window.Whisper.MessageCollection }, + ); + + // Set first member of series here. + const messageModels = messageSet.models; + let messages = []; + let previousSender; + for (let i = 0; i < messageModels.length; i++){ + // Handle firstMessageOfSeries for conditional avatar rendering + let firstMessageOfSeries = true; + if (i > 0 && previousSender === messageModels[i].authorPhoneNumber){ + firstMessageOfSeries = false; + } + + messages.push({...messageModels[i], firstMessageOfSeries}); + previousSender = messageModels[i].authorPhoneNumber; + } + + const previousTopMessage = this.state.messages[0]?.id; + const newTopMessage = messages[0]?.id; + + this.setState({ messages, messageFetchTimestamp }); + + console.log(`[vince][messages] Previous Top Message: `, previousTopMessage); + console.log(`[vince][messages] New Top Message: `, newTopMessage); + + return { newTopMessage, previousTopMessage }; + } + public getTimestamp() { return Math.floor(Date.now() / 1000); } - public handleScroll() { - // Update unread count + public async handleScroll() { + const { messages } = this.state; + const messageContainer = document.getElementsByClassName('messages-container')[0]; + const isScrolledToBottom = messageContainer.scrollHeight - messageContainer.clientHeight <= messageContainer.scrollTop + 1; + + // FIXME VINCE: Update unread count + // In models/conversations + // Update unread count by geting all divs of .session-message-wrapper + // which are currently in view. - // Get id of message at bottom of screen in full view. This is scroll position by messageID + // Pin scroll to bottom on new message, unless user has scrolled up + if (this.state.isScrolledToBottom !== isScrolledToBottom){ + this.setState({ isScrolledToBottom }); + } + // Fetch more messages when nearing the top of the message list + const shouldFetchMoreMessages = messageContainer.scrollTop <= window.CONSTANTS.MESSAGE_CONTAINER_BUFFER_OFFSET_PX; + + if (shouldFetchMoreMessages){ + const numMessages = this.state.messages.length + window.CONSTANTS.DEFAULT_MESSAGE_FETCH_COUNT; + + // Prevent grabbing messags with scroll more frequently than once per 5s. + // const messageFetchInterval = 5; + // const previousTopMessage = (await this.getMessages(numMessages, messageFetchInterval))?.previousTopMessage; + // this.scrollToMessage(previousTopMessage); + } } public scrollToUnread() { - const topUnreadMessage = document.getElementById('70fd6220-5292-43d8-9e0d-f98bf4792f43'); - topUnreadMessage?.scrollIntoView(false); + const { messages, unreadCount } = this.state; + + const message = messages[(messages.length - 1) - unreadCount]; + this.scrollToMessage(message.id); + } + + public scrollToMessage(messageId: string) { + const topUnreadMessage = document.getElementById(messageId); + topUnreadMessage?.scrollIntoView(); } public scrollToBottom(firstLoad = false) { - this.messagesEndRef.current?.scrollIntoView( - { behavior: firstLoad ? 'auto' : 'smooth' } - ); + // FIXME VINCE: Smooth scrolling that isn't slow@! + // this.messagesEndRef.current?.scrollIntoView( + // { behavior: firstLoad ? 'auto' : 'smooth' } + // ); + + const messageContainer = document.getElementsByClassName('messages-container')[0]; + messageContainer.scrollTop = messageContainer.scrollHeight - messageContainer.clientHeight; } }