MOBILE-2632 message: Send message online and offline
parent
4710610909
commit
06f7a427c6
|
@ -40,6 +40,7 @@
|
||||||
"type_strangers": "Others",
|
"type_strangers": "Others",
|
||||||
"unblockuser": "Unblock user",
|
"unblockuser": "Unblock user",
|
||||||
"unblockuserconfirm": "Are you sure you want to unblock {{$a}}?",
|
"unblockuserconfirm": "Are you sure you want to unblock {{$a}}?",
|
||||||
|
"warningconversationmessagenotsent": "Couldn't send message(s) to conversation {{conversation}}. {{error}}",
|
||||||
"warningmessagenotsent": "Couldn't send message(s) to user {{user}}. {{error}}",
|
"warningmessagenotsent": "Couldn't send message(s) to user {{user}}. {{error}}",
|
||||||
"you": "You:"
|
"you": "You:"
|
||||||
}
|
}
|
|
@ -18,6 +18,7 @@ import { TranslateService } from '@ngx-translate/core';
|
||||||
import { CoreEventsProvider } from '@providers/events';
|
import { CoreEventsProvider } from '@providers/events';
|
||||||
import { CoreSitesProvider } from '@providers/sites';
|
import { CoreSitesProvider } from '@providers/sites';
|
||||||
import { AddonMessagesProvider } from '../../providers/messages';
|
import { AddonMessagesProvider } from '../../providers/messages';
|
||||||
|
import { AddonMessagesOfflineProvider } from '../../providers/messages-offline';
|
||||||
import { AddonMessagesSyncProvider } from '../../providers/sync';
|
import { AddonMessagesSyncProvider } from '../../providers/sync';
|
||||||
import { CoreUserProvider } from '@core/user/providers/user';
|
import { CoreUserProvider } from '@core/user/providers/user';
|
||||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||||
|
@ -79,7 +80,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
|
||||||
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) {
|
@Optional() private svComponent: CoreSplitViewComponent, private messagesOffline: AddonMessagesOfflineProvider) {
|
||||||
this.siteId = sitesProvider.getCurrentSiteId();
|
this.siteId = sitesProvider.getCurrentSiteId();
|
||||||
this.currentUserId = sitesProvider.getCurrentSiteUserId();
|
this.currentUserId = sitesProvider.getCurrentSiteUserId();
|
||||||
this.groupMessagingEnabled = this.messagesProvider.isGroupMessagingEnabled();
|
this.groupMessagingEnabled = this.messagesProvider.isGroupMessagingEnabled();
|
||||||
|
@ -166,7 +167,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Synchronize messages if needed.
|
// Synchronize messages if needed.
|
||||||
this.messagesSync.syncDiscussion(this.userId).catch(() => {
|
this.messagesSync.syncDiscussion(this.conversationId, this.userId).catch(() => {
|
||||||
// Ignore errors.
|
// Ignore errors.
|
||||||
}).then((warnings) => {
|
}).then((warnings) => {
|
||||||
if (warnings && warnings[0]) {
|
if (warnings && warnings[0]) {
|
||||||
|
@ -253,7 +254,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
|
||||||
this.fetching = true;
|
this.fetching = true;
|
||||||
|
|
||||||
// Wait for synchronization process to finish.
|
// Wait for synchronization process to finish.
|
||||||
return this.messagesSync.waitForSync(this.userId).then(() => {
|
return this.messagesSync.waitForSyncConversation(this.conversationId, this.userId).then(() => {
|
||||||
// Fetch messages. Invalidate the cache before fetching.
|
// Fetch messages. Invalidate the cache before fetching.
|
||||||
if (this.groupMessagingEnabled) {
|
if (this.groupMessagingEnabled) {
|
||||||
return this.messagesProvider.invalidateConversationMessages(this.conversationId).catch(() => {
|
return this.messagesProvider.invalidateConversationMessages(this.conversationId).catch(() => {
|
||||||
|
@ -269,8 +270,20 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).then((messages) => {
|
}).then((messages) => {
|
||||||
|
this.loadMessages(messages);
|
||||||
|
}).finally(() => {
|
||||||
|
this.fetching = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format and load a list of messages into the view.
|
||||||
|
*
|
||||||
|
* @param {any[]} messages Messages to load.
|
||||||
|
*/
|
||||||
|
protected loadMessages(messages: any[]): void {
|
||||||
if (this.viewDestroyed) {
|
if (this.viewDestroyed) {
|
||||||
return Promise.resolve();
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if we are at the bottom to scroll it after render.
|
// Check if we are at the bottom to scroll it after render.
|
||||||
|
@ -279,7 +292,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
|
||||||
|
|
||||||
if (this.messagesBeingSent > 0) {
|
if (this.messagesBeingSent > 0) {
|
||||||
// Ignore polling due to a race condition.
|
// Ignore polling due to a race condition.
|
||||||
return Promise.reject(null);
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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.
|
||||||
|
@ -306,10 +319,6 @@ 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(() => {
|
|
||||||
this.fetching = false;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -328,18 +337,29 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
|
||||||
} else {
|
} else {
|
||||||
// We don't have the conversation ID, check if it exists.
|
// We don't have the conversation ID, check if it exists.
|
||||||
promise = this.messagesProvider.getConversationBetweenUsers(this.userId).catch((error) => {
|
promise = this.messagesProvider.getConversationBetweenUsers(this.userId).catch((error) => {
|
||||||
if (error.errorcode == 'conversationdoesntexist') {
|
|
||||||
// Conversation does not exist, return undefined.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Probably conversation does not exist or user is offline. Try to load offline messages.
|
||||||
|
return this.messagesOffline.getMessages(this.userId).then((messages) => {
|
||||||
|
if (messages && messages.length) {
|
||||||
|
// We have offline messages, this probably means that the conversation didn't exist. Don't display error.
|
||||||
|
messages.forEach((message) => {
|
||||||
|
message.pending = true;
|
||||||
|
message.text = message.smallmessage;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.loadMessages(messages);
|
||||||
|
} else if (error.errorcode != 'conversationdoesntexist') {
|
||||||
|
// Display the error.
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return promise.then((conversation) => {
|
return promise.then((conversation) => {
|
||||||
this.conversation = conversation;
|
this.conversation = conversation;
|
||||||
if (conversation) {
|
if (conversation) {
|
||||||
|
this.conversationId = conversation.id;
|
||||||
this.title = conversation.name;
|
this.title = conversation.name;
|
||||||
this.conversationImage = conversation.imageurl;
|
this.conversationImage = conversation.imageurl;
|
||||||
this.isGroup = conversation.type == AddonMessagesProvider.MESSAGE_CONVERSATION_TYPE_GROUP;
|
this.isGroup = conversation.type == AddonMessagesProvider.MESSAGE_CONVERSATION_TYPE_GROUP;
|
||||||
|
@ -677,7 +697,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
|
||||||
* Function to delete a message.
|
* Function to delete a message.
|
||||||
*
|
*
|
||||||
* @param {any} message Message object to delete.
|
* @param {any} message Message object to delete.
|
||||||
* @param {number} index Index where the mesasge is to delete it from the view.
|
* @param {number} index Index where the message is to delete it from the view.
|
||||||
*/
|
*/
|
||||||
deleteMessage(message: any, index: number): void {
|
deleteMessage(message: any, index: number): void {
|
||||||
const langKey = message.pending ? 'core.areyousure' : 'addon.messages.deletemessageconfirmation';
|
const langKey = message.pending ? 'core.areyousure' : 'addon.messages.deletemessageconfirmation';
|
||||||
|
@ -779,6 +799,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
|
||||||
text: text,
|
text: text,
|
||||||
timecreated: new Date().getTime()
|
timecreated: new Date().getTime()
|
||||||
};
|
};
|
||||||
|
message.showDate = this.showDate(message, this.messages[this.messages.length - 1]);
|
||||||
this.addMessage(message, false);
|
this.addMessage(message, false);
|
||||||
|
|
||||||
this.messagesBeingSent++;
|
this.messagesBeingSent++;
|
||||||
|
@ -786,7 +807,15 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
|
||||||
// If there is an ongoing fetch, wait for it to finish.
|
// If there is an ongoing fetch, wait for it to finish.
|
||||||
// Otherwise, if a message is sent while fetching it could disappear until the next fetch.
|
// Otherwise, if a message is sent while fetching it could disappear until the next fetch.
|
||||||
this.waitForFetch().finally(() => {
|
this.waitForFetch().finally(() => {
|
||||||
this.messagesProvider.sendMessage(this.userId, text).then((data) => {
|
let promise;
|
||||||
|
|
||||||
|
if (this.conversationId) {
|
||||||
|
promise = this.messagesProvider.sendMessageToConversation(this.conversationId, text);
|
||||||
|
} else {
|
||||||
|
promise = this.messagesProvider.sendMessage(this.userId, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
promise.then((data) => {
|
||||||
let promise;
|
let promise;
|
||||||
|
|
||||||
this.messagesBeingSent--;
|
this.messagesBeingSent--;
|
||||||
|
@ -836,9 +865,6 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
|
||||||
if (!prevMessage) {
|
if (!prevMessage) {
|
||||||
// First message, show it.
|
// First message, show it.
|
||||||
return true;
|
return true;
|
||||||
} else if (message.pending) {
|
|
||||||
// If pending, it has no date, not show.
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if day has changed.
|
// Check if day has changed.
|
||||||
|
|
|
@ -26,7 +26,8 @@ export class AddonMessagesOfflineProvider {
|
||||||
protected logger;
|
protected logger;
|
||||||
|
|
||||||
// Variables for database.
|
// Variables for database.
|
||||||
static MESSAGES_TABLE = 'addon_messages_offline_messages';
|
static MESSAGES_TABLE = 'addon_messages_offline_messages'; // When group messaging isn't available or a new conversation starts.
|
||||||
|
static CONVERSATION_MESSAGES_TABLE = 'addon_messages_offline_conversation_messages'; // Conversation messages.
|
||||||
protected tablesSchema = [
|
protected tablesSchema = [
|
||||||
{
|
{
|
||||||
name: AddonMessagesOfflineProvider.MESSAGES_TABLE,
|
name: AddonMessagesOfflineProvider.MESSAGES_TABLE,
|
||||||
|
@ -53,6 +54,28 @@ export class AddonMessagesOfflineProvider {
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
primaryKeys: ['touserid', 'smallmessage', 'timecreated']
|
primaryKeys: ['touserid', 'smallmessage', 'timecreated']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: AddonMessagesOfflineProvider.CONVERSATION_MESSAGES_TABLE,
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'conversationid',
|
||||||
|
type: 'INTEGER'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'text',
|
||||||
|
type: 'TEXT'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'timecreated',
|
||||||
|
type: 'INTEGER'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'deviceoffline', // If message was stored because device was offline.
|
||||||
|
type: 'INTEGER'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
primaryKeys: ['conversationid', 'text', 'timecreated']
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -61,6 +84,25 @@ export class AddonMessagesOfflineProvider {
|
||||||
this.sitesProvider.createTablesFromSchema(this.tablesSchema);
|
this.sitesProvider.createTablesFromSchema(this.tablesSchema);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a message.
|
||||||
|
*
|
||||||
|
* @param {number} conversationId Conversation ID.
|
||||||
|
* @param {string} message The message.
|
||||||
|
* @param {number} timeCreated The time the message was created.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<any>} Promise resolved if stored, rejected if failure.
|
||||||
|
*/
|
||||||
|
deleteConversationMessage(conversationId: number, message: string, timeCreated: number, siteId?: string): Promise<any> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
return site.getDb().deleteRecords(AddonMessagesOfflineProvider.CONVERSATION_MESSAGES_TABLE, {
|
||||||
|
conversationid: conversationId,
|
||||||
|
text: message,
|
||||||
|
timecreated: timeCreated
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete a message.
|
* Delete a message.
|
||||||
*
|
*
|
||||||
|
@ -84,24 +126,18 @@ export class AddonMessagesOfflineProvider {
|
||||||
* Get all messages where deviceoffline is set to 1.
|
* Get all messages where deviceoffline is set to 1.
|
||||||
*
|
*
|
||||||
* @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 with messages.
|
* @return {Promise<any[]>} Promise resolved with messages.
|
||||||
*/
|
*/
|
||||||
getAllDeviceOfflineMessages(siteId?: string): Promise<any> {
|
getAllDeviceOfflineMessages(siteId?: string): Promise<any[]> {
|
||||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
return site.getDb().getRecords(AddonMessagesOfflineProvider.MESSAGES_TABLE, {deviceoffline: 1});
|
const promises = [];
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
promises.push(site.getDb().getRecords(AddonMessagesOfflineProvider.MESSAGES_TABLE, {deviceoffline: 1}));
|
||||||
* Get offline messages to send to a certain user.
|
promises.push(site.getDb().getRecords(AddonMessagesOfflineProvider.CONVERSATION_MESSAGES_TABLE, {deviceoffline: 1}));
|
||||||
*
|
|
||||||
* @param {number} toUserId User ID to get messages to.
|
return Promise.all(promises).then((results) => {
|
||||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
return results[0].concat(results[1]);
|
||||||
* @return {Promise<any>} Promise resolved with messages.
|
});
|
||||||
*/
|
|
||||||
getMessages(toUserId: number, siteId?: string): Promise<any> {
|
|
||||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
|
||||||
return site.getDb().getRecords(AddonMessagesOfflineProvider.MESSAGES_TABLE, {touserid: toUserId});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,7 +149,54 @@ export class AddonMessagesOfflineProvider {
|
||||||
*/
|
*/
|
||||||
getAllMessages(siteId?: string): Promise<any> {
|
getAllMessages(siteId?: string): Promise<any> {
|
||||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
return site.getDb().getAllRecords(AddonMessagesOfflineProvider.MESSAGES_TABLE);
|
const promises = [];
|
||||||
|
|
||||||
|
promises.push(site.getDb().getAllRecords(AddonMessagesOfflineProvider.MESSAGES_TABLE));
|
||||||
|
promises.push(site.getDb().getAllRecords(AddonMessagesOfflineProvider.CONVERSATION_MESSAGES_TABLE));
|
||||||
|
|
||||||
|
return Promise.all(promises).then((results) => {
|
||||||
|
return results[0].concat(results[1]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get offline messages to send to a certain user.
|
||||||
|
*
|
||||||
|
* @param {number} conversationId Conversation ID.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<any[]>} Promise resolved with messages.
|
||||||
|
*/
|
||||||
|
getConversationMessages(conversationId: number, siteId?: string): Promise<any[]> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
return site.getDb().getRecords(AddonMessagesOfflineProvider.CONVERSATION_MESSAGES_TABLE,
|
||||||
|
{conversationid: conversationId});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get offline messages to send to a certain user.
|
||||||
|
*
|
||||||
|
* @param {number} toUserId User ID to get messages to.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<any[]>} Promise resolved with messages.
|
||||||
|
*/
|
||||||
|
getMessages(toUserId: number, siteId?: string): Promise<any[]> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
return site.getDb().getRecords(AddonMessagesOfflineProvider.MESSAGES_TABLE, {touserid: toUserId});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if there are offline messages to send to a conversation.
|
||||||
|
*
|
||||||
|
* @param {number} conversationId Conversation ID.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<boolean>} Promise resolved with boolean: true if has offline messages, false otherwise.
|
||||||
|
*/
|
||||||
|
hasConversationMessages(conversationId: number, siteId?: string): Promise<boolean> {
|
||||||
|
return this.getConversationMessages(conversationId, siteId).then((messages) => {
|
||||||
|
return !!messages.length;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,14 +205,37 @@ export class AddonMessagesOfflineProvider {
|
||||||
*
|
*
|
||||||
* @param {number} toUserId User ID to check.
|
* @param {number} toUserId User ID to check.
|
||||||
* @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 with boolean: true if has offline messages, false otherwise.
|
* @return {Promise<boolean>} Promise resolved with boolean: true if has offline messages, false otherwise.
|
||||||
*/
|
*/
|
||||||
hasMessages(toUserId: number, siteId?: string): Promise<any> {
|
hasMessages(toUserId: number, siteId?: string): Promise<boolean> {
|
||||||
return this.getMessages(toUserId, siteId).then((messages) => {
|
return this.getMessages(toUserId, siteId).then((messages) => {
|
||||||
return !!messages.length;
|
return !!messages.length;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save a conversation message to be sent later.
|
||||||
|
*
|
||||||
|
* @param {number} conversationId Conversation ID.
|
||||||
|
* @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> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
const entry = {
|
||||||
|
conversationid: conversationId,
|
||||||
|
text: message,
|
||||||
|
timecreated: Date.now(),
|
||||||
|
deviceoffline: this.appProvider.isOnline() ? 0 : 1
|
||||||
|
};
|
||||||
|
|
||||||
|
return site.getDb().insertRecord(AddonMessagesOfflineProvider.CONVERSATION_MESSAGES_TABLE, entry).then(() => {
|
||||||
|
return entry;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save a message to be sent later.
|
* Save a message to be sent later.
|
||||||
*
|
*
|
||||||
|
@ -169,7 +275,11 @@ export class AddonMessagesOfflineProvider {
|
||||||
data = { deviceoffline: value ? 1 : 0 };
|
data = { deviceoffline: value ? 1 : 0 };
|
||||||
|
|
||||||
messages.forEach((message) => {
|
messages.forEach((message) => {
|
||||||
|
if (message.conversationid) {
|
||||||
|
promises.push(db.insertRecord(AddonMessagesOfflineProvider.CONVERSATION_MESSAGES_TABLE, data));
|
||||||
|
} else {
|
||||||
promises.push(db.insertRecord(AddonMessagesOfflineProvider.MESSAGES_TABLE, data));
|
promises.push(db.insertRecord(AddonMessagesOfflineProvider.MESSAGES_TABLE, data));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return Promise.all(promises);
|
return Promise.all(promises);
|
||||||
|
|
|
@ -115,8 +115,12 @@ export class AddonMessagesProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
// It's an offline message.
|
// It's an offline message.
|
||||||
|
if (message.conversationid) {
|
||||||
|
return this.messagesOffline.deleteConversationMessage(message.conversationid, message.text, message.timecreated);
|
||||||
|
} else {
|
||||||
return this.messagesOffline.deleteMessage(message.touserid, message.smallmessage, message.timecreated);
|
return this.messagesOffline.deleteMessage(message.touserid, message.smallmessage, message.timecreated);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete a message from the server.
|
* Delete a message from the server.
|
||||||
|
@ -127,13 +131,15 @@ export class AddonMessagesProvider {
|
||||||
* @return {Promise<any>} Promise resolved when the message has been deleted.
|
* @return {Promise<any>} Promise resolved when the message has been deleted.
|
||||||
*/
|
*/
|
||||||
deleteMessageOnline(id: number, read: number, userId?: number): Promise<any> {
|
deleteMessageOnline(id: number, read: number, userId?: number): Promise<any> {
|
||||||
userId = userId || this.sitesProvider.getCurrentSiteUserId();
|
const params: any = {
|
||||||
const params = {
|
|
||||||
messageid: id,
|
messageid: id,
|
||||||
userid: userId,
|
userid: userId || this.sitesProvider.getCurrentSiteUserId()
|
||||||
read: read
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (typeof read != 'undefined') {
|
||||||
|
params.read = read;
|
||||||
|
}
|
||||||
|
|
||||||
return this.sitesProvider.getCurrentSite().write('core_message_delete_message', params).then(() => {
|
return this.sitesProvider.getCurrentSite().write('core_message_delete_message', params).then(() => {
|
||||||
return this.invalidateDiscussionCache(userId);
|
return this.invalidateDiscussionCache(userId);
|
||||||
});
|
});
|
||||||
|
@ -526,11 +532,11 @@ export class AddonMessagesProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get offline messages.
|
// Get offline messages.
|
||||||
return this.messagesOffline.getMessages(userId).then((offlineMessages) => {
|
return this.messagesOffline.getConversationMessages(conversationId).then((offlineMessages) => {
|
||||||
// Mark offline messages as pending.
|
// Mark offline messages as pending.
|
||||||
offlineMessages.forEach((message) => {
|
offlineMessages.forEach((message) => {
|
||||||
message.pending = true;
|
message.pending = true;
|
||||||
message.text = message.smallmessage;
|
message.useridfrom = userId;
|
||||||
});
|
});
|
||||||
|
|
||||||
result.messages = result.messages.concat(offlineMessages);
|
result.messages = result.messages.concat(offlineMessages);
|
||||||
|
@ -1497,6 +1503,113 @@ export class AddonMessagesProvider {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a message to a conversation.
|
||||||
|
*
|
||||||
|
* @param {number} conversationId Conversation ID.
|
||||||
|
* @param {string} message The message to send.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<any>} Promise resolved with:
|
||||||
|
* - sent (boolean) True if message was sent to server, false if stored in device.
|
||||||
|
* - message (any) If sent=false, contains the stored message.
|
||||||
|
* @since 3.6
|
||||||
|
*/
|
||||||
|
sendMessageToConversation(conversationId: number, 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 {
|
||||||
|
sent: false,
|
||||||
|
message: entry
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||||
|
|
||||||
|
if (!this.appProvider.isOnline()) {
|
||||||
|
// App is offline, store the message.
|
||||||
|
return storeOffline();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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(() => {
|
||||||
|
// Error, it's safer to assume it has messages.
|
||||||
|
return true;
|
||||||
|
}).then((hasStoredMessages) => {
|
||||||
|
if (hasStoredMessages) {
|
||||||
|
return storeOffline();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Online and no messages stored. Send it to server.
|
||||||
|
return this.sendMessageToConversationOnline(conversationId, message).then(() => {
|
||||||
|
return { sent: true };
|
||||||
|
}).catch((error) => {
|
||||||
|
if (this.utils.isWebServiceError(error)) {
|
||||||
|
// It's a WebService error, the user cannot send the message so don't store it.
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error sending message, store it to retry later.
|
||||||
|
return storeOffline();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a message to a conversation. It will fail if offline or cannot connect.
|
||||||
|
*
|
||||||
|
* @param {number} conversationId Conversation ID.
|
||||||
|
* @param {string} message The message to send
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<any>} Promise resolved if success, rejected if failure.
|
||||||
|
* @since 3.6
|
||||||
|
*/
|
||||||
|
sendMessageToConversationOnline(conversationId: number, message: string, siteId?: string): Promise<any> {
|
||||||
|
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||||
|
|
||||||
|
const messages = [
|
||||||
|
{
|
||||||
|
text: message,
|
||||||
|
textformat: 1
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
return this.sendMessagesToConversationOnline(conversationId, messages, siteId).then((response) => {
|
||||||
|
return this.invalidateConversationMessages(conversationId, siteId).catch(() => {
|
||||||
|
// Ignore errors.
|
||||||
|
}).then(() => {
|
||||||
|
return response[0];
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send some messages to a conversation. It will fail if offline or cannot connect.
|
||||||
|
*
|
||||||
|
* @param {number} conversationId Conversation ID.
|
||||||
|
* @param {any} messages Messages to send. Each message must contain text and, optionally, textformat.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<any>} Promise resolved if success, rejected if failure.
|
||||||
|
* @since 3.6
|
||||||
|
*/
|
||||||
|
sendMessagesToConversationOnline(conversationId: number, messages: any, siteId?: string): Promise<any> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
const params = {
|
||||||
|
conversationid: conversationId,
|
||||||
|
messages: messages.map((message) => {
|
||||||
|
return {
|
||||||
|
text: message.text,
|
||||||
|
textformat: typeof message.textformat != 'undefined' ? message.textformat : 1
|
||||||
|
};
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
return site.write('core_message_send_messages_to_conversation', params);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper method to sort messages by time.
|
* Helper method to sort messages by time.
|
||||||
*
|
*
|
||||||
|
|
|
@ -42,6 +42,21 @@ export class AddonMessagesSyncProvider extends CoreSyncBaseProvider {
|
||||||
super('AddonMessagesSync', loggerProvider, sitesProvider, appProvider, syncProvider, textUtils, translate);
|
super('AddonMessagesSync', loggerProvider, sitesProvider, appProvider, syncProvider, textUtils, translate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the ID of a discussion sync.
|
||||||
|
*
|
||||||
|
* @param {number} conversationId Conversation ID.
|
||||||
|
* @param {number} userId User ID talking to (if no conversation ID).
|
||||||
|
* @return {string} Sync ID.
|
||||||
|
*/
|
||||||
|
protected getSyncId(conversationId: number, userId: number): string {
|
||||||
|
if (conversationId) {
|
||||||
|
return 'conversationid:' + conversationId;
|
||||||
|
} else {
|
||||||
|
return 'userid:' + userId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Try to synchronize all the discussions in a certain site or in all sites.
|
* Try to synchronize all the discussions in a certain site or in all sites.
|
||||||
*
|
*
|
||||||
|
@ -70,22 +85,39 @@ export class AddonMessagesSyncProvider extends CoreSyncBaseProvider {
|
||||||
|
|
||||||
return promise.then((messages) => {
|
return promise.then((messages) => {
|
||||||
const userIds = [],
|
const userIds = [],
|
||||||
|
conversationIds = [],
|
||||||
promises = [];
|
promises = [];
|
||||||
|
|
||||||
// Get all the discussions to be synced.
|
// Get all the conversations to be synced.
|
||||||
messages.forEach((message) => {
|
messages.forEach((message) => {
|
||||||
if (userIds.indexOf(message.touserid) == -1) {
|
if (message.conversationid) {
|
||||||
|
if (conversationIds.indexOf(message.conversationid) == -1) {
|
||||||
|
conversationIds.push(message.conversationid);
|
||||||
|
}
|
||||||
|
} else if (userIds.indexOf(message.touserid) == -1) {
|
||||||
userIds.push(message.touserid);
|
userIds.push(message.touserid);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Sync all discussions.
|
// Sync all conversations.
|
||||||
userIds.forEach((userId) => {
|
conversationIds.forEach((conversationId) => {
|
||||||
promises.push(this.syncDiscussion(userId, siteId).then((warnings) => {
|
promises.push(this.syncDiscussion(conversationId, undefined, siteId).then((warnings) => {
|
||||||
if (typeof warnings != 'undefined') {
|
if (typeof warnings != 'undefined') {
|
||||||
// Sync successful, send event.
|
// Sync successful, send event.
|
||||||
this.eventsProvider.trigger(AddonMessagesSyncProvider.AUTO_SYNCED, {
|
this.eventsProvider.trigger(AddonMessagesSyncProvider.AUTO_SYNCED, {
|
||||||
userid: userId,
|
conversationId: conversationId,
|
||||||
|
warnings: warnings
|
||||||
|
}, siteId);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
userIds.forEach((userId) => {
|
||||||
|
promises.push(this.syncDiscussion(undefined, userId, siteId).then((warnings) => {
|
||||||
|
if (typeof warnings != 'undefined') {
|
||||||
|
// Sync successful, send event.
|
||||||
|
this.eventsProvider.trigger(AddonMessagesSyncProvider.AUTO_SYNCED, {
|
||||||
|
userId: userId,
|
||||||
warnings: warnings
|
warnings: warnings
|
||||||
}, siteId);
|
}, siteId);
|
||||||
}
|
}
|
||||||
|
@ -99,24 +131,39 @@ export class AddonMessagesSyncProvider extends CoreSyncBaseProvider {
|
||||||
/**
|
/**
|
||||||
* Synchronize a discussion.
|
* Synchronize a discussion.
|
||||||
*
|
*
|
||||||
* @param {number} userId User ID of the discussion.
|
* @param {number} conversationId Conversation ID.
|
||||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
* @param {number} userId User ID talking to (if no conversation ID).
|
||||||
* @return {Promise<any>} Promise resolved if sync is successful, rejected otherwise.
|
* @return {Promise<any>} Promise resolved if sync is successful, rejected otherwise.
|
||||||
*/
|
*/
|
||||||
syncDiscussion(userId: number, siteId?: string): Promise<any> {
|
syncDiscussion(conversationId: number, userId: number, siteId?: string): Promise<any> {
|
||||||
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||||
|
|
||||||
if (this.isSyncing(userId, siteId)) {
|
const syncId = this.getSyncId(conversationId, userId),
|
||||||
// There's already a sync ongoing for this SCORM, return the promise.
|
groupMessagingEnabled = this.messagesProvider.isGroupMessagingEnabled();
|
||||||
return this.getOngoingSync(userId, siteId);
|
|
||||||
|
if (this.isSyncing(syncId, siteId)) {
|
||||||
|
// There's already a sync ongoing for this conversation, return the promise.
|
||||||
|
return this.getOngoingSync(syncId, siteId);
|
||||||
}
|
}
|
||||||
|
|
||||||
const warnings = [];
|
const warnings = [];
|
||||||
|
|
||||||
|
if (conversationId) {
|
||||||
|
this.logger.debug(`Try to sync conversation '${conversationId}'`);
|
||||||
|
} else {
|
||||||
this.logger.debug(`Try to sync discussion with user '${userId}'`);
|
this.logger.debug(`Try to sync discussion with user '${userId}'`);
|
||||||
|
}
|
||||||
|
|
||||||
// Get offline messages to be sent.
|
// Get offline messages to be sent.
|
||||||
const syncPromise = this.messagesOffline.getMessages(userId, siteId).then((messages) => {
|
let syncPromise;
|
||||||
|
|
||||||
|
if (conversationId) {
|
||||||
|
syncPromise = this.messagesOffline.getConversationMessages(conversationId, siteId);
|
||||||
|
} else {
|
||||||
|
syncPromise = this.messagesOffline.getMessages(userId, siteId);
|
||||||
|
}
|
||||||
|
|
||||||
|
syncPromise = syncPromise.then((messages) => {
|
||||||
if (!messages.length) {
|
if (!messages.length) {
|
||||||
// Nothing to sync.
|
// Nothing to sync.
|
||||||
return [];
|
return [];
|
||||||
|
@ -134,12 +181,19 @@ export class AddonMessagesSyncProvider extends CoreSyncBaseProvider {
|
||||||
messages = this.messagesProvider.sortMessages(messages);
|
messages = this.messagesProvider.sortMessages(messages);
|
||||||
|
|
||||||
// Send the messages.
|
// Send the messages.
|
||||||
// We don't use AddonMessagesProvider#sendMessagesOnline because there's a problem with display order.
|
// Send them 1 by 1 to simulate web's behaviour and to make sure we know which message has failed.
|
||||||
// @todo Use AddonMessagesProvider#sendMessagesOnline once the display order is fixed.
|
|
||||||
messages.forEach((message, index) => {
|
messages.forEach((message, index) => {
|
||||||
// Chain message sending. If 1 message fails to be sent we'll stop sending.
|
// Chain message sending. If 1 message fails to be sent we'll stop sending.
|
||||||
promise = promise.then(() => {
|
promise = promise.then(() => {
|
||||||
return this.messagesProvider.sendMessageOnline(userId, message.smallmessage, siteId).catch((error) => {
|
let subPromise;
|
||||||
|
|
||||||
|
if (conversationId) {
|
||||||
|
subPromise = this.messagesProvider.sendMessageToConversationOnline(conversationId, message.text, siteId);
|
||||||
|
} else {
|
||||||
|
subPromise = this.messagesProvider.sendMessageOnline(userId, message.smallmessage, siteId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return subPromise.catch((error) => {
|
||||||
if (this.utils.isWebServiceError(error)) {
|
if (this.utils.isWebServiceError(error)) {
|
||||||
// Error returned by WS. Store the error to show a warning but keep sending messages.
|
// Error returned by WS. Store the error to show a warning but keep sending messages.
|
||||||
if (errors.indexOf(error) == -1) {
|
if (errors.indexOf(error) == -1) {
|
||||||
|
@ -158,22 +212,61 @@ export class AddonMessagesSyncProvider extends CoreSyncBaseProvider {
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
// Message was sent, delete it from local DB.
|
// Message was sent, delete it from local DB.
|
||||||
|
if (conversationId) {
|
||||||
|
return this.messagesOffline.deleteConversationMessage(conversationId, message.text,
|
||||||
|
message.timecreated, siteId);
|
||||||
|
} else {
|
||||||
return this.messagesOffline.deleteMessage(userId, message.smallmessage, message.timecreated, siteId);
|
return this.messagesOffline.deleteMessage(userId, message.smallmessage, message.timecreated, siteId);
|
||||||
|
}
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
// All done. Wait 1 second to ensure timecreated of messages is different.
|
// In some Moodle versions, wait 1 second to make sure timecreated is different.
|
||||||
if (index < messages.length - 1) {
|
// This is because there was a bug where messages with the same timecreated had a wrong order.
|
||||||
|
if (!groupMessagingEnabled && index < messages.length - 1) {
|
||||||
return setTimeout(() => {return; }, 1000);
|
return setTimeout(() => {return; }, 1000);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return promise.then(() => {
|
return promise;
|
||||||
return errors;
|
|
||||||
});
|
|
||||||
}).then((errors) => {
|
}).then((errors) => {
|
||||||
|
return this.handleSyncErrors(conversationId, userId, errors, warnings);
|
||||||
|
}).then(() => {
|
||||||
|
// All done, return the warnings.
|
||||||
|
return warnings;
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.addOngoingSync(syncId, syncPromise, siteId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle sync errors.
|
||||||
|
*
|
||||||
|
* @param {number} conversationId Conversation ID.
|
||||||
|
* @param {number} userId User ID talking to (if no conversation ID).
|
||||||
|
* @param {any[]} errors List of errors.
|
||||||
|
* @param {any[]} warnings Array where to place the warnings.
|
||||||
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
|
*/
|
||||||
|
protected handleSyncErrors(conversationId: number, userId: number, errors: any[], warnings: any[]): Promise<any> {
|
||||||
if (errors && errors.length) {
|
if (errors && errors.length) {
|
||||||
// At least an error occurred, get user full name and add errors to warnings array.
|
if (conversationId) {
|
||||||
|
|
||||||
|
// Get conversation name and add errors to warnings array.
|
||||||
|
return this.messagesProvider.getConversation(conversationId, false, false).catch(() => {
|
||||||
|
// Ignore errors.
|
||||||
|
return {};
|
||||||
|
}).then((conversation) => {
|
||||||
|
errors.forEach((error) => {
|
||||||
|
warnings.push(this.translate.instant('addon.messages.warningconversationmessagenotsent', {
|
||||||
|
conversation: conversation.name ? conversation.name : conversationId,
|
||||||
|
error: this.textUtils.getErrorMessageFromError(error)
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// Get user full name and add errors to warnings array.
|
||||||
return this.userProvider.getProfile(userId, undefined, true).catch(() => {
|
return this.userProvider.getProfile(userId, undefined, true).catch(() => {
|
||||||
// Ignore errors.
|
// Ignore errors.
|
||||||
return {};
|
return {};
|
||||||
|
@ -181,16 +274,26 @@ export class AddonMessagesSyncProvider extends CoreSyncBaseProvider {
|
||||||
errors.forEach((error) => {
|
errors.forEach((error) => {
|
||||||
warnings.push(this.translate.instant('addon.messages.warningmessagenotsent', {
|
warnings.push(this.translate.instant('addon.messages.warningmessagenotsent', {
|
||||||
user: user.fullname ? user.fullname : userId,
|
user: user.fullname ? user.fullname : userId,
|
||||||
error: error
|
error: this.textUtils.getErrorMessageFromError(error)
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).then(() => {
|
}
|
||||||
// All done, return the warnings.
|
}
|
||||||
return warnings;
|
|
||||||
});
|
|
||||||
|
|
||||||
return this.addOngoingSync(userId, syncPromise, siteId);
|
/**
|
||||||
|
* If there's an ongoing sync for a certain conversation, wait for it to end.
|
||||||
|
* If there's no sync ongoing the promise will be resolved right away.
|
||||||
|
*
|
||||||
|
* @param {number} conversationId Conversation ID.
|
||||||
|
* @param {number} userId User ID talking to (if no conversation ID).
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<any>} Promise resolved when there's no sync going on for the identifier.
|
||||||
|
*/
|
||||||
|
waitForSyncConversation(conversationId: number, userId: number, siteId?: string): Promise<any> {
|
||||||
|
const syncId = this.getSyncId(conversationId, userId);
|
||||||
|
|
||||||
|
return this.waitForSync(syncId, siteId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -188,6 +188,7 @@
|
||||||
"addon.messages.type_strangers": "Others",
|
"addon.messages.type_strangers": "Others",
|
||||||
"addon.messages.unblockuser": "Unblock user",
|
"addon.messages.unblockuser": "Unblock user",
|
||||||
"addon.messages.unblockuserconfirm": "Are you sure you want to unblock {{$a}}?",
|
"addon.messages.unblockuserconfirm": "Are you sure you want to unblock {{$a}}?",
|
||||||
|
"addon.messages.warningconversationmessagenotsent": "Couldn't send message(s) to conversation {{conversation}}. {{error}}",
|
||||||
"addon.messages.warningmessagenotsent": "Couldn't send message(s) to user {{user}}. {{error}}",
|
"addon.messages.warningmessagenotsent": "Couldn't send message(s) to user {{user}}. {{error}}",
|
||||||
"addon.messages.you": "You:",
|
"addon.messages.you": "You:",
|
||||||
"addon.mod_assign.acceptsubmissionstatement": "Please accept the submission statement.",
|
"addon.mod_assign.acceptsubmissionstatement": "Please accept the submission statement.",
|
||||||
|
|
Loading…
Reference in New Issue