Mark messages read only when visible, on receipt, focus, scroll

- Only mark messages read when scrolling if in focus and visible
- Remove last seen indicator when scrolling to the bottom with scroll
  down button
- Update last seen indicator when we don't already have one and we're
  scrolled up.

FREEBIE
pull/749/head
Scott Nonnenberg 8 years ago
parent 9a0a87ab40
commit b60b20bde4

@ -71,7 +71,7 @@
}.bind(this)); }.bind(this));
}, },
getUnread: function() { getUnread: function(newestUnreadDate) {
var conversationId = this.id; var conversationId = this.id;
var unreadMessages = new Whisper.MessageCollection(); var unreadMessages = new Whisper.MessageCollection();
return new Promise(function(resolve) { return new Promise(function(resolve) {
@ -83,7 +83,15 @@
upper : [conversationId, Number.MAX_VALUE], upper : [conversationId, Number.MAX_VALUE],
} }
}).always(function() { }).always(function() {
resolve(unreadMessages); if (!newestUnreadDate) {
return resolve(unreadMessages);
}
// TODO: look into an index which would allow us to efficiently get the
// set of unread messages before a certain date.
resolve(unreadMessages.filter(function(message) {
return message.get('received_at') <= newestUnreadDate;
}));
}); });
}); });
@ -291,15 +299,14 @@
} }
}, },
markRead: function() { markRead: function(newestUnreadDate) {
if (this.get('unreadCount') > 0) { if (this.get('unreadCount') > 0) {
this.save({ unreadCount: 0 });
var conversationId = this.id; var conversationId = this.id;
Whisper.Notifications.remove(Whisper.Notifications.where({ Whisper.Notifications.remove(Whisper.Notifications.where({
conversationId: conversationId conversationId: conversationId
})); }));
this.getUnread().then(function(unreadMessages) { this.getUnread(newestUnreadDate).then(function(unreadMessages) {
var read = unreadMessages.map(function(m) { var read = unreadMessages.map(function(m) {
if (this.messageCollection.get(m.id)) { if (this.messageCollection.get(m.id)) {
m = this.messageCollection.get(m.id); m = this.messageCollection.get(m.id);
@ -313,7 +320,16 @@
timestamp : m.get('sent_at') timestamp : m.get('sent_at')
}; };
}.bind(this)); }.bind(this));
if (read.length > 0) { if (read.length > 0) {
var unreadCount = this.get('unreadCount');
unreadCount = unreadCount - read.length;
if (unreadCount < 0) {
console.log('conversation unreadCount went below zero!');
unreadCount = 0;
}
this.save({ unreadCount: unreadCount });
console.log('Sending', read.length, 'read receipts'); console.log('Sending', read.length, 'read receipts');
textsecure.messaging.syncReadMessages(read); textsecure.messaging.syncReadMessages(read);
} }

@ -160,6 +160,7 @@
'newOffscreenMessage .message-list': 'addScrollDownButtonWithCount', 'newOffscreenMessage .message-list': 'addScrollDownButtonWithCount',
'atBottom .message-list': 'hideScrollDownButton', 'atBottom .message-list': 'hideScrollDownButton',
'farFromBottom .message-list': 'addScrollDownButton', 'farFromBottom .message-list': 'addScrollDownButton',
'lazyScroll .message-list': 'onLazyScroll',
'close .menu': 'closeMenu', 'close .menu': 'closeMenu',
'select .message-list .entry': 'messageDetail', 'select .message-list .entry': 'messageDetail',
'force-resize': 'forceUpdateMessageFieldSize', 'force-resize': 'forceUpdateMessageFieldSize',
@ -206,9 +207,14 @@
this.$('.bottom-bar form').addClass('active'); this.$('.bottom-bar form').addClass('active');
}, },
onLazyScroll: function() {
if (!this.isHidden() && window.isFocused()) {
this.markRead();
}
},
updateUnread: function() { updateUnread: function() {
this.updateLastSeenIndicator(); this.updateLastSeenIndicator();
this.model.markRead(); this.markRead();
}, },
onOpened: function() { onOpened: function() {
@ -266,6 +272,8 @@
if (location > 0) { if (location > 0) {
this.lastSeenIndicator.el.scrollIntoView(); this.lastSeenIndicator.el.scrollIntoView();
return; return;
} else {
this.removeLastSeenIndicator();
} }
} }
this.view.scrollToBottom(); this.view.scrollToBottom();
@ -362,8 +370,6 @@
this.model.messageCollection.add(message, {merge: true}); this.model.messageCollection.add(message, {merge: true});
message.setToExpire(); message.setToExpire();
// If the last seen indicator is old enough, it will go away.
// if it isn't, we want to make sure it's up to date
if (this.lastSeenIndicator) { if (this.lastSeenIndicator) {
this.lastSeenIndicator.increment(1); this.lastSeenIndicator.increment(1);
} }
@ -374,10 +380,11 @@
} }
else if (!this.isHidden() && window.isFocused()) { else if (!this.isHidden() && window.isFocused()) {
// The conversation is visible and in focus // The conversation is visible and in focus
this.markRead();
if (this.view.atBottom()) { // When we're scrolled up and we don't already have a last seen indicator
this.markRead(); // we add a new one.
} else { if (!this.view.atBottom() && !this.lastSeenIndicator) {
this.updateLastSeenIndicator({scroll: false}); this.updateLastSeenIndicator({scroll: false});
} }
} }
@ -402,8 +409,58 @@
this.markRead(e); this.markRead(e);
}, },
findNewestVisibleUnread: function() {
var collection = this.model.messageCollection;
var length = collection.length;
var viewportBottom = this.view.outerHeight;
var unreadCount = this.model.get('unreadCount');
if (unreadCount < 1) {
return;
}
// Start with the most recent message, search backwards in time
var foundUnread = 0;
for (var i = length - 1; i >= 0; i -= 1) {
// We don't want to search through all messages, so we stop after we've
// hit all unread messages. The unread should be relatively recent.
if (foundUnread >= unreadCount) {
return;
}
var message = collection.at(i);
if (!message.get('unread')) {
continue;
}
foundUnread += 1;
var el = this.$('#' + message.id);
var position = el.position();
var top = position.top;
// We're fully below the viewport, continue searching up.
if (top > viewportBottom) {
continue;
}
// If the bottom fits on screen, we'll call it visible. Even if the
// message is really tall.
var height = el.height();
var bottom = top + height;
if (bottom <= viewportBottom) {
return message;
}
// Continue searching up.
}
},
markRead: function(e) { markRead: function(e) {
this.model.markRead(); var unread = this.findNewestVisibleUnread();
if (unread) {
this.model.markRead(unread.get('received_at'));
}
}, },
verifyIdentity: function(ev, model) { verifyIdentity: function(ev, model) {

@ -12,6 +12,13 @@
events: { events: {
'scroll': 'onScroll', 'scroll': 'onScroll',
}, },
initialize: function() {
Whisper.ListView.prototype.initialize.call(this);
this.triggerLazyScroll = _.debounce(function() {
this.$el.trigger('lazyScroll');
}.bind(this), 500);
},
onScroll: function() { onScroll: function() {
this.measureScrollPosition(); this.measureScrollPosition();
if (this.$el.scrollTop() === 0) { if (this.$el.scrollTop() === 0) {
@ -22,6 +29,8 @@
} else if (this.bottomOffset > this.outerHeight) { } else if (this.bottomOffset > this.outerHeight) {
this.$el.trigger('farFromBottom'); this.$el.trigger('farFromBottom');
} }
this.triggerLazyScroll();
}, },
atBottom: function() { atBottom: function() {
return this.bottomOffset < 30; return this.bottomOffset < 30;

Loading…
Cancel
Save