MOBILE-2632 message: Display offline messages in conversations list

main
Dani Palou 2018-11-20 13:16:31 +01:00
parent 06f7a427c6
commit ffc98f2c71
5 changed files with 190 additions and 19 deletions

View File

@ -54,7 +54,7 @@
<core-empty-box *ngIf="!messages || messages.length <= 0" icon="chatbubbles" [message]="'addon.messages.nomessages' | translate"></core-empty-box>
</core-loading>
</ion-content>
<ion-footer color="light" class="footer-adjustable">
<ion-footer color="light" class="footer-adjustable" *ngIf="!conversationId || conversation">
<ion-toolbar color="light" position="bottom">
<core-send-message-form (onSubmit)="sendMessage($event)" [showKeyboard]="showKeyboard" [placeholder]="'addon.messages.newmessage' | translate" (onResize)="resizeContent()"></core-send-message-form>
</ion-toolbar>

View File

@ -781,6 +781,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
/**
* Sends a message to the server.
*
* @param {string} text Message text.
*/
sendMessage(text: string): void {
@ -810,7 +811,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
let promise;
if (this.conversationId) {
promise = this.messagesProvider.sendMessageToConversation(this.conversationId, text);
promise = this.messagesProvider.sendMessageToConversation(this.conversation, text);
} else {
promise = this.messagesProvider.sendMessage(this.userId, text);
}

View File

@ -18,11 +18,13 @@ import { TranslateService } from '@ngx-translate/core';
import { CoreEventsProvider } from '@providers/events';
import { CoreSitesProvider } from '@providers/sites';
import { AddonMessagesProvider } from '../../providers/messages';
import { AddonMessagesOfflineProvider } from '../../providers/messages-offline';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CoreUtilsProvider } from '@providers/utils/utils';
import { CoreAppProvider } from '@providers/app';
import { AddonPushNotificationsDelegate } from '@addon/pushnotifications/providers/delegate';
import { CoreSplitViewComponent } from '@components/split-view/split-view';
import { CoreUserProvider } from '@core/user/providers/user';
/**
* Page that displays the list of conversations, including group conversations.
@ -71,7 +73,8 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
constructor(private eventsProvider: CoreEventsProvider, sitesProvider: CoreSitesProvider, translate: TranslateService,
private messagesProvider: AddonMessagesProvider, private domUtils: CoreDomUtilsProvider, navParams: NavParams,
private appProvider: CoreAppProvider, platform: Platform, utils: CoreUtilsProvider,
pushNotificationsDelegate: AddonPushNotificationsDelegate) {
pushNotificationsDelegate: AddonPushNotificationsDelegate, private messagesOffline: AddonMessagesOfflineProvider,
private userProvider: CoreUserProvider) {
this.search.loading = translate.instant('core.searching');
this.loadingString = translate.instant('core.loading');
@ -179,12 +182,25 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
// Load the first conversations of each type.
const promises = [];
let offlineMessages;
promises.push(this.fetchDataForOption(this.favourites, false));
promises.push(this.fetchDataForOption(this.group, false));
promises.push(this.fetchDataForOption(this.individual, false));
promises.push(this.messagesOffline.getAllMessages().then((messages) => {
offlineMessages = messages;
}));
return Promise.all(promises).then(() => {
return this.loadOfflineMessages(offlineMessages);
}).then(() => {
if (offlineMessages && offlineMessages.length) {
// Sort the conversations, the offline messages could affect the order.
this.favourites.conversations = this.messagesProvider.sortConversations(this.favourites.conversations);
this.group.conversations = this.messagesProvider.sortConversations(this.group.conversations);
this.individual.conversations = this.messagesProvider.sortConversations(this.individual.conversations);
}
if (typeof this.favourites.expanded == 'undefined') {
// The expanded status hasn't been initialized. Do it now.
this.favourites.expanded = this.favourites.count != 0;
@ -286,6 +302,98 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
});
}
/**
* Load offline messages into the conversations.
*
* @param {any[]} messages Offline messages.
* @return {Promise<any>} Promise resolved when done.
*/
protected loadOfflineMessages(messages: any[]): Promise<any> {
const promises = [];
messages.forEach((message) => {
if (message.conversationid) {
// It's an existing conversation. Search it.
let conversation = this.findConversation(message.conversationid);
if (conversation) {
// Check if it's the last message. Offline messages are considered more recent than sent messages.
if (typeof conversation.lastmessage === 'undefined' || conversation.lastmessage === null ||
!conversation.lastmessagepending || conversation.lastmessagedate <= message.timecreated / 1000) {
this.addLastOfflineMessage(conversation, message);
}
} else {
// Conversation not found, it's probably an old one. Add it.
conversation = message.conversation || {};
conversation.id = message.conversationid;
this.addLastOfflineMessage(conversation, message);
this.addOfflineConversation(conversation);
}
} else {
// Its a new conversation. Check if we already created it (there is more than one message for the same user).
const conversation = this.individual.conversations.find((conv) => {
return conv.userid == message.touserid;
});
message.text = message.smallmessage;
if (conversation) {
// Check if it's the last message. Offline messages are considered more recent than sent messages.
if (conversation.lastmessagedate <= message.timecreated / 1000) {
this.addLastOfflineMessage(conversation, message);
}
} else {
// Get the user data and create a new conversation.
promises.push(this.userProvider.getProfile(message.touserid, undefined, true).catch(() => {
// User not found.
}).then((user) => {
const conversation = {
userid: message.touserid,
name: user ? user.fullname : String(message.touserid),
imageurl: user ? user.profileimageurl : '',
type: AddonMessagesProvider.MESSAGE_CONVERSATION_TYPE_INDIVIDUAL
};
this.addLastOfflineMessage(conversation, message);
this.addOfflineConversation(conversation);
}));
}
}
});
return Promise.all(promises);
}
/**
* Add an offline conversation into the right list of conversations.
*
* @param {any} conversation Offline conversation to add.
*/
protected addOfflineConversation(conversation: any): void {
if (conversation.isfavourite) {
this.favourites.conversations.unshift(conversation);
} else if (conversation.type == AddonMessagesProvider.MESSAGE_CONVERSATION_TYPE_GROUP) {
this.group.conversations.unshift(conversation);
} else {
this.individual.conversations.unshift(conversation);
}
}
/**
* Add a last offline message into a conversation.
*
* @param {any} conversation Conversation where to put the last message.
* @param {any} message Offline message to add.
*/
protected addLastOfflineMessage(conversation: any, message: any): void {
conversation.lastmessage = message.text;
conversation.lastmessagedate = message.timecreated / 1000;
conversation.lastmessagepending = true;
conversation.sentfromcurrentuser = true;
}
/**
* Refresh the data.
*

View File

@ -16,6 +16,7 @@ import { Injectable } from '@angular/core';
import { CoreLoggerProvider } from '@providers/logger';
import { CoreSitesProvider } from '@providers/sites';
import { CoreAppProvider } from '@providers/app';
import { CoreTextUtilsProvider } from '@providers/utils/text';
/**
* Service to handle Offline messages.
@ -73,13 +74,18 @@ export class AddonMessagesOfflineProvider {
{
name: 'deviceoffline', // If message was stored because device was offline.
type: 'INTEGER'
},
{
name: 'conversation', // Data about the conversation.
type: 'TEXT'
}
],
primaryKeys: ['conversationid', 'text', 'timecreated']
}
];
constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private appProvider: CoreAppProvider) {
constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private appProvider: CoreAppProvider,
private textUtils: CoreTextUtilsProvider) {
this.logger = logger.getInstance('AddonMessagesOfflineProvider');
this.sitesProvider.createTablesFromSchema(this.tablesSchema);
}
@ -136,6 +142,8 @@ export class AddonMessagesOfflineProvider {
promises.push(site.getDb().getRecords(AddonMessagesOfflineProvider.CONVERSATION_MESSAGES_TABLE, {deviceoffline: 1}));
return Promise.all(promises).then((results) => {
results[1] = this.parseConversationMessages(results[1]);
return results[0].concat(results[1]);
});
});
@ -155,6 +163,8 @@ export class AddonMessagesOfflineProvider {
promises.push(site.getDb().getAllRecords(AddonMessagesOfflineProvider.CONVERSATION_MESSAGES_TABLE));
return Promise.all(promises).then((results) => {
results[1] = this.parseConversationMessages(results[1]);
return results[0].concat(results[1]);
});
});
@ -170,7 +180,10 @@ export class AddonMessagesOfflineProvider {
getConversationMessages(conversationId: number, siteId?: string): Promise<any[]> {
return this.sitesProvider.getSite(siteId).then((site) => {
return site.getDb().getRecords(AddonMessagesOfflineProvider.CONVERSATION_MESSAGES_TABLE,
{conversationid: conversationId});
{conversationid: conversationId}).then((messages) => {
return this.parseConversationMessages(messages);
});
});
}
@ -213,21 +226,48 @@ export class AddonMessagesOfflineProvider {
});
}
/**
* Parse some fields of each offline conversation messages.
*
* @param {any[]} messages List of messages to parse.
* @return {any[]} Parsed messages.
*/
protected parseConversationMessages(messages: any[]): any[] {
if (!messages) {
return [];
}
messages.forEach((message) => {
if (message.conversation) {
message.conversation = this.textUtils.parseJSON(message.conversation, {});
}
});
return messages;
}
/**
* Save a conversation message to be sent later.
*
* @param {number} conversationId Conversation ID.
* @param {any} conversation Conversation.
* @param {string} message The message to send.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved if stored, rejected if failure.
*/
saveConversationMessage(conversationId: number, message: string, siteId?: string): Promise<any> {
saveConversationMessage(conversation: any, message: string, siteId?: string): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => {
const entry = {
conversationid: conversationId,
conversationid: conversation.id,
text: message,
timecreated: Date.now(),
deviceoffline: this.appProvider.isOnline() ? 0 : 1
deviceoffline: this.appProvider.isOnline() ? 0 : 1,
conversation: JSON.stringify({
name: conversation.name || '',
subname: conversation.subname || '',
imageurl: conversation.imageurl || '',
isfavourite: conversation.isfavourite ? 1 : 0,
type: conversation.type
})
};
return site.getDb().insertRecord(AddonMessagesOfflineProvider.CONVERSATION_MESSAGES_TABLE, entry).then(() => {
@ -276,9 +316,11 @@ export class AddonMessagesOfflineProvider {
messages.forEach((message) => {
if (message.conversationid) {
promises.push(db.insertRecord(AddonMessagesOfflineProvider.CONVERSATION_MESSAGES_TABLE, data));
promises.push(db.updateRecords(AddonMessagesOfflineProvider.CONVERSATION_MESSAGES_TABLE, data,
{conversationid: message.conversationid, text: message.text, timecreated: message.timecreated}));
} else {
promises.push(db.insertRecord(AddonMessagesOfflineProvider.MESSAGES_TABLE, data));
promises.push(db.updateRecords(AddonMessagesOfflineProvider.MESSAGES_TABLE, data,
{touserid: message.touserid, smallmessage: message.smallmessage, timecreated: message.timecreated}));
}
});

View File

@ -1506,7 +1506,7 @@ export class AddonMessagesProvider {
/**
* Send a message to a conversation.
*
* @param {number} conversationId Conversation ID.
* @param {any} conversation Conversation.
* @param {string} message The message to send.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved with:
@ -1514,10 +1514,10 @@ export class AddonMessagesProvider {
* - message (any) If sent=false, contains the stored message.
* @since 3.6
*/
sendMessageToConversation(conversationId: number, message: string, siteId?: string): Promise<any> {
sendMessageToConversation(conversation: any, message: string, siteId?: string): Promise<any> {
// Convenience function to store a message to be synchronized later.
const storeOffline = (): Promise<any> => {
return this.messagesOffline.saveConversationMessage(conversationId, message, siteId).then((entry) => {
return this.messagesOffline.saveConversationMessage(conversation, message, siteId).then((entry) => {
return {
sent: false,
message: entry
@ -1534,7 +1534,7 @@ export class AddonMessagesProvider {
// Check if this conversation already has offline messages.
// If so, store this message since they need to be sent in order.
return this.messagesOffline.hasConversationMessages(conversationId, siteId).catch(() => {
return this.messagesOffline.hasConversationMessages(conversation.id, siteId).catch(() => {
// Error, it's safer to assume it has messages.
return true;
}).then((hasStoredMessages) => {
@ -1543,7 +1543,7 @@ export class AddonMessagesProvider {
}
// Online and no messages stored. Send it to server.
return this.sendMessageToConversationOnline(conversationId, message).then(() => {
return this.sendMessageToConversationOnline(conversation.id, message).then(() => {
return { sent: true };
}).catch((error) => {
if (this.utils.isWebServiceError(error)) {
@ -1610,13 +1610,33 @@ export class AddonMessagesProvider {
});
}
/**
* Helper method to sort conversations by last message time.
*
* @param {any[]} conversations Array of conversations.
* @return {any[]} Conversations sorted with most recent last.
*/
sortConversations(conversations: any[]): any[] {
return conversations.sort((a, b) => {
const timeA = parseInt(a.lastmessagedate, 10),
timeB = parseInt(b.lastmessagedate, 10);
if (timeA == timeB && a.id) {
// Same time, sort by ID.
return a.id <= b.id ? 1 : -1;
}
return timeA <= timeB ? 1 : -1;
});
}
/**
* Helper method to sort messages by time.
*
* @param {any} messages Array of messages containing the key 'timecreated'.
* @return {any} Messages sorted with most recent last.
* @param {any[]} messages Array of messages containing the key 'timecreated'.
* @return {any[]} Messages sorted with most recent last.
*/
sortMessages(messages: any): any {
sortMessages(messages: any[]): any[] {
return messages.sort((a, b) => {
// Pending messages last.
if (a.pending && !b.pending) {