From db6f9dadf7f9c5dc0ed9c800670c4377612e99c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Tue, 12 May 2020 17:53:10 +0200 Subject: [PATCH 1/3] MOBILE-3382 messages: Show badge to scroll bottom --- .../messages/pages/discussion/discussion.html | 9 +- .../messages/pages/discussion/discussion.scss | 25 +++++ .../messages/pages/discussion/discussion.ts | 106 ++++++++++++++++-- 3 files changed, 130 insertions(+), 10 deletions(-) diff --git a/src/addon/messages/pages/discussion/discussion.html b/src/addon/messages/pages/discussion/discussion.html index a869dad74..fbe0c89b3 100644 --- a/src/addon/messages/pages/discussion/discussion.html +++ b/src/addon/messages/pages/discussion/discussion.html @@ -41,7 +41,7 @@ - {{ 'addon.messages.newmessages' | translate:{$a: title} }} + {{ 'addon.messages.newmessages' | translate }} @@ -68,6 +68,13 @@ + + + + diff --git a/src/addon/messages/pages/discussion/discussion.scss b/src/addon/messages/pages/discussion/discussion.scss index 0c2237fc2..240d98af0 100644 --- a/src/addon/messages/pages/discussion/discussion.scss +++ b/src/addon/messages/pages/discussion/discussion.scss @@ -4,6 +4,9 @@ $item-message-note-text: $gray-dark !default; $item-message-note-font-size: 75% !default; $item-message-mine-bg: $gray-light !default; +$core-discussion-messages-badge: $core-color !default; +$core-discussion-messages-badge-text: $white !default; + @mixin message-page { ion-content { background-color: $gray-lighter !important; @@ -194,6 +197,28 @@ $item-message-mine-bg: $gray-light !default; border-top-right-radius: 0; border-top-left-radius: 0; } + + .has-fab .scroll-content { + padding-bottom: 0; + } + ion-fab button { + overflow: visible; + position: relative; + .core-discussion-messages-badge { + position: absolute; + border-radius: 50%; + color: $core-discussion-messages-badge-text; + background-color: $core-discussion-messages-badge; + display: block; + line-height: 20px; + height: 20px; + width: 20px; + right: -6px; + top: -6px; + + } + } + } diff --git a/src/addon/messages/pages/discussion/discussion.ts b/src/addon/messages/pages/discussion/discussion.ts index a5b79b15f..043376f1d 100644 --- a/src/addon/messages/pages/discussion/discussion.ts +++ b/src/addon/messages/pages/discussion/discussion.ts @@ -65,6 +65,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy { protected viewDestroyed = false; protected memberInfoObserver: any; protected showLoadingModal = false; // Whether to show a loading modal while fetching data. + protected scrollListener; conversationId: number; // Conversation ID. Undefined if it's a new individual conversation. conversation: AddonMessagesConversationFormatted; // The conversation object (if it exists). @@ -95,6 +96,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy { isSelf = false; muteEnabled = false; muteIcon = 'volume-off'; + newMessages = 0; constructor(private eventsProvider: CoreEventsProvider, sitesProvider: CoreSitesProvider, navParams: NavParams, private userProvider: CoreUserProvider, private navCtrl: NavController, private messagesSync: AddonMessagesSyncProvider, @@ -134,6 +136,8 @@ export class AddonMessagesDiscussionPage implements OnDestroy { this.fetchData(); } }, this.siteId); + + this.scrollListener = this.scrollListenerFunction.bind(this); } /** @@ -141,21 +145,26 @@ export class AddonMessagesDiscussionPage implements OnDestroy { * * @param message Message to be added. * @param keep If set the keep flag or not. + * @return If message is not mine and was recently added. */ protected addMessage(message: AddonMessagesConversationMessageFormatted | AddonMessagesGetMessagesMessageFormatted, - keep: boolean = true): void { + keep: boolean = true): boolean { /* Create a hash to identify the message. The text of online messages isn't reliable because it can have random data like VideoJS ID. Try to use id and fallback to text for offline messages. */ message.hash = Md5.hashAsciiStr(String(message.id || message.text || '')) + '#' + message.timecreated + '#' + message.useridfrom; + let added = false; if (typeof this.keepMessageMap[message.hash] === 'undefined') { // Message not added to the list. Add it now. this.messages.push(message); + added = message.useridfrom != this.currentUserId; } // Message needs to be kept in the list. this.keepMessageMap[message.hash] = keep; + + return added; } /** @@ -306,9 +315,10 @@ export class AddonMessagesDiscussionPage implements OnDestroy { /** * Convenience function to fetch messages. * + * @param messagesAreNew If messages loaded are new messages. * @return Resolved when done. */ - protected fetchMessages(): Promise { + protected fetchMessages(messagesAreNew: boolean = true): Promise { this.loadMoreError = false; if (this.messagesBeingSent > 0) { @@ -348,7 +358,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy { }); } }).then((messages: (AddonMessagesConversationMessageFormatted | AddonMessagesGetMessagesMessageFormatted)[]) => { - this.loadMessages(messages); + this.loadMessages(messages, messagesAreNew); }).finally(() => { this.fetching = false; }); @@ -357,10 +367,11 @@ export class AddonMessagesDiscussionPage implements OnDestroy { /** * Format and load a list of messages into the view. * + * @param messagesAreNew If messages loaded are new messages. * @param messages Messages to load. */ - protected loadMessages(messages: (AddonMessagesConversationMessageFormatted | AddonMessagesGetMessagesMessageFormatted)[]) - : void { + protected loadMessages(messages: (AddonMessagesConversationMessageFormatted | AddonMessagesGetMessagesMessageFormatted)[], + messagesAreNew: boolean = true): void { if (this.viewDestroyed) { return; @@ -380,9 +391,14 @@ export class AddonMessagesDiscussionPage implements OnDestroy { } // Add new messages to the list and mark the messages that should still be displayed. - messages.forEach((message) => { - this.addMessage(message); - }); + const newMessages = messages.reduce((val, message) => { + return val + (this.addMessage(message) ? 1 : 0); + }, 0); + + // Set the new badges message if we're loading new messages. + if (messagesAreNew) { + this.setNewMessagesBadge(this.newMessages + newMessages); + } // Remove messages that shouldn't be in the list anymore. for (const hash in this.keepMessageMap) { @@ -414,6 +430,63 @@ export class AddonMessagesDiscussionPage implements OnDestroy { this.markMessagesAsRead(forceMark); } + /** + * Set the new message badge number and set scroll listener if needed. + * + * @param addMessages NUmber of messages still to be read. + */ + protected setNewMessagesBadge(addMessages: number): void { + if (this.newMessages == 0 && addMessages > 0) { + // Setup scrolling. + this.content.getScrollElement().addEventListener('scroll', this.scrollListener); + + this.scrollListenerFunction(); + } else if (this.newMessages > 0 && addMessages == 0) { + // Remove scrolling. + this.content.getScrollElement().removeEventListener('scroll', this.scrollListener); + } + + this.newMessages = addMessages; + } + + /** + * The scroll was moved. Update new messages count. + */ + protected scrollListenerFunction(): void { + if (this.newMessages > 0) { + const scrollBottom = this.domUtils.getScrollTop(this.content) + this.domUtils.getContentHeight(this.content); + const scrollHeight = this.domUtils.getScrollHeight(this.content); + if (scrollBottom > scrollHeight - 40) { + // At the bottom, reset. + this.setNewMessagesBadge(0); + + return; + } + + const scrollElRect = this.content.getScrollElement().getBoundingClientRect(); + const scrollBottomPos = (scrollElRect && scrollElRect.bottom) || 0; + + if (scrollBottomPos == 0) { + return; + } + + const messages = Array.from(document.querySelectorAll('.addon-message-not-mine')).slice(-this.newMessages).reverse(); + + const newMessagesUnread = messages.findIndex((message, index) => { + const elementRect = message.getBoundingClientRect(); + if (!elementRect) { + return false; + } + + return elementRect.bottom <= scrollBottomPos; + }); + + if (newMessagesUnread > 0 && newMessagesUnread < this.newMessages) { + this.setNewMessagesBadge(newMessagesUnread); + } + } + } + /** * Get the conversation. * @@ -887,7 +960,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy { return this.waitForFetch().finally(() => { this.pagesLoaded++; - this.fetchMessages().then(() => { + this.fetchMessages(false).then(() => { // Try to keep the scroll position. const scrollBottom = scrollHeight - this.domUtils.getScrollTop(this.content); @@ -972,6 +1045,20 @@ export class AddonMessagesDiscussionPage implements OnDestroy { } }); this.scrollBottom = false; + + // Reset the badge. + this.setNewMessagesBadge(0); + } + } + + /** + * Scroll to the first new unread message. + */ + scrollToFirstUnreadMessage(): void { + if (this.newMessages > 0) { + const messages = Array.from(document.querySelectorAll('.addon-message-not-mine')); + + this.domUtils.scrollToElement(this.content, messages[messages.length - this.newMessages]); } } @@ -987,6 +1074,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy { this.showDelete = false; this.scrollBottom = true; + this.setNewMessagesBadge(0); message = { id: null, From b30e578db6e143cd466c036a27a00946ec720af3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Wed, 13 May 2020 15:51:41 +0200 Subject: [PATCH 2/3] MOBILE-3414 login: Fix typo on previous button --- src/core/login/pages/site-onboarding/site-onboarding.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/login/pages/site-onboarding/site-onboarding.html b/src/core/login/pages/site-onboarding/site-onboarding.html index 8c3e1ba5b..df63a68bd 100644 --- a/src/core/login/pages/site-onboarding/site-onboarding.html +++ b/src/core/login/pages/site-onboarding/site-onboarding.html @@ -1,7 +1,7 @@ - From b031919ccad709d6189522b7084fa9cd4213ce57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Mon, 18 May 2020 12:47:23 +0200 Subject: [PATCH 3/3] MOBILE-3402 login: Recover minimum version and deprecate --- src/core/login/pages/site/site.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/core/login/pages/site/site.ts b/src/core/login/pages/site/site.ts index 5ee7ab0d3..4585bfbb8 100644 --- a/src/core/login/pages/site/site.ts +++ b/src/core/login/pages/site/site.ts @@ -14,6 +14,7 @@ import { Component, ViewChild, ElementRef } from '@angular/core'; import { IonicPage, NavController, ModalController, AlertController, NavParams } from 'ionic-angular'; +import { CoreSite } from '@classes/site'; import { CoreAppProvider } from '@providers/app'; import { CoreEventsProvider } from '@providers/events'; import { CoreSitesProvider, CoreSiteCheckResponse, CoreLoginSiteInfo } from '@providers/sites'; @@ -293,8 +294,9 @@ export class CoreLoginSitePage { } ]; + // @TODO: Remove CoreSite.MINIMUM_MOODLE_VERSION, not used on translations since 3.8.3. this.domUtils.showAlertWithOptions({ - title: this.translate.instant('core.cannotconnect'), + title: this.translate.instant('core.cannotconnect', {$a: CoreSite.MINIMUM_MOODLE_VERSION}), message, buttons, });