From fed26c36ca49a086b668f8063eba43b0250b2b74 Mon Sep 17 00:00:00 2001 From: Scott Nonnenberg Date: Wed, 17 May 2017 14:32:03 -0700 Subject: [PATCH] Add new Last Seen Indicator with unread count, scroll to it This is to ensure that when there are a lot of unread messages, the user is given the chance to see all of them by being scrolled to the oldest new message. When a new message comes in, the indicator will be incremented. When the user sends a message or switches away from the conversation, the last seen indicator will be removed. FREEBIE --- _locales/en/messages.json | 14 +++++++++ background.html | 6 ++++ js/views/conversation_view.js | 47 ++++++++++++++++++++++++++-- js/views/last_seen_indicator_view.js | 30 ++++++++++++++++++ stylesheets/_android.scss | 4 +++ stylesheets/_conversation.scss | 14 +++++++++ stylesheets/android-dark.scss | 5 +++ stylesheets/manifest.css | 15 +++++++++ 8 files changed, 133 insertions(+), 2 deletions(-) create mode 100644 js/views/last_seen_indicator_view.js diff --git a/_locales/en/messages.json b/_locales/en/messages.json index e8bd87cd1..c41337eee 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -3,6 +3,20 @@ "message": "You left the group", "description": "Displayed when a user can't send a message because they have left the group" }, + "unreadMessage": { + "message": "1 unread message", + "description": "Text for unread message separator, just one message" + }, + "unreadMessages": { + "message": "$count$ unread messages", + "description": "Text for unread message separator, with count", + "placeholders": { + "count": { + "content": "$1", + "example": "5" + } + } + }, "debugLogExplanation": { "message": "This log will be posted publicly online for contributors to view. You may examine and edit it before submitting." }, diff --git a/background.html b/background.html index a6ea82e61..242975df7 100644 --- a/background.html +++ b/background.html @@ -42,6 +42,11 @@ + + diff --git a/js/views/conversation_view.js b/js/views/conversation_view.js index af051d554..2fc333275 100644 --- a/js/views/conversation_view.js +++ b/js/views/conversation_view.js @@ -201,11 +201,45 @@ this.$('.bottom-bar form').addClass('active'); }, + updateUnread: function() { + this.updateLastSeenIndicator(); + this.model.markRead(); + }, + onOpened: function() { this.view.resetScrollPosition(); this.$el.trigger('force-resize'); this.focusMessageField(); - this.model.markRead(); + + if (this.inProgressFetch) { + this.inProgressFetch.then(this.updateUnread.bind(this)); + } else { + this.updateUnread(); + } + }, + + removeLastSeenIndicator: function() { + if (this.lastSeenIndicator) { + this.lastSeenIndicator.remove(); + this.lastSeenIndicator = null; + } + }, + + updateLastSeenIndicator: function() { + this.removeLastSeenIndicator(); + + var oldestUnread = this.model.messageCollection.find(function(model) { + return model.get('unread'); + }); + + if (oldestUnread) { + var unreadCount = this.model.get('unreadCount'); + this.lastSeenIndicator = new Whisper.LastSeenIndicatorView({count: unreadCount}); + var unreadEl = this.lastSeenIndicator.render().$el; + + unreadEl.insertBefore(this.$('#' + oldestUnread.get('id'))); + var position = unreadEl[0].scrollIntoView(true); + } }, focusMessageField: function() { @@ -215,15 +249,18 @@ fetchMessages: function() { console.log('fetchMessages'); this.$('.bar-container').show(); - return this.model.fetchContacts().then(function() { + this.inProgressFetch = this.model.fetchContacts().then(function() { return this.model.fetchMessages().then(function() { this.$('.bar-container').hide(); this.model.messageCollection.where({unread: 1}).forEach(function(m) { m.fetch(); }); + this.inProgressFetch = null; }.bind(this)); }.bind(this)); // TODO catch? + + return this.inProgressFetch; }, onExpired: function(message) { @@ -241,6 +278,10 @@ this.model.messageCollection.add(message, {merge: true}); message.setToExpire(); + if (this.lastSeenIndicator) { + this.lastSeenIndicator.increment(1); + } + if (!this.isHidden() && window.isFocused()) { this.markRead(); } @@ -345,6 +386,8 @@ }, sendMessage: function(e) { + this.removeLastSeenIndicator(); + var toast; if (extension.expired()) { toast = new Whisper.ExpiredToast(); diff --git a/js/views/last_seen_indicator_view.js b/js/views/last_seen_indicator_view.js new file mode 100644 index 000000000..0ce6a945a --- /dev/null +++ b/js/views/last_seen_indicator_view.js @@ -0,0 +1,30 @@ +/* + * vim: ts=4:sw=4:expandtab + */ +(function () { + 'use strict'; + window.Whisper = window.Whisper || {}; + + Whisper.LastSeenIndicatorView = Whisper.View.extend({ + className: 'last-seen-indicator-view', + templateName: 'last-seen-indicator-view', + initialize: function(options) { + options = options || {}; + this.count = options.count || 0; + }, + + increment: function(count) { + this.count += count; + this.render(); + }, + + render_attributes: function() { + var unreadMessages = this.count === 1 ? i18n('unreadMessage') + : i18n('unreadMessages', [this.count]); + + return { + unreadMessages: unreadMessages + }; + } + }); +})(); diff --git a/stylesheets/_android.scss b/stylesheets/_android.scss index 03824943e..2ea2631fc 100644 --- a/stylesheets/_android.scss +++ b/stylesheets/_android.scss @@ -70,4 +70,8 @@ .inactive button.back { @include header-icon-black('/images/back.svg'); } + + .message-list .last-seen-indicator-view .text { + margin-top: 2em; + } } diff --git a/stylesheets/_conversation.scss b/stylesheets/_conversation.scss index 1308290df..aed6f3523 100644 --- a/stylesheets/_conversation.scss +++ b/stylesheets/_conversation.scss @@ -682,3 +682,17 @@ li.entry .error-icon-container { border-radius: $border-radius; } } + +.message-list .last-seen-indicator-view { + display: flex; + flex-direction: column; + align-items: center; + + .text { + border-radius: $border-radius; + padding: 5px 10px; + margin: 1em; + + background-color: $grey_l2; + } +} diff --git a/stylesheets/android-dark.scss b/stylesheets/android-dark.scss index 51fff994a..d7ec3f26d 100644 --- a/stylesheets/android-dark.scss +++ b/stylesheets/android-dark.scss @@ -202,4 +202,9 @@ $text-dark: #CCCCCC; .recorder { background: $grey-dark_l2; } + + .message-list .last-seen-indicator-view .text { + margin-top: 2em; + background-color: $grey-dark_l2; + } } diff --git a/stylesheets/manifest.css b/stylesheets/manifest.css index 0e31b59a3..df5b4f0f6 100644 --- a/stylesheets/manifest.css +++ b/stylesheets/manifest.css @@ -1496,6 +1496,16 @@ li.entry .error-icon-container { color: #454545; border-radius: 5px; } +.message-list .last-seen-indicator-view { + display: flex; + flex-direction: column; + align-items: center; } + .message-list .last-seen-indicator-view .text { + border-radius: 5px; + padding: 5px 10px; + margin: 1em; + background-color: #d9d9d9; } + .ios #header { height: 64px; border-bottom: 1px solid rgba(0, 0, 0, 0.05); @@ -1838,6 +1848,8 @@ li.entry .error-icon-container { -webkit-mask: url("/images/back.svg") no-repeat center; -webkit-mask-size: 100%; background-color: black; } +.android .message-list .last-seen-indicator-view .text { + margin-top: 2em; } .android-dark { color: #CCCCCC; } @@ -2098,5 +2110,8 @@ li.entry .error-icon-container { background-color: #292929; } .android-dark .recorder { background: #292929; } + .android-dark .message-list .last-seen-indicator-view .text { + margin-top: 2em; + background-color: #292929; } /*# sourceMappingURL=manifest.css.map */