diff --git a/scripts/langindex.json b/scripts/langindex.json index 9f1566add..392af6ed2 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -148,6 +148,7 @@ "addon.files.sitefiles": "moodle", "addon.messageoutput_airnotifier.processorsettingsdesc": "local_moodlemobileapp", "addon.messages.addcontact": "message", + "addon.messages.addtofavourites": "message", "addon.messages.blocknoncontacts": "message", "addon.messages.blockuser": "message", "addon.messages.blockuserconfirm": "message", @@ -159,6 +160,8 @@ "addon.messages.contactlistempty": "local_moodlemobileapp", "addon.messages.contactname": "local_moodlemobileapp", "addon.messages.contacts": "message", + "addon.messages.deleteallconfirm": "message", + "addon.messages.deleteconversationq": "message", "addon.messages.deletemessage": "local_moodlemobileapp", "addon.messages.deletemessageconfirmation": "local_moodlemobileapp", "addon.messages.errordeletemessage": "local_moodlemobileapp", @@ -181,6 +184,7 @@ "addon.messages.numparticipants": "message", "addon.messages.removecontact": "message", "addon.messages.removecontactconfirm": "local_moodlemobileapp", + "addon.messages.removefromfavourites": "message", "addon.messages.showdeletemessages": "local_moodlemobileapp", "addon.messages.type_blocked": "local_moodlemobileapp", "addon.messages.type_offline": "local_moodlemobileapp", diff --git a/src/addon/messages/lang/en.json b/src/addon/messages/lang/en.json index a95948ee2..9bfb0f29b 100644 --- a/src/addon/messages/lang/en.json +++ b/src/addon/messages/lang/en.json @@ -1,5 +1,6 @@ { "addcontact": "Add contact", + "addtofavourites": "Star", "blocknoncontacts": "Prevent non-contacts from messaging me", "blockuser": "Block user", "blockuserconfirm": "Are you sure you want to block {{$a}}?", @@ -11,6 +12,8 @@ "contactlistempty": "The contact list is empty", "contactname": "Contact name", "contacts": "Contacts", + "deleteallconfirm": "Are you sure you would like to delete this entire conversation? This will not delete it for other conversation participants.", + "deleteconversation": "Delete conversation", "deletemessage": "Delete message", "deletemessageconfirmation": "Are you sure you want to delete this message? It will only be deleted from your messaging history and will still be viewable by the user who sent or received the message.", "errordeletemessage": "Error while deleting the message.", @@ -33,6 +36,7 @@ "numparticipants": "{{$a}} participants", "removecontact": "Remove contact", "removecontactconfirm": "Contact will be removed from your contacts list.", + "removefromfavourites": "Unstar", "showdeletemessages": "Show delete messages", "type_blocked": "Blocked", "type_offline": "Offline", diff --git a/src/addon/messages/pages/discussion/discussion.html b/src/addon/messages/pages/discussion/discussion.html index 0629928fc..ce5d58787 100644 --- a/src/addon/messages/pages/discussion/discussion.html +++ b/src/addon/messages/pages/discussion/discussion.html @@ -3,14 +3,17 @@ + - - + + + + @@ -54,7 +57,7 @@ - + diff --git a/src/addon/messages/pages/discussion/discussion.ts b/src/addon/messages/pages/discussion/discussion.ts index 7cbe7df9f..cd1d1d967 100644 --- a/src/addon/messages/pages/discussion/discussion.ts +++ b/src/addon/messages/pages/discussion/discussion.ts @@ -75,6 +75,8 @@ export class AddonMessagesDiscussionPage implements OnDestroy { groupMessagingEnabled: boolean; isGroup = false; members: any = {}; // Members that wrote a message, indexed by ID. + favouriteIcon = 'fa-star'; + deleteIcon = 'trash'; constructor(private eventsProvider: CoreEventsProvider, sitesProvider: CoreSitesProvider, navParams: NavParams, private userProvider: CoreUserProvider, private navCtrl: NavController, private messagesSync: AddonMessagesSyncProvider, @@ -332,51 +334,67 @@ export class AddonMessagesDiscussionPage implements OnDestroy { * @return {Promise} Promise resolved with a boolean: whether the conversation exists or not. */ protected getConversation(conversationId: number, userId: number): Promise { - let promise; + let promise, + fallbackConversation; + // Try to get the conversationId if we don't have it. if (conversationId) { - // Retrieve the conversation. Invalidate data first to get the right unreadcount. - promise = this.messagesProvider.invalidateConversation(conversationId).then(() => { - return this.messagesProvider.getConversation(conversationId); - }); + promise = Promise.resolve(conversationId); } else { - // We don't have the conversation ID, check if it exists. - promise = this.messagesProvider.getConversationBetweenUsers(userId).catch((error) => { + promise = this.messagesProvider.getConversationBetweenUsers(userId).then((conversation) => { + fallbackConversation = conversation; - // Probably conversation does not exist or user is offline. Try to load offline messages. - return this.messagesOffline.getMessages(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 != 'errorconversationdoesnotexist') { - // Display the error. - return Promise.reject(error); - } - }); + return conversation.id; }); } - return promise.then((conversation) => { - this.conversation = conversation; - - if (conversation) { - this.conversationId = conversation.id; - 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 promise.then((conversationId) => { + // Retrieve the conversation. Invalidate data first to get the right unreadcount. + return this.messagesProvider.invalidateConversation(conversationId).catch(() => { + // Ignore errors. + }).then(() => { + return this.messagesProvider.getConversation(conversationId); + }).catch((error) => { + // Get conversation failed, use the fallback one if we have it. + if (fallbackConversation) { + return fallbackConversation; } - return true; - } else { - return false; - } + return Promise.reject(error); + }).then((conversation) => { + this.conversation = conversation; + + if (conversation) { + this.conversationId = conversation.id; + this.title = conversation.name; + this.conversationImage = conversation.imageurl; + this.isGroup = conversation.type == AddonMessagesProvider.MESSAGE_CONVERSATION_TYPE_GROUP; + this.favouriteIcon = conversation.isfavourite ? 'fa-star-o' : 'fa-star'; + if (!this.isGroup) { + this.userId = conversation.userid; + } + + return true; + } else { + return false; + } + }); + }, (error) => { + // Probably conversation does not exist or user is offline. Try to load offline messages. + return this.messagesOffline.getMessages(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 != 'errorconversationdoesnotexist') { + // Display the error. + return Promise.reject(error); + } + }); }); } @@ -940,6 +958,60 @@ export class AddonMessagesDiscussionPage implements OnDestroy { } } + /** + * Change the favourite state of the current conversation. + * + * @param {Function} [done] Function to call when done. + */ + changeFavourite(done?: () => void): void { + this.favouriteIcon = 'spinner'; + + this.messagesProvider.setFavouriteConversation(this.conversation.id, !this.conversation.isfavourite).then(() => { + this.conversation.isfavourite = !this.conversation.isfavourite; + + // Get the conversation data so it's cached. Don't block the user for this. + this.messagesProvider.getConversation(this.conversation.id); + + this.eventsProvider.trigger(AddonMessagesProvider.UPDATE_CONVERSATION_LIST_EVENT, { + conversationId: this.conversation.id, + action: 'favourite', + value: this.conversation.isfavourite + }, this.siteId); + }).catch((error) => { + this.domUtils.showErrorModalDefault(error, 'Error changing favourite state.'); + }).finally(() => { + this.favouriteIcon = this.conversation.isfavourite ? 'fa-star-o' : 'fa-star'; + done && done(); + }); + } + + /** + * Delete the conversation. + * + * @param {Function} [done] Function to call when done. + */ + deleteConversation(done?: () => void): void { + this.domUtils.showConfirm(this.translate.instant('addon.messages.deleteallconfirm')).then(() => { + this.deleteIcon = 'spinner'; + + return this.messagesProvider.deleteConversation(this.conversation.id).then(() => { + this.eventsProvider.trigger(AddonMessagesProvider.UPDATE_CONVERSATION_LIST_EVENT, { + conversationId: this.conversation.id, + action: 'delete' + }, this.siteId); + + this.conversationId = undefined; + this.conversation = undefined; + this.messages = []; + }).finally(() => { + this.deleteIcon = 'trash'; + done && done(); + }); + }).catch((error) => { + this.domUtils.showErrorModalDefault(error, 'Error deleting conversation.'); + }); + } + /** * Page destroyed. */ diff --git a/src/addon/messages/pages/group-conversations/group-conversations.ts b/src/addon/messages/pages/group-conversations/group-conversations.ts index 2c8cf2adc..2b9239741 100644 --- a/src/addon/messages/pages/group-conversations/group-conversations.ts +++ b/src/addon/messages/pages/group-conversations/group-conversations.ts @@ -73,6 +73,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { protected readChangedObserver: any; protected cronObserver: any; protected openConversationObserver: any; + protected updateConversationListObserver: any; constructor(private eventsProvider: CoreEventsProvider, sitesProvider: CoreSitesProvider, translate: TranslateService, private messagesProvider: AddonMessagesProvider, private domUtils: CoreDomUtilsProvider, navParams: NavParams, @@ -115,22 +116,22 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { } }, this.siteId); - // Update discussions when a message is read. + // Update conversations when a message is read. this.readChangedObserver = eventsProvider.on(AddonMessagesProvider.READ_CHANGED_EVENT, (data) => { if (data.conversationId) { const conversation = this.findConversation(data.conversationId); if (typeof conversation != 'undefined') { - // A discussion has been read reset counter. + // A conversation has been read reset counter. conversation.unreadcount = 0; - // Discussions changed, invalidate them. + // Conversations changed, invalidate them. this.messagesProvider.invalidateConversations(); } } }, this.siteId); - // Update discussions when cron read is executed. + // Update conversations when cron read is executed. this.cronObserver = eventsProvider.on(AddonMessagesProvider.READ_CRON_EVENT, (data) => { this.refreshData(); }, this.siteId); @@ -153,6 +154,11 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { }); }); + // Update conversations if we receive an event to do so. + this.updateConversationListObserver = eventsProvider.on(AddonMessagesProvider.UPDATE_CONVERSATION_LIST_EVENT, () => { + this.refreshData(); + }, this.siteId); + // If a message push notification is received, refresh the view. this.pushObserver = pushNotificationsDelegate.on('receive').subscribe((notification) => { // New message received. If it's from current site, refresh the data. @@ -551,5 +557,6 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { this.readChangedObserver && this.readChangedObserver.off(); this.cronObserver && this.cronObserver.off(); this.openConversationObserver && this.openConversationObserver.off(); + this.updateConversationListObserver && this.updateConversationListObserver.off(); } } diff --git a/src/addon/messages/providers/messages-offline.ts b/src/addon/messages/providers/messages-offline.ts index f746c9147..f340c7e17 100644 --- a/src/addon/messages/providers/messages-offline.ts +++ b/src/addon/messages/providers/messages-offline.ts @@ -109,6 +109,21 @@ export class AddonMessagesOfflineProvider { }); } + /** + * Delete all the messages in a conversation. + * + * @param {number} conversationId Conversation ID. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved if stored, rejected if failure. + */ + deleteConversationMessages(conversationId: number, siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + return site.getDb().deleteRecords(AddonMessagesOfflineProvider.CONVERSATION_MESSAGES_TABLE, { + conversationid: conversationId + }); + }); + } + /** * Delete a message. * diff --git a/src/addon/messages/providers/messages.ts b/src/addon/messages/providers/messages.ts index eeae588bb..e6f859509 100644 --- a/src/addon/messages/providers/messages.ts +++ b/src/addon/messages/providers/messages.ts @@ -35,6 +35,7 @@ export class AddonMessagesProvider { static READ_CRON_EVENT = 'addon_messages_read_cron_event'; static OPEN_CONVERSATION_EVENT = 'addon_messages_open_conversation_event'; // Notify that a conversation should be opened. static SPLIT_VIEW_LOAD_EVENT = 'addon_messages_split_view_load_event'; + static UPDATE_CONVERSATION_LIST_EVENT = 'addon_messages_update_conversation_list_event'; static POLL_INTERVAL = 10000; static PUSH_SIMULATION_COMPONENT = 'AddonMessagesPushSimulation'; @@ -105,6 +106,49 @@ export class AddonMessagesProvider { }); } + /** + * Delete a conversation. + * + * @param {number} conversationId Conversation to delete. + * @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} Promise resolved when the conversation has been deleted. + */ + deleteConversation(conversationId: number, siteId?: string, userId?: number): Promise { + return this.deleteConversations([conversationId], siteId, userId); + } + + /** + * Delete several conversations. + * + * @param {number[]} conversationIds Conversations to delete. + * @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} Promise resolved when the conversations have been deleted. + */ + deleteConversations(conversationIds: number[], siteId?: string, userId?: number): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + userId = userId || site.getUserId(); + + const params = { + userid: userId, + conversationids: conversationIds + }; + + return site.write('core_message_delete_conversations_by_id', params).then(() => { + const promises = []; + + conversationIds.forEach((conversationId) => { + promises.push(this.messagesOffline.deleteConversationMessages(conversationId, site.getId()).catch(() => { + // Ignore errors (shouldn't happen). + })); + }); + + return Promise.all(promises); + }); + }); + } + /** * Delete a message (online or offline). * @@ -1114,7 +1158,7 @@ export class AddonMessagesProvider { return this.sitesProvider.getSite(siteId).then((site) => { userId = userId || site.getUserId(); - return site.invalidateWsCacheForKey(this.getCacheKeyForConversation(conversationId, userId)); + return site.invalidateWsCacheForKey(this.getCacheKeyForConversation(userId, conversationId)); }); } @@ -1697,6 +1741,53 @@ export class AddonMessagesProvider { }); } + /** + * Set or unset a conversation as favourite. + * + * @param {number} conversationId Conversation ID. + * @param {boolean} set Whether to set or unset it as favourite. + * @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} Resolved when done. + */ + setFavouriteConversation(conversationId: number, set: boolean, siteId?: string, userId?: number): Promise { + return this.setFavouriteConversations([conversationId], set, siteId, userId); + } + + /** + * Set or unset some conversations as favourites. + * + * @param {number[]} conversations Conversation IDs. + * @param {boolean} set Whether to set or unset them as favourites. + * @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} Resolved when done. + */ + setFavouriteConversations(conversations: number[], set: boolean, siteId?: string, userId?: number): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + userId = userId || site.getUserId(); + + const params = { + userid: userId, + conversations: conversations + }, + wsName = set ? 'core_message_set_favourite_conversations' : 'core_message_unset_favourite_conversations'; + + return site.write(wsName, params).then(() => { + // Invalidate the conversations data. + const promises = []; + + conversations.forEach((conversationId) => { + promises.push(this.invalidateConversation(conversationId, site.getId(), userId)); + }); + + return Promise.all(promises).catch(() => { + // Ignore errors. + }); + }); + }); + } + /** * Helper method to sort conversations by last message time. * diff --git a/src/assets/lang/en.json b/src/assets/lang/en.json index 810196869..45d914929 100644 --- a/src/assets/lang/en.json +++ b/src/assets/lang/en.json @@ -148,6 +148,7 @@ "addon.files.sitefiles": "Site files", "addon.messageoutput_airnotifier.processorsettingsdesc": "Configure devices", "addon.messages.addcontact": "Add contact", + "addon.messages.addtofavourites": "Star", "addon.messages.blocknoncontacts": "Prevent non-contacts from messaging me", "addon.messages.blockuser": "Block user", "addon.messages.blockuserconfirm": "Are you sure you want to block {{$a}}?", @@ -159,6 +160,8 @@ "addon.messages.contactlistempty": "The contact list is empty", "addon.messages.contactname": "Contact name", "addon.messages.contacts": "Contacts", + "addon.messages.deleteallconfirm": "Are you sure you would like to delete this entire conversation? This will not delete it for other conversation participants.", + "addon.messages.deleteconversation": "Delete conversation", "addon.messages.deletemessage": "Delete message", "addon.messages.deletemessageconfirmation": "Are you sure you want to delete this message? It will only be deleted from your messaging history and will still be viewable by the user who sent or received the message.", "addon.messages.errordeletemessage": "Error while deleting the message.", @@ -181,6 +184,7 @@ "addon.messages.numparticipants": "{{$a}} participants", "addon.messages.removecontact": "Remove contact", "addon.messages.removecontactconfirm": "Contact will be removed from your contacts list.", + "addon.messages.removefromfavourites": "Unstar", "addon.messages.showdeletemessages": "Show delete messages", "addon.messages.type_blocked": "Blocked", "addon.messages.type_offline": "Offline",