MOBILE-3382 messages: Show badge to scroll bottom
parent
a9aba7739b
commit
db6f9dadf7
|
@ -41,7 +41,7 @@
|
||||||
</h6>
|
</h6>
|
||||||
|
|
||||||
<ion-chip class="addon-messages-unreadfrom" *ngIf="unreadMessageFrom && message.id == unreadMessageFrom" color="light">
|
<ion-chip class="addon-messages-unreadfrom" *ngIf="unreadMessageFrom && message.id == unreadMessageFrom" color="light">
|
||||||
<ion-label>{{ 'addon.messages.newmessages' | translate:{$a: title} }}</ion-label>
|
<ion-label>{{ 'addon.messages.newmessages' | translate }}</ion-label>
|
||||||
<ion-icon name="arrow-round-down"></ion-icon>
|
<ion-icon name="arrow-round-down"></ion-icon>
|
||||||
</ion-chip>
|
</ion-chip>
|
||||||
|
|
||||||
|
@ -68,6 +68,13 @@
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ion-list>
|
</ion-list>
|
||||||
|
<!-- Scroll bottom. -->
|
||||||
|
<ion-fab core-fab bottom end *ngIf="newMessages > 0">
|
||||||
|
<button ion-fab mini (click)="scrollToFirstUnreadMessage(true)" color="light" [attr.aria-label]="'addon.messages.newmessages' | translate">
|
||||||
|
<ion-icon name="arrow-round-down"></ion-icon>
|
||||||
|
<span class="core-discussion-messages-badge">{{ newMessages }}</span>
|
||||||
|
</button>
|
||||||
|
</ion-fab>
|
||||||
<core-empty-box *ngIf="!messages || messages.length <= 0" icon="chatbubbles" [message]="'addon.messages.nomessagesfound' | translate"></core-empty-box>
|
<core-empty-box *ngIf="!messages || messages.length <= 0" icon="chatbubbles" [message]="'addon.messages.nomessagesfound' | translate"></core-empty-box>
|
||||||
</core-loading>
|
</core-loading>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
|
|
|
@ -4,6 +4,9 @@ $item-message-note-text: $gray-dark !default;
|
||||||
$item-message-note-font-size: 75% !default;
|
$item-message-note-font-size: 75% !default;
|
||||||
$item-message-mine-bg: $gray-light !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 {
|
@mixin message-page {
|
||||||
ion-content {
|
ion-content {
|
||||||
background-color: $gray-lighter !important;
|
background-color: $gray-lighter !important;
|
||||||
|
@ -194,6 +197,28 @@ $item-message-mine-bg: $gray-light !default;
|
||||||
border-top-right-radius: 0;
|
border-top-right-radius: 0;
|
||||||
border-top-left-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;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -65,6 +65,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
|
||||||
protected viewDestroyed = false;
|
protected viewDestroyed = false;
|
||||||
protected memberInfoObserver: any;
|
protected memberInfoObserver: any;
|
||||||
protected showLoadingModal = false; // Whether to show a loading modal while fetching data.
|
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.
|
conversationId: number; // Conversation ID. Undefined if it's a new individual conversation.
|
||||||
conversation: AddonMessagesConversationFormatted; // The conversation object (if it exists).
|
conversation: AddonMessagesConversationFormatted; // The conversation object (if it exists).
|
||||||
|
@ -95,6 +96,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
|
||||||
isSelf = false;
|
isSelf = false;
|
||||||
muteEnabled = false;
|
muteEnabled = false;
|
||||||
muteIcon = 'volume-off';
|
muteIcon = 'volume-off';
|
||||||
|
newMessages = 0;
|
||||||
|
|
||||||
constructor(private eventsProvider: CoreEventsProvider, sitesProvider: CoreSitesProvider, navParams: NavParams,
|
constructor(private eventsProvider: CoreEventsProvider, sitesProvider: CoreSitesProvider, navParams: NavParams,
|
||||||
private userProvider: CoreUserProvider, private navCtrl: NavController, private messagesSync: AddonMessagesSyncProvider,
|
private userProvider: CoreUserProvider, private navCtrl: NavController, private messagesSync: AddonMessagesSyncProvider,
|
||||||
|
@ -134,6 +136,8 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
|
||||||
this.fetchData();
|
this.fetchData();
|
||||||
}
|
}
|
||||||
}, this.siteId);
|
}, this.siteId);
|
||||||
|
|
||||||
|
this.scrollListener = this.scrollListenerFunction.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -141,21 +145,26 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
|
||||||
*
|
*
|
||||||
* @param message Message to be added.
|
* @param message Message to be added.
|
||||||
* @param keep If set the keep flag or not.
|
* @param keep If set the keep flag or not.
|
||||||
|
* @return If message is not mine and was recently added.
|
||||||
*/
|
*/
|
||||||
protected addMessage(message: AddonMessagesConversationMessageFormatted | AddonMessagesGetMessagesMessageFormatted,
|
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
|
/* 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. */
|
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.hash = Md5.hashAsciiStr(String(message.id || message.text || '')) + '#' + message.timecreated + '#' +
|
||||||
message.useridfrom;
|
message.useridfrom;
|
||||||
|
|
||||||
|
let added = false;
|
||||||
if (typeof this.keepMessageMap[message.hash] === 'undefined') {
|
if (typeof this.keepMessageMap[message.hash] === 'undefined') {
|
||||||
// Message not added to the list. Add it now.
|
// Message not added to the list. Add it now.
|
||||||
this.messages.push(message);
|
this.messages.push(message);
|
||||||
|
added = message.useridfrom != this.currentUserId;
|
||||||
}
|
}
|
||||||
// Message needs to be kept in the list.
|
// Message needs to be kept in the list.
|
||||||
this.keepMessageMap[message.hash] = keep;
|
this.keepMessageMap[message.hash] = keep;
|
||||||
|
|
||||||
|
return added;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -306,9 +315,10 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
|
||||||
/**
|
/**
|
||||||
* Convenience function to fetch messages.
|
* Convenience function to fetch messages.
|
||||||
*
|
*
|
||||||
|
* @param messagesAreNew If messages loaded are new messages.
|
||||||
* @return Resolved when done.
|
* @return Resolved when done.
|
||||||
*/
|
*/
|
||||||
protected fetchMessages(): Promise<void> {
|
protected fetchMessages(messagesAreNew: boolean = true): Promise<void> {
|
||||||
this.loadMoreError = false;
|
this.loadMoreError = false;
|
||||||
|
|
||||||
if (this.messagesBeingSent > 0) {
|
if (this.messagesBeingSent > 0) {
|
||||||
|
@ -348,7 +358,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).then((messages: (AddonMessagesConversationMessageFormatted | AddonMessagesGetMessagesMessageFormatted)[]) => {
|
}).then((messages: (AddonMessagesConversationMessageFormatted | AddonMessagesGetMessagesMessageFormatted)[]) => {
|
||||||
this.loadMessages(messages);
|
this.loadMessages(messages, messagesAreNew);
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
this.fetching = false;
|
this.fetching = false;
|
||||||
});
|
});
|
||||||
|
@ -357,10 +367,11 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
|
||||||
/**
|
/**
|
||||||
* Format and load a list of messages into the view.
|
* Format and load a list of messages into the view.
|
||||||
*
|
*
|
||||||
|
* @param messagesAreNew If messages loaded are new messages.
|
||||||
* @param messages Messages to load.
|
* @param messages Messages to load.
|
||||||
*/
|
*/
|
||||||
protected loadMessages(messages: (AddonMessagesConversationMessageFormatted | AddonMessagesGetMessagesMessageFormatted)[])
|
protected loadMessages(messages: (AddonMessagesConversationMessageFormatted | AddonMessagesGetMessagesMessageFormatted)[],
|
||||||
: void {
|
messagesAreNew: boolean = true): void {
|
||||||
|
|
||||||
if (this.viewDestroyed) {
|
if (this.viewDestroyed) {
|
||||||
return;
|
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.
|
// Add new messages to the list and mark the messages that should still be displayed.
|
||||||
messages.forEach((message) => {
|
const newMessages = messages.reduce((val, message) => {
|
||||||
this.addMessage(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.
|
// Remove messages that shouldn't be in the list anymore.
|
||||||
for (const hash in this.keepMessageMap) {
|
for (const hash in this.keepMessageMap) {
|
||||||
|
@ -414,6 +430,63 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
|
||||||
this.markMessagesAsRead(forceMark);
|
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.
|
* Get the conversation.
|
||||||
*
|
*
|
||||||
|
@ -887,7 +960,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
|
||||||
return this.waitForFetch().finally(() => {
|
return this.waitForFetch().finally(() => {
|
||||||
this.pagesLoaded++;
|
this.pagesLoaded++;
|
||||||
|
|
||||||
this.fetchMessages().then(() => {
|
this.fetchMessages(false).then(() => {
|
||||||
|
|
||||||
// Try to keep the scroll position.
|
// Try to keep the scroll position.
|
||||||
const scrollBottom = scrollHeight - this.domUtils.getScrollTop(this.content);
|
const scrollBottom = scrollHeight - this.domUtils.getScrollTop(this.content);
|
||||||
|
@ -972,6 +1045,20 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.scrollBottom = false;
|
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, <HTMLElement> messages[messages.length - this.newMessages]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -987,6 +1074,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
|
||||||
|
|
||||||
this.showDelete = false;
|
this.showDelete = false;
|
||||||
this.scrollBottom = true;
|
this.scrollBottom = true;
|
||||||
|
this.setNewMessagesBadge(0);
|
||||||
|
|
||||||
message = {
|
message = {
|
||||||
id: null,
|
id: null,
|
||||||
|
|
Loading…
Reference in New Issue