MOBILE-2632 message: Get conversation messages

main
Dani Palou 2018-11-19 11:10:25 +01:00
parent 601508aefc
commit a6f5543870
8 changed files with 589 additions and 133 deletions

View File

@ -165,7 +165,9 @@
"addon.messages.errorwhileretrievingcontacts": "local_moodlemobileapp", "addon.messages.errorwhileretrievingcontacts": "local_moodlemobileapp",
"addon.messages.errorwhileretrievingdiscussions": "local_moodlemobileapp", "addon.messages.errorwhileretrievingdiscussions": "local_moodlemobileapp",
"addon.messages.errorwhileretrievingmessages": "local_moodlemobileapp", "addon.messages.errorwhileretrievingmessages": "local_moodlemobileapp",
"addon.messages.groupinfo": "message",
"addon.messages.groupmessages": "message", "addon.messages.groupmessages": "message",
"addon.messages.info": "message",
"addon.messages.message": "message", "addon.messages.message": "message",
"addon.messages.messagenotsent": "local_moodlemobileapp", "addon.messages.messagenotsent": "local_moodlemobileapp",
"addon.messages.messagepreferences": "message", "addon.messages.messagepreferences": "message",
@ -178,6 +180,7 @@
"addon.messages.nousersfound": "local_moodlemobileapp", "addon.messages.nousersfound": "local_moodlemobileapp",
"addon.messages.removecontact": "message", "addon.messages.removecontact": "message",
"addon.messages.removecontactconfirm": "local_moodlemobileapp", "addon.messages.removecontactconfirm": "local_moodlemobileapp",
"addon.messages.showdeletemessages": "local_moodlemobileapp",
"addon.messages.type_blocked": "local_moodlemobileapp", "addon.messages.type_blocked": "local_moodlemobileapp",
"addon.messages.type_offline": "local_moodlemobileapp", "addon.messages.type_offline": "local_moodlemobileapp",
"addon.messages.type_online": "local_moodlemobileapp", "addon.messages.type_online": "local_moodlemobileapp",

View File

@ -17,7 +17,9 @@
"errorwhileretrievingcontacts": "Error while retrieving contacts from the server.", "errorwhileretrievingcontacts": "Error while retrieving contacts from the server.",
"errorwhileretrievingdiscussions": "Error while retrieving discussions from the server.", "errorwhileretrievingdiscussions": "Error while retrieving discussions from the server.",
"errorwhileretrievingmessages": "Error while retrieving messages from the server.", "errorwhileretrievingmessages": "Error while retrieving messages from the server.",
"groupinfo": "Group info",
"groupmessages": "Group messages", "groupmessages": "Group messages",
"info": "Info",
"messagenotsent": "The message was not sent. Please try again later.", "messagenotsent": "The message was not sent. Please try again later.",
"message": "Message", "message": "Message",
"messagepreferences": "Message preferences", "messagepreferences": "Message preferences",
@ -30,6 +32,7 @@
"nousersfound": "No users found", "nousersfound": "No users found",
"removecontact": "Remove contact", "removecontact": "Remove contact",
"removecontactconfirm": "Contact will be removed from your contacts list.", "removecontactconfirm": "Contact will be removed from your contacts list.",
"showdeletemessages": "Show delete messages",
"type_blocked": "Blocked", "type_blocked": "Blocked",
"type_offline": "Offline", "type_offline": "Offline",
"type_online": "Online", "type_online": "Online",

View File

