Merge pull request #2376 from crazyserver/MOBILE-3382

Mobile 3382
main
Juan Leyva 2020-05-19 17:29:41 +02:00 committed by GitHub
commit 9057b62333
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 134 additions and 12 deletions

View File

@ -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>

View File

@ -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;
}
}
} }

View File

@ -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,

View File

@ -1,7 +1,7 @@
<ion-header> <ion-header>
<ion-navbar> <ion-navbar>
<ion-buttons step> <ion-buttons step>
<button ion-button icon-only (click)="previous($$event)" [attr.aria-label]="'core.back' | translate"> <button ion-button icon-only (click)="previous($event)" [attr.aria-label]="'core.back' | translate">
<ion-icon name="arrow-back"></ion-icon> <ion-icon name="arrow-back"></ion-icon>
</button> </button>
</ion-buttons> </ion-buttons>

View File

@ -14,6 +14,7 @@
import { Component, ViewChild, ElementRef } from '@angular/core'; import { Component, ViewChild, ElementRef } from '@angular/core';
import { IonicPage, NavController, ModalController, AlertController, NavParams } from 'ionic-angular'; import { IonicPage, NavController, ModalController, AlertController, NavParams } from 'ionic-angular';
import { CoreSite } from '@classes/site';
import { CoreAppProvider } from '@providers/app'; import { CoreAppProvider } from '@providers/app';
import { CoreEventsProvider } from '@providers/events'; import { CoreEventsProvider } from '@providers/events';
import { CoreSitesProvider, CoreSiteCheckResponse, CoreLoginSiteInfo } from '@providers/sites'; 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({ this.domUtils.showAlertWithOptions({
title: this.translate.instant('core.cannotconnect'), title: this.translate.instant('core.cannotconnect', {$a: CoreSite.MINIMUM_MOODLE_VERSION}),
message, message,
buttons, buttons,
}); });