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
This commit is contained in:
parent
9a0a87ab40
commit
b60b20bde4
3 changed files with 94 additions and 12 deletions
|
@ -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…
Add table
Add a link
Reference in a new issue