From 0a59a420062416b8c4474bf75e2516eb04a552e1 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Wed, 2 Jan 2019 14:34:54 +0100 Subject: [PATCH] MOBILE-2630 messages: Fetch conversations when option is expanded --- .../messages/pages/discussion/discussion.ts | 4 +- .../group-conversations.html | 22 +- .../group-conversations.ts | 269 ++++++++++++------ src/addon/messages/providers/messages.ts | 15 +- 4 files changed, 204 insertions(+), 106 deletions(-) diff --git a/src/addon/messages/pages/discussion/discussion.ts b/src/addon/messages/pages/discussion/discussion.ts index 8de0d7dd0..6b94fe915 100644 --- a/src/addon/messages/pages/discussion/discussion.ts +++ b/src/addon/messages/pages/discussion/discussion.ts @@ -630,7 +630,9 @@ export class AddonMessagesDiscussionPage implements OnDestroy { conversationId: this.conversationId, userId: this.userId, message: this.lastMessage.text, - timecreated: this.lastMessage.timecreated + timecreated: this.lastMessage.timecreated, + isfavourite: this.conversation && this.conversation.isfavourite, + type: this.conversation && this.conversation.type }, this.siteId); // Update navBar links and buttons. diff --git a/src/addon/messages/pages/group-conversations/group-conversations.html b/src/addon/messages/pages/group-conversations/group-conversations.html index ddf0ef5c4..f455f8428 100644 --- a/src/addon/messages/pages/group-conversations/group-conversations.html +++ b/src/addon/messages/pages/group-conversations/group-conversations.html @@ -27,13 +27,13 @@ {{contactRequestsCount}} - + {{ 'core.favourites' | translate }} ({{ favourites.count }}) {{ favourites.unread }} -
+
@@ -41,15 +41,18 @@

{{ 'addon.messages.nofavourites' | translate }}

+ + + - + {{ 'addon.messages.groupconversations' | translate }} ({{ group.count }}) {{ group.unread }} -
+
@@ -57,14 +60,17 @@

{{ 'addon.messages.nogroupconversations' | translate }}

+ + + - + {{ 'addon.messages.individualconversations' | translate }} ({{ individual.count }}) {{ individual.unread }} -
+
@@ -72,6 +78,10 @@

{{ 'addon.messages.noindividualconversations' | translate }}

