diff --git a/js/views/conversation_view.js b/js/views/conversation_view.js
index 5fcfa9e0f..96e846869 100644
--- a/js/views/conversation_view.js
+++ b/js/views/conversation_view.js
@@ -83,9 +83,6 @@
'show-message-detail',
this.showMessageDetail
);
- this.listenTo(this.model.messageCollection, 'navigate-to', url => {
- window.location = url;
- });
this.lazyUpdateVerified = _.debounce(
this.model.updateVerified.bind(this.model),
diff --git a/ts/components/conversation/ExpireTimer.tsx b/ts/components/conversation/ExpireTimer.tsx
index 43f18016a..290f90eaf 100644
--- a/ts/components/conversation/ExpireTimer.tsx
+++ b/ts/components/conversation/ExpireTimer.tsx
@@ -12,10 +12,6 @@ type Props = {
};
export const ExpireTimer = (props: Props) => {
- const [lastUpdated, setLastUpdated] = useState(Date.now());
- const update = () => {
- setLastUpdated(Date.now());
- };
const {
direction,
expirationLength,
@@ -23,14 +19,27 @@ export const ExpireTimer = (props: Props) => {
withImageNoCaption,
} = props;
+ const initialTimeLeft = Math.max(
+ Math.round((expirationTimestamp - Date.now()) / 1000),
+ 0
+ );
+ const [timeLeft, setTimeLeft] = useState(initialTimeLeft);
+
+ const update = () => {
+ const newTimeLeft = Math.max(
+ Math.round((expirationTimestamp - Date.now()) / 1000),
+ 0
+ );
+ if (newTimeLeft !== timeLeft) {
+ setTimeLeft(newTimeLeft);
+ }
+ };
+
const increment = getIncrement(expirationLength);
const updateFrequency = Math.max(increment, 500);
useInterval(update, updateFrequency);
- const bucket = getTimerBucket(expirationTimestamp, expirationLength);
- let timeLeft = Math.round((expirationTimestamp - Date.now()) / 1000);
- timeLeft = timeLeft >= 0 ? timeLeft : 0;
if (timeLeft <= 60) {
return (
{
);
}
+ const bucket = getTimerBucket(expirationTimestamp, expirationLength);
return (
{
);
if (this.messageContainerRef.current) {
// force scrolling to bottom on message sent
+ // this will mark all messages as read
(this.messageContainerRef
.current as any).scrollTop = this.messageContainerRef.current?.scrollHeight;
}
diff --git a/ts/components/session/conversation/SessionMessagesList.tsx b/ts/components/session/conversation/SessionMessagesList.tsx
index 4903729b6..b018592cb 100644
--- a/ts/components/session/conversation/SessionMessagesList.tsx
+++ b/ts/components/session/conversation/SessionMessagesList.tsx
@@ -19,7 +19,6 @@ import { ToastUtils } from '../../../session/utils';
interface State {
showScrollButton: boolean;
- doneInitialScroll: boolean;
}
interface Props {
@@ -45,23 +44,25 @@ interface Props {
export class SessionMessagesList extends React.Component
{
private readonly messageContainerRef: React.RefObject;
- private scrollOffsetPx: number = Number.MAX_VALUE;
+ private scrollOffsetBottomPx: number = Number.MAX_VALUE;
+ private ignoreScrollEvents: boolean;
public constructor(props: Props) {
super(props);
this.state = {
showScrollButton: false,
- doneInitialScroll: false,
};
this.renderMessage = this.renderMessage.bind(this);
this.handleScroll = this.handleScroll.bind(this);
this.scrollToUnread = this.scrollToUnread.bind(this);
this.scrollToBottom = this.scrollToBottom.bind(this);
this.scrollToQuoteMessage = this.scrollToQuoteMessage.bind(this);
- this.getScrollOffsetPx = this.getScrollOffsetPx.bind(this);
+ this.getScrollOffsetBottomPx = this.getScrollOffsetBottomPx.bind(this);
+ this.displayUnreadBannerIndex = this.displayUnreadBannerIndex.bind(this);
this.messageContainerRef = this.props.messageContainerRef;
+ this.ignoreScrollEvents = true;
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -83,11 +84,11 @@ export class SessionMessagesList extends React.Component {
(prevProps.messages.length === 0 && this.props.messages.length !== 0)
) {
// displayed conversation changed. We have a bit of cleaning to do here
- this.scrollOffsetPx = Number.MAX_VALUE;
+ this.scrollOffsetBottomPx = Number.MAX_VALUE;
+ this.ignoreScrollEvents = true;
this.setState(
{
showScrollButton: false,
- doneInitialScroll: false,
},
this.scrollToUnread
);
@@ -95,18 +96,18 @@ export class SessionMessagesList extends React.Component {
// if we got new message for this convo, and we are scrolled to bottom
if (isSameConvo && messageLengthChanged) {
// Keep scrolled to bottom unless user scrolls up
- if (this.getScrollOffsetPx() === 0) {
+ if (this.getScrollOffsetBottomPx() === 0) {
this.scrollToBottom();
} else {
const messageContainer = this.messageContainerRef?.current;
if (messageContainer) {
- global.setTimeout(() => {
- const scrollHeight = messageContainer.scrollHeight;
- const clientHeight = messageContainer.clientHeight;
- messageContainer.scrollTop =
- scrollHeight - clientHeight - this.scrollOffsetPx;
- }, 10);
+ const scrollHeight = messageContainer.scrollHeight;
+ const clientHeight = messageContainer.clientHeight;
+ this.ignoreScrollEvents = true;
+ messageContainer.scrollTop =
+ scrollHeight - clientHeight - this.scrollOffsetBottomPx;
+ this.ignoreScrollEvents = false;
}
}
}
@@ -133,40 +134,48 @@ export class SessionMessagesList extends React.Component {
);
}
- private renderMessages(messages: Array) {
+ private displayUnreadBannerIndex(messages: Array) {
const { conversation } = this.props;
-
- const multiSelectMode = Boolean(this.props.selectedMessages.length);
- let currentMessageIndex = 0;
- // find the first unread message in the list of messages. We use this to display the
- // unread banner so this is at all times at the correct index.
- // Our messages are marked read, so be sure to skip those.
-
- // the messages variable is ordered with most recent message being on index 0.
- // so we need to start from the end to find our first message unread
+ if (conversation.unreadCount === 0) {
+ return -1;
+ }
+ // conversation.unreadCount is the number of messages we incoming we did not read yet.
+ // also, unreacCount is updated only when the conversation is marked as read.
+ // So we can have an unreadCount for the conversation not correct based on the real number of unread messages.
+ // some of the messages we have in "messages" are ones we sent ourself (or from another device).
+ // those messages should not be counted to display the unread banner.
let findFirstUnreadIndex = -1;
+ let incomingMessagesSoFar = 0;
+ const { unreadCount } = conversation;
- for (let index = messages.length - 1; index >= 0; index--) {
+ // Basically, count the number of incoming messages from the most recent one.
+ for (let index = 0; index <= messages.length - 1; index++) {
const message = messages[index];
- if (
- message.attributes.type === 'incoming' &&
- message.attributes.unread !== undefined
- ) {
- findFirstUnreadIndex = index;
- break;
+ if (message.attributes.type === 'incoming') {
+ incomingMessagesSoFar++;
+ // message.attributes.unread is !== undefined if the message is unread.
+ if (
+ message.attributes.unread !== undefined &&
+ incomingMessagesSoFar >= unreadCount
+ ) {
+ findFirstUnreadIndex = index;
+ break;
+ }
}
}
- // if we did not find an unread messsages, but the conversation has some,
- // we must not have enough messages in memory, so just display the unread banner
- // at the top of the screen
- if (findFirstUnreadIndex === -1 && conversation.unreadCount !== 0) {
- findFirstUnreadIndex = messages.length - 1;
- }
- if (conversation.unreadCount === 0) {
- findFirstUnreadIndex = -1;
+ //
+ if (findFirstUnreadIndex === -1 && conversation.unreadCount >= 0) {
+ return conversation.unreadCount - 1;
}
+ return findFirstUnreadIndex;
+ }
+
+ private renderMessages(messages: Array) {
+ const multiSelectMode = Boolean(this.props.selectedMessages.length);
+ let currentMessageIndex = 0;
+ const displayUnreadBannerIndex = this.displayUnreadBannerIndex(messages);
return (
<>
@@ -186,12 +195,12 @@ export class SessionMessagesList extends React.Component {
// AND we are not scrolled all the way to the bottom
// THEN, show the unread banner for the current message
const showUnreadIndicator =
- findFirstUnreadIndex >= 0 &&
- currentMessageIndex === findFirstUnreadIndex &&
- this.getScrollOffsetPx() !== 0;
+ displayUnreadBannerIndex >= 0 &&
+ currentMessageIndex === displayUnreadBannerIndex &&
+ this.getScrollOffsetBottomPx() !== 0;
const unreadIndicator = (
@@ -327,11 +336,11 @@ export class SessionMessagesList extends React.Component {
return;
}
- if (!this.state.doneInitialScroll) {
+ if (this.ignoreScrollEvents) {
return;
}
- if (this.getScrollOffsetPx() === 0) {
+ if (this.getScrollOffsetBottomPx() === 0) {
void conversation.markRead(messages[0].attributes.received_at);
}
}
@@ -348,7 +357,7 @@ export class SessionMessagesList extends React.Component {
}
contextMenu.hideAll();
- if (!this.state.doneInitialScroll) {
+ if (this.ignoreScrollEvents) {
return;
}
@@ -357,9 +366,9 @@ export class SessionMessagesList extends React.Component {
const scrollButtonViewShowLimit = 0.75;
const scrollButtonViewHideLimit = 0.4;
- this.scrollOffsetPx = this.getScrollOffsetPx();
+ this.scrollOffsetBottomPx = this.getScrollOffsetBottomPx();
- const scrollOffsetPc = this.scrollOffsetPx / clientHeight;
+ const scrollOffsetPc = this.scrollOffsetBottomPx / clientHeight;
// Scroll button appears if you're more than 75% scrolled up
if (
@@ -377,7 +386,7 @@ export class SessionMessagesList extends React.Component {
}
// Scrolled to bottom
- const isScrolledToBottom = this.getScrollOffsetPx() === 0;
+ const isScrolledToBottom = this.getScrollOffsetBottomPx() === 0;
if (isScrolledToBottom) {
// Mark messages read
this.updateReadMessages();
@@ -418,19 +427,18 @@ export class SessionMessagesList extends React.Component {
}
}
- if (!this.state.doneInitialScroll && messages.length > 0) {
- this.setState(
- {
- doneInitialScroll: true,
- },
- this.updateReadMessages
- );
+ if (this.ignoreScrollEvents && messages.length > 0) {
+ this.ignoreScrollEvents = false;
+ this.updateReadMessages();
}
}
- private scrollToMessage(messageId: string) {
+ private scrollToMessage(messageId: string, smooth: boolean = false) {
const topUnreadMessage = document.getElementById(messageId);
- topUnreadMessage?.scrollIntoView(false);
+ topUnreadMessage?.scrollIntoView({
+ behavior: smooth ? 'smooth' : 'auto',
+ block: 'center',
+ });
const messageContainer = this.messageContainerRef.current;
if (!messageContainer) {
@@ -510,11 +518,11 @@ export class SessionMessagesList extends React.Component {
// return;
// }
// this probably does not work for us as we need to call getMessages before
- this.scrollToMessage(databaseId);
+ this.scrollToMessage(databaseId, true);
}
// basically the offset in px from the bottom of the view (most recent message)
- private getScrollOffsetPx() {
+ private getScrollOffsetBottomPx() {
const messageContainer = this.messageContainerRef?.current;
if (!messageContainer) {
@@ -524,8 +532,6 @@ export class SessionMessagesList extends React.Component {
const scrollTop = messageContainer.scrollTop;
const scrollHeight = messageContainer.scrollHeight;
const clientHeight = messageContainer.clientHeight;
- const scrollOffsetPx = scrollHeight - scrollTop - clientHeight;
-
- return scrollOffsetPx;
+ return scrollHeight - scrollTop - clientHeight;
}
}
diff --git a/ts/state/ducks/conversations.ts b/ts/state/ducks/conversations.ts
index 9e6f14e89..c58876f97 100644
--- a/ts/state/ducks/conversations.ts
+++ b/ts/state/ducks/conversations.ts
@@ -568,11 +568,17 @@ export function reducer(
m => m.id === messageId
);
if (messageInStoreIndex >= 0) {
- // we cannot edit the array directly, so slice the first part, and slice the second part
+ // we cannot edit the array directly, so slice the first part, and slice the second part,
+ // keeping the index removed out
const editedMessages = [
...state.messages.slice(0, messageInStoreIndex),
...state.messages.slice(messageInStoreIndex + 1),
];
+
+ // FIXME two other thing we have to do:
+ // * update the last message text if the message deleted was the last one
+ // * update the unread count of the convo if the message was one one counted as an unread
+
return {
...state,
messages: editedMessages,