From f22f33dad4821ab41f49eee4942b1cf61f2258d4 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Wed, 28 Nov 2018 11:25:25 +0100 Subject: [PATCH 1/3] MOBILE-2631 message: Allow adding/removing favourites --- scripts/langindex.json | 2 + src/addon/messages/lang/en.json | 2 + .../messages/pages/discussion/discussion.html | 6 +- .../messages/pages/discussion/discussion.ts | 116 ++++++++++++------ .../group-conversations.ts | 15 ++- src/addon/messages/providers/messages.ts | 50 +++++++- src/assets/lang/en.json | 2 + 7 files changed, 150 insertions(+), 43 deletions(-) diff --git a/scripts/langindex.json b/scripts/langindex.json index 9f1566add..4f57c5479 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", @@ -181,6 +182,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..b494638c7 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}}?", @@ -33,6 +34,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..1dbde4d4c 100644 --- a/src/addon/messages/pages/discussion/discussion.html +++ b/src/addon/messages/pages/discussion/discussion.html @@ -3,14 +3,16 @@ + - - + + + diff --git a/src/addon/messages/pages/discussion/discussion.ts b/src/addon/messages/pages/discussion/discussion.ts index 7cbe7df9f..13c0ec5d7 100644 --- a/src/addon/messages/pages/discussion/discussion.ts +++ b/src/addon/messages/pages/discussion/discussion.ts @@ -75,6 +75,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy { groupMessagingEnabled: boolean; isGroup = false; members: any = {}; // Members that wrote a message, indexed by ID. + favouriteIcon = 'fa-star'; constructor(private eventsProvider: CoreEventsProvider, sitesProvider: CoreSitesProvider, navParams: NavParams, private userProvider: CoreUserProvider, private navCtrl: NavController, private messagesSync: AddonMessagesSyncProvider, @@ -332,51 +333,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 +957,33 @@ 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(); + }); + } + /** * 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.ts b/src/addon/messages/providers/messages.ts index eeae588bb..8e160d4d4 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'; @@ -1114,7 +1115,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 +1698,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..e42dcf18d 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}}?", @@ -181,6 +182,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", From 3ea1040cd99d875694cf27c53fe0205ea885a341 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Wed, 28 Nov 2018 13:47:16 +0100 Subject: [PATCH 2/3] MOBILE-2631 message: Allow deleting conversations --- scripts/langindex.json | 2 + src/addon/messages/lang/en.json | 2 + .../messages/pages/discussion/discussion.html | 1 + .../messages/pages/discussion/discussion.ts | 28 ++++++++++++ .../messages/providers/messages-offline.ts | 15 +++++++ src/addon/messages/providers/messages.ts | 43 +++++++++++++++++++ src/assets/lang/en.json | 2 + 7 files changed, 93 insertions(+) diff --git a/scripts/langindex.json b/scripts/langindex.json index 4f57c5479..392af6ed2 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -160,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", diff --git a/src/addon/messages/lang/en.json b/src/addon/messages/lang/en.json index b494638c7..9bfb0f29b 100644 --- a/src/addon/messages/lang/en.json +++ b/src/addon/messages/lang/en.json @@ -12,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.", diff --git a/src/addon/messages/pages/discussion/discussion.html b/src/addon/messages/pages/discussion/discussion.html index 1dbde4d4c..25251f0c3 100644 --- a/src/addon/messages/pages/discussion/discussion.html +++ b/src/addon/messages/pages/discussion/discussion.html @@ -13,6 +13,7 @@ + diff --git a/src/addon/messages/pages/discussion/discussion.ts b/src/addon/messages/pages/discussion/discussion.ts index 13c0ec5d7..cd1d1d967 100644 --- a/src/addon/messages/pages/discussion/discussion.ts +++ b/src/addon/messages/pages/discussion/discussion.ts @@ -76,6 +76,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy { 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, @@ -984,6 +985,33 @@ export class AddonMessagesDiscussionPage implements OnDestroy { }); } + /** + * 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/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 8e160d4d4..e6f859509 100644 --- a/src/addon/messages/providers/messages.ts +++ b/src/addon/messages/providers/messages.ts @@ -106,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). * diff --git a/src/assets/lang/en.json b/src/assets/lang/en.json index e42dcf18d..45d914929 100644 --- a/src/assets/lang/en.json +++ b/src/assets/lang/en.json @@ -160,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.", From 94f6051db70379d39df06e0d3844c53fbc3d1028 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Wed, 28 Nov 2018 15:50:32 +0100 Subject: [PATCH 3/3] MOBILE-2631 message: Do not display footer while loading --- src/addon/messages/pages/discussion/discussion.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/addon/messages/pages/discussion/discussion.html b/src/addon/messages/pages/discussion/discussion.html index 25251f0c3..ce5d58787 100644 --- a/src/addon/messages/pages/discussion/discussion.html +++ b/src/addon/messages/pages/discussion/discussion.html @@ -57,7 +57,7 @@ - +