+ + + + diff --git a/src/addon/messages/pages/group-conversations/group-conversations.ts b/src/addon/messages/pages/group-conversations/group-conversations.ts index 86b78189a..acfa047f7 100644 --- a/src/addon/messages/pages/group-conversations/group-conversations.ts +++ b/src/addon/messages/pages/group-conversations/group-conversations.ts @@ -89,8 +89,16 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { // Update conversations when new message is received. this.newMessagesObserver = eventsProvider.on(AddonMessagesProvider.NEW_MESSAGE_EVENT, (data) => { + // Check if the new message belongs to the option that is currently expanded. + const expandedOption = this.getExpandedOption(), + messageOption = this.getConversationOption(data); + + if (expandedOption != messageOption) { + return; // Message doesn't belong to current list, stop. + } + // Search the conversation to update. - const conversation = this.findConversation(data.conversationId, data.userId); + const conversation = this.findConversation(data.conversationId, data.userId, expandedOption); if (typeof conversation == 'undefined') { // Probably a new conversation, refresh the list. @@ -135,7 +143,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { // Load a discussion if we receive an event to do so. this.openConversationObserver = eventsProvider.on(AddonMessagesProvider.OPEN_CONVERSATION_EVENT, (data) => { if (data.conversationId || data.userId) { - this.gotoConversation(data.conversationId, data.userId, undefined, true); + this.gotoConversation(data.conversationId, data.userId); } }, this.siteId); @@ -183,18 +191,17 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { return; } - const updateConversations = (conversations: any[]): void => { - if (!conversations || conversations.length <= 0) { + const expandedOption = this.getExpandedOption(); + if (expandedOption == this.individual || expandedOption == this.favourites) { + if (!expandedOption.conversations || expandedOption.conversations.length <= 0) { return; } - const conversation = conversations.find((conv) => conv.userid == data.userId); + + const conversation = this.findConversation(undefined, data.userId, expandedOption); if (conversation) { conversation.isblocked = data.userBlocked; } - }; - - updateConversations(this.individual.conversations); - updateConversations(this.favourites.conversations); + } }, this.siteId); } @@ -211,13 +218,10 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { if (!this.conversationId && this.splitviewCtrl.isOn()) { // Load the first conversation. let conversation; + const expandedOption = this.getExpandedOption(); - if (this.favourites.expanded) { - conversation = this.favourites.conversations[0]; - } else if (this.group.expanded) { - conversation = this.group.conversations[0]; - } else if (this.individual.expanded) { - conversation = this.individual.conversations[0]; + if (expandedOption) { + conversation = expandedOption.conversations[0]; } if (conversation) { @@ -230,53 +234,53 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { /** * Fetch conversations. * + * @param {booleam} [refreshUnreadCounts=true] Whether to refresh unread counts. * @return {Promise} Promise resolved when done. */ - protected fetchData(): Promise { + protected fetchData(refreshUnreadCounts: boolean = true): Promise { this.loadingMessage = this.loadingString; - // Load the first conversations of each type. + // Load the amount of conversations and contact requests. 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.fetchConversationCounts()); - promises.push(this.messagesProvider.getUnreadConversationCounts()); // View updated by the event observer. promises.push(this.messagesProvider.getContactRequestsCount()); // View updated by the event observer. - 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. if (this.conversationId) { - // A certain conversation should be opened, expand its option. - const conversation = this.findConversation(this.conversationId); - if (conversation) { - const option = this.getConversationOption(conversation); - this.expandOption(option); + // A certain conversation should be opened. + // We don't know which option it belongs to, so we need to fetch the data for all of them. + const promises = []; - return; - } + promises.push(this.fetchDataForOption(this.favourites, false, refreshUnreadCounts)); + promises.push(this.fetchDataForOption(this.group, false, refreshUnreadCounts)); + promises.push(this.fetchDataForOption(this.individual, false, refreshUnreadCounts)); + + return Promise.all(promises).then(() => { + // All conversations have been loaded, find the one we need to load and expand its option. + const conversation = this.findConversation(this.conversationId); + if (conversation) { + const option = this.getConversationOption(conversation); + + return this.expandOption(option, refreshUnreadCounts); + } else { + // Conversation not found, just open the default option. + this.calculateExpandedStatus(); + + // Now load the data for the expanded option. + return this.fetchDataForExpandedOption(refreshUnreadCounts); + } + }); } // No conversation specified or not found, determine which one should be expanded. - this.favourites.expanded = this.favourites.count != 0; - this.group.expanded = this.favourites.count == 0 && this.group.count != 0; - this.individual.expanded = this.favourites.count == 0 && this.group.count == 0; + this.calculateExpandedStatus(); } + + // Now load the data for the expanded option. + return this.fetchDataForExpandedOption(refreshUnreadCounts); }).catch((error) => { this.domUtils.showErrorModalDefault(error, 'addon.messages.errorwhileretrievingdiscussions', true); }).finally(() => { @@ -284,27 +288,91 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { }); } + /** + * Calculate which option should be expanded initially. + */ + protected calculateExpandedStatus(): void { + this.favourites.expanded = this.favourites.count != 0; + this.group.expanded = this.favourites.count == 0 && this.group.count != 0; + this.individual.expanded = this.favourites.count == 0 && this.group.count == 0; + } + + /** + * Fetch data for the expanded option. + * + * @param {booleam} [refreshUnreadCounts=true] Whether to refresh unread counts. + * @return {Promise} Promise resolved when done. + */ + protected fetchDataForExpandedOption(refreshUnreadCounts: boolean = true): Promise { + const expandedOption = this.getExpandedOption(); + + if (expandedOption) { + return this.fetchDataForOption(expandedOption, false, refreshUnreadCounts); + } else { + // All options are collapsed, update the counts. + const promises = []; + + promises.push(this.fetchConversationCounts()); + if (refreshUnreadCounts) { + promises.push(this.messagesProvider.refreshUnreadConversationCounts()); // View updated by the event observer. + } + + return Promise.all(promises); + } + } + /** * Fetch data for a certain option. * * @param {any} option The option to fetch data for. * @param {boolean} [loadingMore} Whether we are loading more data or just the first ones. + * @param {booleam} [refreshUnreadCounts=true] Whether to refresh unread counts. * @return {Promise} Promise resolved when done. */ - fetchDataForOption(option: any, loadingMore?: boolean): Promise { + fetchDataForOption(option: any, loadingMore?: boolean, refreshUnreadCounts: boolean = true): Promise { option.loadMoreError = false; - const limitFrom = loadingMore ? option.conversations.length : 0; + const limitFrom = loadingMore ? option.conversations.length : 0, + promises = []; + let data, + offlineMessages; - return this.messagesProvider.getConversations(option.type, option.favourites, limitFrom).then((data) => { + // Get the conversations and, if needed, the offline messages. Always try to get the latest data. + promises.push(this.messagesProvider.invalidateConversations().catch(() => { + // Shouldn't happen. + }).then(() => { + return this.messagesProvider.getConversations(option.type, option.favourites, limitFrom); + }).then((result) => { + data = result; + })); + + if (!loadingMore) { + promises.push(this.messagesOffline.getAllMessages().then((data) => { + offlineMessages = data; + })); + + promises.push(this.fetchConversationCounts()); + if (refreshUnreadCounts) { + promises.push(this.messagesProvider.refreshUnreadConversationCounts()); // View updated by the event observer. + } + } + + return Promise.all(promises).then(() => { if (loadingMore) { option.conversations = option.conversations.concat(data.conversations); + option.canLoadMore = data.canLoadMore; } else { option.conversations = data.conversations; + option.canLoadMore = data.canLoadMore; + + if (offlineMessages && offlineMessages.length) { + return this.loadOfflineMessages(option, offlineMessages).then(() => { + // Sort the conversations, the offline messages could affect the order. + option.conversations = this.messagesProvider.sortConversations(option.conversations); + }); + } } - option.unread = 0; // @todo. - option.canLoadMore = data.canLoadMore; }); } @@ -314,7 +382,12 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { * @return {Promise} Promise resolved when done. */ protected fetchConversationCounts(): Promise { - return this.messagesProvider.getConversationCounts().then((counts) => { + // Always try to get the latest data. + return this.messagesProvider.invalidateConversationCounts().catch(() => { + // Shouldn't happen. + }).then(() => { + return this.messagesProvider.getConversationCounts(); + }).then((counts) => { this.favourites.count = counts.favourites; this.individual.count = counts.individual; this.group.count = counts.group; @@ -326,25 +399,42 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { * * @param {number} conversationId The conversation ID to search. * @param {number} userId User ID to search (if no conversationId). + * @param {any} [option] The option to search in. If not defined, search in all options. * @return {any} Conversation. */ - protected findConversation(conversationId: number, userId?: number): any { + protected findConversation(conversationId: number, userId?: number, option?: any): any { if (conversationId) { - const conversations = (this.favourites.conversations || []).concat(this.group.conversations || []) - .concat(this.individual.conversations || []); + const conversations = option ? (option.conversations || []) : ((this.favourites.conversations || []) + .concat(this.group.conversations || []).concat(this.individual.conversations || [])); return conversations.find((conv) => { return conv.id == conversationId; }); } - const conversations = (this.favourites.conversations || []).concat(this.individual.conversations || []); + const conversations = option ? (option.conversations || []) : + ((this.favourites.conversations || []).concat(this.individual.conversations || [])); return conversations.find((conv) => { return conv.userid == userId; }); } + /** + * Get the option that is currently expanded, undefined if they are all collapsed. + * + * @return {any} Option currently expanded. + */ + protected getExpandedOption(): any { + if (this.favourites.expanded) { + return this.favourites; + } else if (this.group.expanded) { + return this.group; + } else if (this.individual.expanded) { + return this.individual; + } + } + /** * Navigate to contacts view. */ @@ -358,9 +448,8 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { * @param {number} conversationId Conversation Id to load. * @param {number} userId User of the conversation. Only if there is no conversationId. * @param {number} [messageId] Message to scroll after loading the discussion. Used when searching. - * @param {boolean} [scrollToConversation] Whether to scroll to the conversation. */ - gotoConversation(conversationId: number, userId?: number, messageId?: number, scrollToConversation?: boolean): void { + gotoConversation(conversationId: number, userId?: number, messageId?: number): void { this.selectedConversationId = conversationId; this.selectedUserId = userId; @@ -372,23 +461,6 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { params['message'] = messageId; } this.splitviewCtrl.push('AddonMessagesDiscussionPage', params); - - if (scrollToConversation) { - // Search the conversation. - const conversation = this.findConversation(conversationId, userId); - if (conversation) { - // First expand the option if it isn't expanded. - const option = this.getConversationOption(conversation); - this.expandOption(option); - - // Wait for the view to expand the option. - setTimeout(() => { - // Now scroll to the conversation. - this.domUtils.scrollToElementBySelector(this.content, '#addon-message-conversation-' + - (conversation.id ? conversation.id : 'user-' + conversation.userid)); - }); - } - } } /** @@ -417,16 +489,17 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { /** * Load offline messages into the conversations. * + * @param {any} option The option where the messages should be loaded. * @param {any[]} messages Offline messages. * @return {Promise} Promise resolved when done. */ - protected loadOfflineMessages(messages: any[]): Promise { + protected loadOfflineMessages(option: any, messages: any[]): Promise { const promises = []; messages.forEach((message) => { if (message.conversationid) { - // It's an existing conversation. Search it. - let conversation = this.findConversation(message.conversationid); + // It's an existing conversation. Search it in the current option. + let conversation = this.findConversation(message.conversationid, undefined, option); if (conversation) { // Check if it's the last message. Offline messages are considered more recent than sent messages. @@ -436,16 +509,19 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { this.addLastOfflineMessage(conversation, message); } } else { - // Conversation not found, it's probably an old one. Add it. + // Conversation not found, it could be an old one or the message could belong to another option. conversation = message.conversation || {}; conversation.id = message.conversationid; - this.addLastOfflineMessage(conversation, message); - this.addOfflineConversation(conversation); + if (this.getConversationOption(conversation) == option) { + // Message belongs to current option, add the conversation. + 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.findConversation(undefined, message.touserid); + } else if (option == this.individual) { + // It's a new conversation. Check if we already created it (there is more than one message for the same user). + const conversation = this.findConversation(undefined, message.touserid, option); message.text = message.smallmessage; @@ -455,7 +531,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { this.addLastOfflineMessage(conversation, message); } } else { - // Get the user data and create a new conversation. + // Get the user data and create a new conversation if it belongs to the current option. promises.push(this.userProvider.getProfile(message.touserid, undefined, true).catch(() => { // User not found. }).then((user) => { @@ -523,18 +599,13 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { * @return {Promise} Promise resolved when done. */ refreshData(refresher?: any, refreshUnreadCounts: boolean = true): Promise { + // Don't invalidate conversations and so, they always try to get latest data. const promises = [ - this.messagesProvider.invalidateConversations(), - this.messagesProvider.invalidateConversationCounts(), this.messagesProvider.invalidateContactRequestsCountCache() ]; - if (refreshUnreadCounts) { - promises.push(this.messagesProvider.invalidateUnreadConversationCounts()); - } - return this.utils.allPromises(promises).finally(() => { - return this.fetchData().finally(() => { + return this.fetchData(refreshUnreadCounts).finally(() => { if (refresher) { refresher.complete(); } @@ -552,7 +623,9 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { // Already expanded, close it. option.expanded = false; } else { - this.expandOption(option); + this.expandOption(option).catch((error) => { + this.domUtils.showErrorModalDefault(error, 'addon.messages.errorwhileretrievingdiscussions', true); + }); } } @@ -560,13 +633,25 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { * Expand a certain option. * * @param {any} option The option to expand. + * @param {booleam} [refreshUnreadCounts=true] Whether to refresh unread counts. + * @return {Promise} Promise resolved when done. */ - protected expandOption(option: any): void { + protected expandOption(option: any, refreshUnreadCounts: boolean = true): Promise { // Collapse all and expand the right one. this.favourites.expanded = false; this.group.expanded = false; this.individual.expanded = false; + option.expanded = true; + option.loading = true; + + return this.fetchDataForOption(option, false, refreshUnreadCounts).catch((error) => { + option.expanded = false; + + return Promise.reject(error); + }).finally(() => { + option.loading = false; + }); } /** diff --git a/src/addon/messages/providers/messages.ts b/src/addon/messages/providers/messages.ts index 7b5cfe940..5b49d149c 100644 --- a/src/addon/messages/providers/messages.ts +++ b/src/addon/messages/providers/messages.ts @@ -917,10 +917,10 @@ export class AddonMessagesProvider { * Get the discussions of a certain user. This function is used in Moodle sites higher than 3.6. * If the site is older than 3.6, please use getDiscussions. * - * @param {number} [limitFrom=0] The offset to start at. * @param {number} [type] Filter by type. * @param {boolean} [favourites] Whether to restrict the results to contain NO favourite conversations (false), ONLY favourite * conversation (true), or ignore any restriction altogether (undefined or null). + * @param {number} [limitFrom=0] The offset to start at. * @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 with the conversations. @@ -966,13 +966,15 @@ export class AddonMessagesProvider { * Get conversation counts by type. * * @param {string} [siteId] Site ID. If not defined, use current site. - * @return {Promise} Promise resolved with favourite, individual and group conversation counts. + * @return {Promise} Promise resolved with favourite, individual and + * group conversation counts. * @since 3.6 */ getConversationCounts(siteId?: string): Promise<{favourites: number, individual: number, group: number}> { + return this.sitesProvider.getSite(siteId).then((site) => { const preSets = { - cacheKey: this.getCacheKeyForConversationCounts(), + cacheKey: this.getCacheKeyForConversationCounts() }; return site.read('core_message_get_conversation_counts', {}, preSets).then((result) => { @@ -1350,7 +1352,7 @@ export class AddonMessagesProvider { if (this.isGroupMessagingEnabled()) { // @since 3.6 const preSets = { - cacheKey: this.getCacheKeyForUnreadConversationCounts(), + cacheKey: this.getCacheKeyForUnreadConversationCounts() }; promise = site.read('core_message_get_unread_conversation_counts', {}, preSets).then((result) => { @@ -1914,12 +1916,11 @@ export class AddonMessagesProvider { * Refresh unread conversation counts and trigger event. * * @param {string} [siteId] Site ID. If not defined, use current site. - * @param {number} [conversationId] ID of the conversation that was read. - * @param {number} [userId] ID of ther other user of the conversation that was read. * @return {Promise} Resolved with the unread favourite, individual and group conversation counts. */ - refreshUnreadConversationCounts(siteId?: string, conversationId?: number, userId?: number): + refreshUnreadConversationCounts(siteId?: string): Promise<{favourites: number, individual: number, group: number, orMore?: boolean}> { + siteId = siteId || this.sitesProvider.getCurrentSiteId(); return this.invalidateUnreadConversationCounts(siteId).then(() => {