@ -1,15 +1,17 @@
<ion-header> <ion-header>
<ion-navbar core-back-button> <ion-navbar core-back-button>
<ion-title><core-format-text [text]="title"></core-format-text></ion-title> <ion-title>
<img *ngIf="conversationImage" class="core-bar-button-image" [src]="conversationImage">
<core-format-text [text]="title"></core-format-text>
</ion-title>
<ion-buttons end></ion-buttons> <ion-buttons end></ion-buttons>
</ion-navbar> </ion-navbar>
<core-navbar-buttons end> <core-navbar-buttons end>
<button ion-button icon-only clear="true" (click)="toggleDelete()" [hidden]="!canDelete"> <core-context-menu>
<ion-icon name="trash"></ion-icon> <core-context-menu-item [hidden]="!showInfo || isGroup" [priority]="1000" [content]="'addon.messages.info' | translate" (action)="viewInfo()" [iconAction]="'information-circle'"></core-context-menu-item>
</button> <core-context-menu-item [hidden]="!showInfo || !isGroup" [priority]="999" [content]="'addon.messages.groupinfo' | translate" (action)="viewInfo()" [iconAction]="'information-circle'"></core-context-menu-item>
<a [hidden]="!showProfileLink" core-user-link [userId]="userId" [attr.aria-label]=" 'core.user.viewprofile' | translate"> <core-context-menu-item [hidden]="!canDelete" [priority]="800" [content]="'addon.messages.showdeletemessages' | translate" (action)="toggleDelete()" [iconAction]="'trash'"></core-context-menu-item>
<img class="button core-bar-button-image" [src]="profileLink" core-external-content onError="this.src='assets/img/user-avatar.png'"> </core-context-menu>
</a>
</core-navbar-buttons> </core-navbar-buttons>
</ion-header> </ion-header>
<ion-content class="has-footer"> <ion-content class="has-footer">
@ -22,12 +24,12 @@
<ion-label>{{ message.timecreated | coreFormatDate: "LL" }}</ion-label> <ion-label>{{ message.timecreated | coreFormatDate: "LL" }}</ion-label>
</ion-chip> </ion-chip>
<ion-chip class="addon-messages-unreadfrom" *ngIf="message.unreadFrom" 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:{$a: title} }}</ion-label>
<ion-icon name="arrow-round-down"></ion-icon> <ion-icon name="arrow-round-down"></ion-icon>
</ion-chip> </ion-chip>
<ion-item text-wrap (longPress)="copyMessage(message.smallmessage)" class="addon-message" [class.addon-message-mine]="message.useridfrom == currentUserId" [@coreSlideInOut]="message.useridfrom == currentUserId ? '' : 'fromLeft'"> <ion-item text-wrap (longPress)="copyMessage(message)" class="addon-message" [class.addon-message-mine]="message.useridfrom == currentUserId" [@coreSlideInOut]="message.useridfrom == currentUserId ? '' : 'fromLeft'">
<!-- Some messages have <p> and some others don't. Add a <p> so they all have same styles. --> <!-- Some messages have <p> and some others don't. Add a <p> so they all have same styles. -->
<p class="addon-message-text"> <p class="addon-message-text">
<core-format-text (afterRender)="last && scrollToBottom()" [text]="message.text"></core-format-text> <core-format-text (afterRender)="last && scrollToBottom()" [text]="message.text"></core-format-text>

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { Component, OnDestroy, ViewChild } from '@angular/core'; import { Component, OnDestroy, ViewChild, Optional } from '@angular/core';
import { IonicPage, NavParams, NavController, Content } from 'ionic-angular'; import { IonicPage, NavParams, NavController, Content } from 'ionic-angular';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { CoreEventsProvider } from '@providers/events'; import { CoreEventsProvider } from '@providers/events';
@ -25,6 +25,7 @@ import { CoreUtilsProvider } from '@providers/utils/utils';
import { CoreLoggerProvider } from '@providers/logger'; import { CoreLoggerProvider } from '@providers/logger';
import { CoreAppProvider } from '@providers/app'; import { CoreAppProvider } from '@providers/app';
import { coreSlideInOut } from '@classes/animations'; import { coreSlideInOut } from '@classes/animations';
import { CoreSplitViewComponent } from '@components/split-view/split-view';
import { Md5 } from 'ts-md5/dist/md5'; import { Md5 } from 'ts-md5/dist/md5';
import * as moment from 'moment'; import * as moment from 'moment';
@ -53,12 +54,16 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
protected syncObserver: any; protected syncObserver: any;
protected oldContentHeight = 0; protected oldContentHeight = 0;
protected keyboardObserver: any; protected keyboardObserver: any;
protected scrollBottom = true;
protected viewDestroyed = false;
userId: number; conversationId: number; // Conversation ID. Undefined if it's a new individual conversation.
conversation: any; // The conversation object (if it exists).
userId: number; // User ID you're talking to (only if group messaging not enabled or it's a new individual conversation).
currentUserId: number; currentUserId: number;
title: string; title: string;
profileLink: string; showInfo: boolean;
showProfileLink: boolean; conversationImage: string;
loaded = false; loaded = false;
showKeyboard = false; showKeyboard = false;
canLoadMore = false; canLoadMore = false;
@ -66,26 +71,30 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
messages = []; messages = [];
showDelete = false; showDelete = false;
canDelete = false; canDelete = false;
scrollBottom = true; groupMessagingEnabled: boolean;
viewDestroyed = false; isGroup = false;
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,
private domUtils: CoreDomUtilsProvider, private messagesProvider: AddonMessagesProvider, logger: CoreLoggerProvider, private domUtils: CoreDomUtilsProvider, private messagesProvider: AddonMessagesProvider, logger: CoreLoggerProvider,
private utils: CoreUtilsProvider, private appProvider: CoreAppProvider, private translate: TranslateService) { private utils: CoreUtilsProvider, private appProvider: CoreAppProvider, private translate: TranslateService,
@Optional() private svComponent: CoreSplitViewComponent) {
this.siteId = sitesProvider.getCurrentSiteId(); this.siteId = sitesProvider.getCurrentSiteId();
this.currentUserId = sitesProvider.getCurrentSiteUserId(); this.currentUserId = sitesProvider.getCurrentSiteUserId();
this.groupMessagingEnabled = this.messagesProvider.isGroupMessagingEnabled();
this.logger = logger.getInstance('AddonMessagesDiscussionPage'); this.logger = logger.getInstance('AddonMessagesDiscussionPage');
this.conversationId = navParams.get('conversationId');
this.userId = navParams.get('userId'); this.userId = navParams.get('userId');
this.showKeyboard = navParams.get('showKeyboard'); this.showKeyboard = navParams.get('showKeyboard');
// Refresh data if this discussion is synchronized automatically. // Refresh data if this discussion is synchronized automatically.
this.syncObserver = eventsProvider.on(AddonMessagesSyncProvider.AUTO_SYNCED, (data) => { this.syncObserver = eventsProvider.on(AddonMessagesSyncProvider.AUTO_SYNCED, (data) => {
if (data.userId == this.userId) { if ((data.userId && data.userId == this.userId) ||
(data.conversationId && data.conversationId == this.conversationId)) {
// Fetch messages. // Fetch messages.
this.fetchData(); this.fetchMessages();
// Show first warning if any. // Show first warning if any.
if (data.warnings && data.warnings[0]) { if (data.warnings && data.warnings[0]) {
@ -102,8 +111,8 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
* @param {boolean} [keep=true] If set the keep flag or not. * @param {boolean} [keep=true] If set the keep flag or not.
*/ */
protected addMessage(message: any, keep: boolean = true): void { protected addMessage(message: any, keep: boolean = true): void {
// Use smallmessage instead of message ID because ID changes when a message is read. // Use text instead of message ID because ID changes when a message is read.
message.hash = Md5.hashAsciiStr(message.smallmessage) + '#' + message.timecreated + '#' + message.useridfrom; message.hash = Md5.hashAsciiStr(message.text || '') + '#' + message.timecreated + '#' + message.useridfrom;
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);
@ -143,15 +152,17 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
ionViewDidLoad(): void { ionViewDidLoad(): void {
// Disable the profile button if we're already coming from a profile. // Disable the profile button if we're already coming from a profile.
const backViewPage = this.navCtrl.getPrevious() && this.navCtrl.getPrevious().component.name; const backViewPage = this.navCtrl.getPrevious() && this.navCtrl.getPrevious().component.name;
this.showProfileLink = !backViewPage || backViewPage !== 'CoreUserProfilePage'; this.showInfo = !backViewPage || backViewPage !== 'CoreUserProfilePage';
if (!this.groupMessagingEnabled && this.userId) {
// Get the user profile to retrieve the user fullname and image. // Get the user profile to retrieve the user fullname and image.
this.userProvider.getProfile(this.userId, undefined, true).then((user) => { this.userProvider.getProfile(this.userId, undefined, true).then((user) => {
if (!this.title) { if (!this.title) {
this.title = user.fullname; this.title = user.fullname;
} }
this.profileLink = user.profileimageurl; this.conversationImage = user.profileimageurl;
}); });
}
// Synchronize messages if needed. // Synchronize messages if needed.
this.messagesSync.syncDiscussion(this.userId).catch(() => { this.messagesSync.syncDiscussion(this.userId).catch(() => {
@ -161,8 +172,17 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
this.domUtils.showErrorModal(warnings[0]); this.domUtils.showErrorModal(warnings[0]);
} }
if (this.groupMessagingEnabled) {
// Get the conversation ID if it exists and we don't have it yet.
return this.getConversation().then((exists) => {
if (exists) {
// Fetch the messages for the first time. // Fetch the messages for the first time.
return this.fetchData().then(() => { return this.fetchMessages();
}
});
} else {
// Fetch the messages for the first time.
return this.fetchMessages().then(() => {
if (!this.title && this.messages.length) { if (!this.title && this.messages.length) {
// Didn't receive the fullname via argument. Try to get it from messages. // Didn't receive the fullname via argument. Try to get it from messages.
// It's possible that name cannot be resolved when no messages were yet exchanged. // It's possible that name cannot be resolved when no messages were yet exchanged.
@ -172,6 +192,8 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
this.title = this.messages[0].userfromfullname || ''; this.title = this.messages[0].userfromfullname || '';
} }
} }
});
}
}).catch((error) => { }).catch((error) => {
this.domUtils.showErrorModalDefault(error, 'addon.messages.errorwhileretrievingmessages', true); this.domUtils.showErrorModalDefault(error, 'addon.messages.errorwhileretrievingmessages', true);
}).finally(() => { }).finally(() => {
@ -179,7 +201,6 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
this.resizeContent(); this.resizeContent();
this.loaded = true; this.loaded = true;
}); });
});
// Recalculate footer position when keyboard is shown or hidden. // Recalculate footer position when keyboard is shown or hidden.
this.keyboardObserver = this.eventsProvider.on(CoreEventsProvider.KEYBOARD_CHANGE, (kbHeight) => { this.keyboardObserver = this.eventsProvider.on(CoreEventsProvider.KEYBOARD_CHANGE, (kbHeight) => {
@ -204,12 +225,12 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
/** /**
* Convenience function to fetch messages. * Convenience function to fetch messages.
*
* @return {Promise<any>} Resolved when done. * @return {Promise<any>} Resolved when done.
*/ */
protected fetchData(): Promise<any> { protected fetchMessages(): Promise<any> {
this.loadMoreError = false; this.loadMoreError = false;
this.logger.debug(`Polling new messages for discussion with user '${this.userId}'`);
if (this.messagesBeingSent > 0) { if (this.messagesBeingSent > 0) {
// We do not poll while a message is being sent or we could confuse the user. // We do not poll while a message is being sent or we could confuse the user.
// Otherwise, his message would disappear from the list, and he'd have to wait for the interval to check for messages. // Otherwise, his message would disappear from the list, and he'd have to wait for the interval to check for messages.
@ -217,6 +238,15 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
} else if (this.fetching) { } else if (this.fetching) {
// Already fetching. // Already fetching.
return Promise.reject(null); return Promise.reject(null);
} else if (this.groupMessagingEnabled && !this.conversationId) {
// Don't have enough data to fetch messages.
return Promise.reject(null);
}
if (this.conversationId) {
this.logger.debug(`Polling new messages for conversation '${this.conversationId}'`);
} else {
this.logger.debug(`Polling new messages for discussion with user '${this.userId}'`);
} }
this.fetching = true; this.fetching = true;
@ -224,11 +254,19 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
// Wait for synchronization process to finish. // Wait for synchronization process to finish.
return this.messagesSync.waitForSync(this.userId).then(() => { return this.messagesSync.waitForSync(this.userId).then(() => {
// Fetch messages. Invalidate the cache before fetching. // Fetch messages. Invalidate the cache before fetching.
if (this.groupMessagingEnabled) {
return this.messagesProvider.invalidateConversationMessages(this.conversationId).catch(() => {
// Ignore errors.
}).then(() => {
return this.getConversationMessages(this.pagesLoaded);
});
} else {
return this.messagesProvider.invalidateDiscussionCache(this.userId).catch(() => { return this.messagesProvider.invalidateDiscussionCache(this.userId).catch(() => {
// Ignore errors. // Ignore errors.
});
}).then(() => { }).then(() => {
return this.getDiscussion(this.pagesLoaded); return this.getDiscussionMessages(this.pagesLoaded);
});
}
}).then((messages) => { }).then((messages) => {
if (this.viewDestroyed) { if (this.viewDestroyed) {
return Promise.resolve(); return Promise.resolve();
@ -261,11 +299,83 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
// Mark retrieved messages as read if they are not. // Mark retrieved messages as read if they are not.
this.markMessagesAsRead(); this.markMessagesAsRead();
}).finally(() => { }).finally(() => {
this.fetching = false; this.fetching = false;
}); });
} }
/**
* Get the conversation.
*
* @return {Promise<boolean>} Promise resolved with a boolean: whether the conversation exists or not.
*/
protected getConversation(): Promise<boolean> {
let promise;
if (this.conversationId) {
// Retrieve the conversation. Invalidate data first to get the right unreadcount.
promise = this.messagesProvider.invalidateConversation(this.conversationId).then(() => {
return this.messagesProvider.getConversation(this.conversationId);
});
} else {
// We don't have the conversation ID, check if it exists.
promise = this.messagesProvider.getConversationBetweenUsers(this.userId).catch((error) => {
if (error.errorcode == 'conversationdoesntexist') {
// Conversation does not exist, return undefined.
return;
}
return Promise.reject(error);
});
}
return promise.then((conversation) => {
this.conversation = conversation;
if (conversation) {
this.title = conversation.name;
this.conversationImage = conversation.imageurl;
this.isGroup = conversation.type == AddonMessagesProvider.MESSAGE_CONVERSATION_TYPE_GROUP;
if (!this.isGroup) {
this.userId = conversation.userid;
}
return true;
} else {
return false;
}
});
}
/**
* Get the messages of the conversation. Used if group messaging is supported.
*
* @param {number} pagesToLoad Number of "pages" to load.
* @param {number} [offset=0] Offset for message list.
* @return {Promise<any[]>} Promise resolved with the list of messages.
*/
protected getConversationMessages(pagesToLoad: number, offset: number = 0): Promise<any[]> {
const excludePending = offset > 0;
return this.messagesProvider.getConversationMessages(this.conversationId, excludePending, offset).then((result) => {
pagesToLoad--;
if (pagesToLoad > 0 && result.canLoadMore) {
offset += AddonMessagesProvider.LIMIT_MESSAGES;
// Get more messages.
return this.getConversationMessages(pagesToLoad, offset).then((nextMessages) => {
return result.messages.concat(nextMessages);
});
} else {
// No more messages to load, return them.
this.canLoadMore = result.canLoadMore;
return result.messages;
}
});
}
/** /**
* Get a discussion. Can load several "pages". * Get a discussion. Can load several "pages".
* *
@ -276,8 +386,8 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
* @param {number} [lfSentRead=0] Number of read sent messages already fetched, so fetch will be done from this. * @param {number} [lfSentRead=0] Number of read sent messages already fetched, so fetch will be done from this.
* @return {Promise<any>} Resolved when done. * @return {Promise<any>} Resolved when done.
*/ */
protected getDiscussion(pagesToLoad: number, lfReceivedUnread: number = 0, lfReceivedRead: number = 0, lfSentUnread: number = 0, protected getDiscussionMessages(pagesToLoad: number, lfReceivedUnread: number = 0, lfReceivedRead: number = 0,
lfSentRead: number = 0): Promise<any> { lfSentUnread: number = 0, lfSentRead: number = 0): Promise<any> {
// Only get offline messages if we're loading the first "page". // Only get offline messages if we're loading the first "page".
const excludePending = lfReceivedUnread > 0 || lfReceivedRead > 0 || lfSentUnread > 0 || lfSentRead > 0; const excludePending = lfReceivedUnread > 0 || lfReceivedRead > 0 || lfSentUnread > 0 || lfSentRead > 0;
@ -308,7 +418,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
}); });
// Get next messages. // Get next messages.
return this.getDiscussion(pagesToLoad, lfReceivedUnread, lfReceivedRead, lfSentUnread, lfSentRead) return this.getDiscussionMessages(pagesToLoad, lfReceivedUnread, lfReceivedRead, lfSentUnread, lfSentRead)
.then((nextMessages) => { .then((nextMessages) => {
return result.messages.concat(nextMessages); return result.messages.concat(nextMessages);
}); });
@ -330,7 +440,11 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
if (this.messagesProvider.isMarkAllMessagesReadEnabled()) { if (this.messagesProvider.isMarkAllMessagesReadEnabled()) {
let messageUnreadFound = false; let messageUnreadFound = false;
// Mark all messages at a time if one messages is unread.
// Mark all messages at a time if there is any unread message.
if (this.groupMessagingEnabled) {
messageUnreadFound = this.conversation && this.conversation.unreadcount > 0 && this.conversationId > 0;
} else {
for (const x in this.messages) { for (const x in this.messages) {
const message = this.messages[x]; const message = this.messages[x];
// If an unread message is found, mark all messages as read. // If an unread message is found, mark all messages as read.
@ -339,14 +453,26 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
break; break;
} }
} }
}
if (messageUnreadFound) { if (messageUnreadFound) {
this.setUnreadLabelPosition(); this.setUnreadLabelPosition();
promises.push(this.messagesProvider.markAllMessagesRead(this.userId).then(() => {
readChanged = true; let promise;
if (this.groupMessagingEnabled) {
promise = this.messagesProvider.markAllConversationMessagesRead(this.conversationId);
} else {
promise = this.messagesProvider.markAllMessagesRead(this.userId).then(() => {
// Mark all messages as read. // Mark all messages as read.
this.messages.forEach((message) => { this.messages.forEach((message) => {
message.read = 1; message.read = 1;
}); });
});
}
promises.push(promise.then(() => {
readChanged = true;
})); }));
} }
} else { } else {
@ -366,6 +492,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
Promise.all(promises).finally(() => { Promise.all(promises).finally(() => {
if (readChanged) { if (readChanged) {
this.eventsProvider.trigger(AddonMessagesProvider.READ_CHANGED_EVENT, { this.eventsProvider.trigger(AddonMessagesProvider.READ_CHANGED_EVENT, {
conversationId: this.conversationId,
userId: this.userId userId: this.userId
}, this.siteId); }, this.siteId);
} }
@ -390,6 +517,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
if (trigger) { if (trigger) {
// Update discussions last message. // Update discussions last message.
this.eventsProvider.trigger(AddonMessagesProvider.NEW_MESSAGE_EVENT, { this.eventsProvider.trigger(AddonMessagesProvider.NEW_MESSAGE_EVENT, {
conversationId: this.conversationId,
userId: this.userId, userId: this.userId,
message: this.lastMessage.text, message: this.lastMessage.text,
timecreated: this.lastMessage.timecreated timecreated: this.lastMessage.timecreated
@ -411,15 +539,32 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
return; return;
} }
if (this.groupMessagingEnabled) {
// Use the unreadcount from the conversation to calculate where should the label be placed.
if (this.conversation && this.conversation.unreadcount > 0 && this.messages) {
// Iterate over messages to find the right message using the unreadcount. Skip offline messages and own messages.
let found = 0;
for (let i = this.messages.length - 1; i >= 0; i--) {
const message = this.messages[i];
if (!message.pending && message.useridfrom != this.currentUserId) {
found++;
if (found == this.conversation.unreadcount) {
this.unreadMessageFrom = parseInt(message.id, 10);
break;
}
}
}
}
} else {
let previousMessageRead = false; let previousMessageRead = false;
for (const x in this.messages) { for (const x in this.messages) {
const message = this.messages[x]; const message = this.messages[x];
if (message.useridfrom != this.currentUserId) { if (message.useridfrom != this.currentUserId) {
// Place unread from message label only once. const unreadFrom = message.read == 0 && previousMessageRead;
message.unreadFrom = message.read == 0 && previousMessageRead;
if (message.unreadFrom) { if (unreadFrom) {
// Save where the label is placed. // Save where the label is placed.
this.unreadMessageFrom = parseInt(message.id, 10); this.unreadMessageFrom = parseInt(message.id, 10);
break; break;
@ -428,6 +573,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
previousMessageRead = message.read != 0; previousMessageRead = message.read != 0;
} }
} }
}
// Do not update the message unread from label on next refresh. // Do not update the message unread from label on next refresh.
if (this.unreadMessageFrom == 0) { if (this.unreadMessageFrom == 0) {
@ -450,15 +596,6 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
*/ */
protected hideUnreadLabel(): void { protected hideUnreadLabel(): void {
if (this.unreadMessageFrom > 0) { if (this.unreadMessageFrom > 0) {
for (const x in this.messages) {
const message = this.messages[x];
if (message.id == this.unreadMessageFrom) {
message.unreadFrom = false;
break;
}
}
// Label hidden.
this.unreadMessageFrom = -1; this.unreadMessageFrom = -1;
} }
} }
@ -487,10 +624,15 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
* Set a polling to get new messages every certain time. * Set a polling to get new messages every certain time.
*/ */
protected setPolling(): void { protected setPolling(): void {
if (this.groupMessagingEnabled && !this.conversationId) {
// Don't have enough data to poll messages.
return;
}
if (!this.polling) { if (!this.polling) {
// Start polling. // Start polling.
this.polling = setInterval(() => { this.polling = setInterval(() => {
this.fetchData().catch(() => { this.fetchMessages().catch(() => {
// Ignore errors. // Ignore errors.
}); });
}, AddonMessagesProvider.POLL_INTERVAL); }, AddonMessagesProvider.POLL_INTERVAL);
@ -509,12 +651,12 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
} }
/** /**
* Copy message to clipboard * Copy message to clipboard.
* *
* @param {string} text Message text to be copied. * @param {any} message Message to be copied.
*/ */
copyMessage(text: string): void { copyMessage(message: any): void {
this.utils.copyToClipboard(text); this.utils.copyToClipboard(message.smallmessage || message.text || '');
} }
/** /**
@ -534,7 +676,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
this.removeMessage(message.hash); this.removeMessage(message.hash);
this.notifyNewMessage(); this.notifyNewMessage();
this.fetchData(); // Re-fetch messages to update cached data. this.fetchMessages(); // Re-fetch messages to update cached data.
}).finally(() => { }).finally(() => {
modal.dismiss(); modal.dismiss();
}); });
@ -554,9 +696,8 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
return this.waitForFetch().finally(() => { return this.waitForFetch().finally(() => {
this.pagesLoaded++; this.pagesLoaded++;
this.fetchData().catch((error) => { this.fetchMessages().catch((error) => {
this.loadMoreError = true; // Set to prevent infinite calls with infinite-loading. this.loadMoreError = true; // Set to prevent infinite calls with infinite-loading.
this.pagesLoaded--; this.pagesLoaded--;
this.domUtils.showErrorModalDefault(error, 'addon.messages.errorwhileretrievingmessages', true); this.domUtils.showErrorModalDefault(error, 'addon.messages.errorwhileretrievingmessages', true);
}).finally(() => { }).finally(() => {
@ -638,7 +779,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
if (data.sent) { if (data.sent) {
// Message was sent, fetch messages right now. // Message was sent, fetch messages right now.
promise = this.fetchData(); promise = this.fetchMessages();
} else { } else {
promise = Promise.reject(null); promise = Promise.reject(null);
} }
@ -697,6 +838,19 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
this.showDelete = !this.showDelete; this.showDelete = !this.showDelete;
} }
/**
* View info. If it's an individual conversation, go to the user profile.
* If it's a group conversation, view info about the group.
*/
viewInfo(): void {
if (this.isGroup) {
// @todo
} else {
const navCtrl = this.svComponent ? this.svComponent.getMasterNav() : this.navCtrl;
navCtrl.push('CoreUserProfilePage', { userId: this.userId });
}
}
/** /**
* Page destroyed. * Page destroyed.
*/ */

View File

@ -8,6 +8,8 @@
<button ion-button icon-only (click)="gotoSettings($event)" [attr.aria-label]="'addon.messages.messagepreferences' | translate"> <button ion-button icon-only (click)="gotoSettings($event)" [attr.aria-label]="'addon.messages.messagepreferences' | translate">
<ion-icon name="cog"></ion-icon> <ion-icon name="cog"></ion-icon>
</button> </button>
<!-- Add an empty context menu so discussion page can add items in split view, otherwise the menu disappears in some cases. -->
<core-context-menu></core-context-menu>
</ion-buttons> </ion-buttons>
</ion-navbar> </ion-navbar>
</ion-header> </ion-header>

View File

@ -94,7 +94,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
} else { } else {
// An existing conversation has a new message, update the last message. // An existing conversation has a new message, update the last message.
conversation.lastmessage = data.message; conversation.lastmessage = data.message;
conversation.lastmessagedate = data.timecreated; conversation.lastmessagedate = data.timecreated / 1000;
} }
} }
}, this.siteId); }, this.siteId);
@ -145,7 +145,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
ngOnInit(): void { ngOnInit(): void {
if (this.conversationId) { if (this.conversationId) {
// There is a discussion to load, open the discussion in a new state. // There is a discussion to load, open the discussion in a new state.
this.gotoConversation(this.conversationId, null); this.gotoConversation(this.conversationId);
} }
this.fetchData().then(() => { this.fetchData().then(() => {
@ -162,7 +162,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
} }
if (conversation) { if (conversation) {
this.gotoConversation(conversation.id, conversation.userid); this.gotoConversation(conversation.id);
} }
} }
}); });
@ -247,13 +247,14 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
* Navigate to a particular conversation. * Navigate to a particular conversation.
* *
* @param {number} conversationId Conversation Id to load. * @param {number} conversationId Conversation Id to load.
* @param {number} userId User of the conversation. @todo This will probably be removed when group messaging is fully supported. * @param {number} userId User of the conversation. Only if there is no conversationId.
* @param {number} [messageId] Message to scroll after loading the discussion. Used when searching. * @param {number} [messageId] Message to scroll after loading the discussion. Used when searching.
*/ */
gotoConversation(conversationId: number, userId: number, messageId?: number): void { gotoConversation(conversationId: number, userId?: number, messageId?: number): void {
this.selectedConversation = conversationId; this.selectedConversation = conversationId;
const params = { const params = {
conversationId: conversationId,
userId: userId userId: userId
}; };
if (messageId) { if (messageId) {
@ -358,7 +359,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
* Page destroyed. * Page destroyed.
*/ */
ngOnDestroy(): void { ngOnDestroy(): void {
this.newMessagesObserver && this.newMessagesObserver.unsubscribe(); this.newMessagesObserver && this.newMessagesObserver.off();
this.appResumeSubscription && this.appResumeSubscription.unsubscribe(); this.appResumeSubscription && this.appResumeSubscription.unsubscribe();
this.pushObserver && this.pushObserver.unsubscribe(); this.pushObserver && this.pushObserver.unsubscribe();
this.readChangedObserver && this.readChangedObserver.off(); this.readChangedObserver && this.readChangedObserver.off();

View File

@ -139,6 +139,41 @@ export class AddonMessagesProvider {
}); });
} }
/**
* Format a conversation.
*
* @param {any} conversation Conversation to format.
* @param {number} userId User ID viewing the conversation.
* @return {any} Formatted conversation.
*/
protected formatConversation(conversation: any, userId: number): any {
const numMessages = conversation.messages.length,
lastMessage = numMessages ? conversation.messages[numMessages - 1] : null;
conversation.lastmessage = lastMessage ? lastMessage.text : null;
conversation.lastmessagedate = lastMessage ? lastMessage.timecreated : null;
conversation.sentfromcurrentuser = lastMessage ? lastMessage.useridfrom == userId : null;
if (conversation.type == AddonMessagesProvider.MESSAGE_CONVERSATION_TYPE_INDIVIDUAL) {
const otherUser = conversation.members.reduce((carry, member) => {
if (!carry && member.id != userId) {
carry = member;
}
return carry;
}, null);
conversation.name = conversation.name ? conversation.name : otherUser.fullname;
conversation.imageurl = conversation.imageurl ? conversation.imageurl : otherUser.profileimageurl;
conversation.userid = otherUser.id;
conversation.showonlinestatus = otherUser.showonlinestatus;
conversation.isonline = otherUser.isonline;
conversation.isblocked = otherUser.isblocked;
}
return conversation;
}
/** /**
* Get the cache key for blocked contacts. * Get the cache key for blocked contacts.
* *
@ -187,6 +222,39 @@ export class AddonMessagesProvider {
return this.ROOT_CACHE_KEY + 'discussions'; return this.ROOT_CACHE_KEY + 'discussions';
} }
/**
* Get cache key for get conversations.
*
* @param {number} userId User ID.
* @param {number} conversationId Conversation ID.
* @return {string} Cache key.
*/
protected getCacheKeyForConversation(userId: number, conversationId: number): string {
return this.ROOT_CACHE_KEY + 'conversation:' + userId + ':' + conversationId;
}
/**
* Get cache key for get conversations between users.
*
* @param {number} userId User ID.
* @param {number} otherUserId Other user ID.
* @return {string} Cache key.
*/
protected getCacheKeyForConversationBetweenUsers(userId: number, otherUserId: number): string {
return this.ROOT_CACHE_KEY + 'conversationBetweenUsers:' + userId + ':' + otherUserId;
}
/**
* Get cache key for get conversation messages.
*
* @param {number} userId User ID.
* @param {number} conversationId Conversation ID.
* @return {string} Cache key.
*/
protected getCacheKeyForConversationMessages(userId: number, conversationId: number): string {
return this.ROOT_CACHE_KEY + 'conversationMessages:' + userId + ':' + conversationId;
}
/** /**
* Get cache key for get conversations. * Get cache key for get conversations.
* *
@ -297,6 +365,182 @@ export class AddonMessagesProvider {
}); });
} }
/**
* Get a conversation by the conversation ID.
*
* @param {number} conversationId Conversation ID to fetch.
* @param {boolean} [includeContactRequests] Include contact requests.
* @param {boolean} [includePrivacyInfo] Include privacy info.
* @param {number} [messageOffset=0] Offset for messages list.
* @param {number} [messageLimit=1] Limit of messages. Defaults to 1 (last message).
* We recommend getConversationMessages to get them.
* @param {number} [memberOffset=0] Offset for members list.
* @param {number} [memberLimit=2] Limit of members. Defaults to 2 (to be able to know the other user in individual ones).
* We recommend getConversationMembers to get them.
* @param {boolean} [newestFirst=true] Whether to order messages by newest first.
* @param {string} [siteId] Site ID. If not defined, use current site.
* @param {number} [userId] User ID. If not defined, current user in the site.
* @return {Promise<any>} Promise resolved with the response.
* @since 3.6
*/
getConversation(conversationId: number, includeContactRequests?: boolean, includePrivacyInfo?: boolean,
messageOffset: number = 0, messageLimit: number = 1, memberOffset: number = 0, memberLimit: number = 2,
newestFirst: boolean = true, siteId?: string, userId?: number): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => {
userId = userId || site.getUserId();
const preSets = {
cacheKey: this.getCacheKeyForConversation(userId, conversationId)
},
params: any = {
userid: userId,
conversationid: conversationId,
includecontactrequests: includeContactRequests ? 1 : 0,
includeprivacyinfo: includePrivacyInfo ? 1 : 0,
messageoffset: messageOffset,
messagelimit: messageLimit,
memberoffset: memberOffset,
memberlimit: memberLimit,
newestmessagesfirst: newestFirst ? 1 : 0
};
return site.read('core_message_get_conversation', params, preSets).then((conversation) => {
return this.formatConversation(conversation, userId);
});
});
}
/**
* Get a conversation between two users.
*
* @param {number} otherUserId The other user ID.
* @param {boolean} [includeContactRequests] Include contact requests.
* @param {boolean} [includePrivacyInfo] Include privacy info.
* @param {number} [messageOffset=0] Offset for messages list.
* @param {number} [messageLimit=1] Limit of messages. Defaults to 1 (last message).
* We recommend getConversationMessages to get them.
* @param {number} [memberOffset=0] Offset for members list.
* @param {number} [memberLimit=2] Limit of members. Defaults to 2 (to be able to know the other user in individual ones).
* We recommend getConversationMembers to get them.
* @param {boolean} [newestFirst=true] Whether to order messages by newest first.
* @param {string} [siteId] Site ID. If not defined, use current site.
* @param {number} [userId] User ID. If not defined, current user in the site.
* @return {Promise<any>} Promise resolved with the response.
* @since 3.6
*/
getConversationBetweenUsers(otherUserId: number, includeContactRequests?: boolean, includePrivacyInfo?: boolean,
messageOffset: number = 0, messageLimit: number = 1, memberOffset: number = 0, memberLimit: number = 2,
newestFirst: boolean = true, siteId?: string, userId?: number): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => {
userId = userId || site.getUserId();
const preSets = {
cacheKey: this.getCacheKeyForConversationBetweenUsers(userId, otherUserId)
},
params: any = {
userid: userId,
otheruserid: otherUserId,
includecontactrequests: includeContactRequests ? 1 : 0,
includeprivacyinfo: includePrivacyInfo ? 1 : 0,
messageoffset: messageOffset,
messagelimit: messageLimit,
memberoffset: memberOffset,
memberlimit: memberLimit,
newestmessagesfirst: newestFirst ? 1 : 0
};
return site.read('core_message_get_conversation_between_users', params, preSets).then((conversation) => {
return this.formatConversation(conversation, userId);
});
});
}
/**
* Get a conversation by the conversation ID.
*
* @param {number} conversationId Conversation ID to fetch.
* @param {boolean} excludePending True to exclude messages pending to be sent.
* @param {number} [limitFrom=0] Offset for messages list.
* @param {number} [limitTo] Limit of messages.
* @param {boolean} [newestFirst=true] Whether to order messages by newest first.
* @param {number} [timeFrom] The timestamp from which the messages were created.
* @param {string} [siteId] Site ID. If not defined, use current site.
* @param {number} [userId] User ID. If not defined, current user in the site.
* @return {Promise<any>} Promise resolved with the response.
* @since 3.6
*/
getConversationMessages(conversationId: number, excludePending: boolean, limitFrom: number = 0, limitTo?: number,
newestFirst: boolean = true, timeFrom: number = 0, siteId?: string, userId?: number): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => {
userId = userId || site.getUserId();
if (typeof limitTo == 'undefined' || limitTo === null) {
limitTo = this.LIMIT_MESSAGES;
}
const preSets = {
cacheKey: this.getCacheKeyForConversationMessages(userId, conversationId)
},
params: any = {
currentuserid: userId,
convid: conversationId,
limitfrom: limitFrom,
limitnum: limitTo < 1 ? limitTo : limitTo + 1, // If there is a limit, get 1 more than requested.
newest: newestFirst ? 1 : 0,
timefrom: timeFrom
};
if (limitFrom > 0) {
// Do not use cache when retrieving older messages.
// This is to prevent storing too much data and to prevent inconsistencies between "pages" loaded.
preSets['getFromCache'] = false;
preSets['saveToCache'] = false;
preSets['emergencyCache'] = false;
}
return site.read('core_message_get_conversation_messages', params, preSets).then((result) => {
if (limitTo < 1) {
result.canLoadMore = false;
result.messages = result.messages;
} else {
result.canLoadMore = result.messages.length > limitTo;
result.messages = result.messages.slice(0, limitTo);
}
result.messages.forEach((message) => {
// Convert time to milliseconds.
message.timecreated = message.timecreated ? message.timecreated * 1000 : 0;
});
if (this.appProvider.isDesktop() && params.useridto == userId && limitFrom === 0) {
// Store the last received message (we cannot know if it's unread or not). Don't block the user for this.
this.storeLastReceivedMessageIfNeeded(conversationId, result.messages[0], site.getId());
}
if (excludePending) {
// No need to get offline messages, return the ones we have.
return result;
}
// Get offline messages.
return this.messagesOffline.getMessages(userId).then((offlineMessages) => {
// Mark offline messages as pending.
offlineMessages.forEach((message) => {
message.pending = true;
message.text = message.smallmessage;
});
result.messages = result.messages.concat(offlineMessages);
return result;
});
});
});
}
/** /**
* Get the discussions of a certain user. This function is used in Moodle sites higher than 3.6. * Get the discussions of a certain user. This function is used in Moodle sites higher than 3.6.
* If the site is older than 3.6, please use getDiscussions. * If the site is older than 3.6, please use getDiscussions.
@ -308,6 +552,7 @@ export class AddonMessagesProvider {
* @param {string} [siteId] Site ID. If not defined, use current site. * @param {string} [siteId] Site ID. If not defined, use current site.
* @param {number} [userId] User ID. If not defined, current user in the site. * @param {number} [userId] User ID. If not defined, current user in the site.
* @return {Promise<any>} Promise resolved with the conversations. * @return {Promise<any>} Promise resolved with the conversations.
* @since 3.6
*/ */
getConversations(type?: number, favourites?: boolean, limitFrom: number = 0, siteId?: string, userId?: number) getConversations(type?: number, favourites?: boolean, limitFrom: number = 0, siteId?: string, userId?: number)
: Promise<{conversations: any[], canLoadMore: boolean}> { : Promise<{conversations: any[], canLoadMore: boolean}> {
@ -333,32 +578,8 @@ export class AddonMessagesProvider {
return site.read('core_message_get_conversations', params, preSets).then((response) => { return site.read('core_message_get_conversations', params, preSets).then((response) => {
// Format the conversations, adding some calculated fields. // Format the conversations, adding some calculated fields.
const conversations = response.conversations.map((conversation) => { const conversations = response.conversations.slice(0, this.LIMIT_MESSAGES).map((conversation) => {
const numMessages = conversation.messages.length, return this.formatConversation(conversation, userId);
lastMessage = numMessages ? conversation.messages[numMessages - 1] : null;
conversation.lastmessage = lastMessage ? lastMessage.text : null;
conversation.lastmessagedate = lastMessage ? lastMessage.timecreated : null;
conversation.sentfromcurrentuser = lastMessage ? lastMessage.useridfrom == userId : null;
if (conversation.type == AddonMessagesProvider.MESSAGE_CONVERSATION_TYPE_INDIVIDUAL) {
const otherUser = conversation.members.reduce((carry, member) => {
if (!carry && member.id != userId) {
carry = member;
}
return carry;
}, null);
conversation.name = conversation.name ? conversation.name : otherUser.fullname;
conversation.imageurl = conversation.imageurl ? conversation.imageurl : otherUser.profileimageurl;
conversation.userid = otherUser.id;
conversation.showonlinestatus = otherUser.showonlinestatus;
conversation.isonline = otherUser.isonline;
conversation.isblocked = otherUser.isblocked;
}
return conversation;
}); });
return { return {
@ -813,7 +1034,55 @@ export class AddonMessagesProvider {
} }
/** /**
* Invalidate contacts cache. * Invalidate conversation.
*
* @param {number} conversationId Conversation ID.
* @param {string} [siteId] Site ID. If not defined, current site.
* @param {number} [userId] User ID. If not defined, current user in the site.
* @return {Promise<any>} Resolved when done.
*/
invalidateConversation(conversationId: number, siteId?: string, userId?: number): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => {
userId = userId || site.getUserId();
return site.invalidateWsCacheForKey(this.getCacheKeyForConversation(conversationId, userId));
});
}
/**
* Invalidate conversation between users.
*
* @param {number} otherUserId Other user ID.
* @param {string} [siteId] Site ID. If not defined, current site.
* @param {number} [userId] User ID. If not defined, current user in the site.
* @return {Promise<any>} Resolved when done.
*/
invalidateConversationBetweenUsers(otherUserId: number, siteId?: string, userId?: number): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => {
userId = userId || site.getUserId();
return site.invalidateWsCacheForKey(this.getCacheKeyForConversationBetweenUsers(userId, otherUserId));
});
}
/**
* Invalidate conversation messages cache.
*
* @param {number} conversationId Conversation ID.
* @param {string} [siteId] Site ID. If not defined, current site.
* @param {number} [userId] User ID. If not defined, current user in the site.
* @return {Promise<any>} Resolved when done.
*/
invalidateConversationMessages(conversationId: number, siteId?: string, userId?: number): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => {
userId = userId || site.getUserId();
return site.invalidateWsCacheForKey(this.getCacheKeyForConversationMessages(userId, conversationId));
});
}
/**
* Invalidate conversations cache.
* *
* @param {string} [siteId] Site ID. If not defined, current site. * @param {string} [siteId] Site ID. If not defined, current site.
* @param {number} [userId] User ID. If not defined, current user in the site. * @param {number} [userId] User ID. If not defined, current user in the site.
@ -1003,6 +1272,25 @@ export class AddonMessagesProvider {
return this.sitesProvider.getCurrentSite().write('core_message_mark_message_read', params); return this.sitesProvider.getCurrentSite().write('core_message_mark_message_read', params);
} }
/**
* Mark all messages of a conversation as read.
*
* @param {number} conversationId Conversation ID.
* @returns {Promise<any>} Promise resolved if success.
* @since 3.6
*/
markAllConversationMessagesRead(conversationId?: number): Promise<any> {
const params = {
userid: this.sitesProvider.getCurrentSiteUserId(),
conversationid: conversationId
},
preSets = {
responseExpected: false
};
return this.sitesProvider.getCurrentSite().write('core_message_mark_all_conversation_messages_as_read', params, preSets);
}
/** /**
* Mark all messages of a discussion as read. * Mark all messages of a discussion as read.
* *
@ -1238,17 +1526,17 @@ export class AddonMessagesProvider {
/** /**
* Store the last received message if it's newer than the last stored. * Store the last received message if it's newer than the last stored.
* *
* @param {number} userIdFrom ID of the useridfrom retrieved, 0 for all users. * @param {number} convIdOrUserIdFrom Conversation ID (3.6+) or ID of the useridfrom retrieved (3.5-), 0 for all users.
* @param {any} message Last message received. * @param {any} message Last message received.
* @param {string} [siteId] Site ID. If not defined, current site. * @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when done. * @return {Promise<any>} Promise resolved when done.
*/ */
protected storeLastReceivedMessageIfNeeded(userIdFrom: number, message: any, siteId?: string): Promise<any> { protected storeLastReceivedMessageIfNeeded(convIdOrUserIdFrom: number, message: any, siteId?: string): Promise<any> {
const component = AddonMessagesProvider.PUSH_SIMULATION_COMPONENT; const component = AddonMessagesProvider.PUSH_SIMULATION_COMPONENT;
// Get the last received message. // Get the last received message.
return this.emulatorHelper.getLastReceivedNotification(component, siteId).then((lastMessage) => { return this.emulatorHelper.getLastReceivedNotification(component, siteId).then((lastMessage) => {
if (userIdFrom > 0 && (!message || !lastMessage)) { if (convIdOrUserIdFrom > 0 && (!message || !lastMessage)) {
// Seeing a single discussion. No received message or cannot know if it really is the last received message. Stop. // Seeing a single discussion. No received message or cannot know if it really is the last received message. Stop.
return; return;
} }

View File

@ -165,7 +165,9 @@
"addon.messages.errorwhileretrievingcontacts": "Error while retrieving contacts from the server.", "addon.messages.errorwhileretrievingcontacts": "Error while retrieving contacts from the server.",
"addon.messages.errorwhileretrievingdiscussions": "Error while retrieving discussions from the server.", "addon.messages.errorwhileretrievingdiscussions": "Error while retrieving discussions from the server.",
"addon.messages.errorwhileretrievingmessages": "Error while retrieving messages from the server.", "addon.messages.errorwhileretrievingmessages": "Error while retrieving messages from the server.",
"addon.messages.groupinfo": "Group info",
"addon.messages.groupmessages": "Group messages", "addon.messages.groupmessages": "Group messages",
"addon.messages.info": "Info",
"addon.messages.message": "Message", "addon.messages.message": "Message",
"addon.messages.messagenotsent": "The message was not sent. Please try again later.", "addon.messages.messagenotsent": "The message was not sent. Please try again later.",
"addon.messages.messagepreferences": "Message preferences", "addon.messages.messagepreferences": "Message preferences",
@ -178,6 +180,7 @@
"addon.messages.nousersfound": "No users found", "addon.messages.nousersfound": "No users found",
"addon.messages.removecontact": "Remove contact", "addon.messages.removecontact": "Remove contact",
"addon.messages.removecontactconfirm": "Contact will be removed from your contacts list.", "addon.messages.removecontactconfirm": "Contact will be removed from your contacts list.",
"addon.messages.showdeletemessages": "Show delete messages",
"addon.messages.type_blocked": "Blocked", "addon.messages.type_blocked": "Blocked",
"addon.messages.type_offline": "Offline", "addon.messages.type_offline": "Offline",
"addon.messages.type_online": "Online", "addon.messages.type_online": "Online",