From 887ef9416d73903756d204f4f83487d4ec1d9d9f Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Thu, 15 Nov 2018 10:34:13 +0100 Subject: [PATCH] MOBILE-2729 messages: First implementation of conversations view --- scripts/langindex.json | 6 + src/addon/messages/lang/en.json | 7 +- .../group-conversations.html | 119 ++++++ .../group-conversations.module.ts | 35 ++ .../group-conversations.scss | 17 + .../group-conversations.ts | 367 ++++++++++++++++++ .../messages/providers/mainmenu-handler.ts | 3 + src/addon/messages/providers/messages.ts | 136 ++++++- src/assets/lang/en.json | 6 + src/lang/en.json | 1 + 10 files changed, 694 insertions(+), 3 deletions(-) create mode 100644 src/addon/messages/pages/group-conversations/group-conversations.html create mode 100644 src/addon/messages/pages/group-conversations/group-conversations.module.ts create mode 100644 src/addon/messages/pages/group-conversations/group-conversations.scss create mode 100644 src/addon/messages/pages/group-conversations/group-conversations.ts diff --git a/scripts/langindex.json b/scripts/langindex.json index 6021f016c..e9720fd30 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -155,6 +155,7 @@ "addon.messages.contactableprivacy_coursemember": "message", "addon.messages.contactableprivacy_onlycontacts": "message", "addon.messages.contactableprivacy_site": "message", + "addon.messages.contactblocked": "message", "addon.messages.contactlistempty": "local_moodlemobileapp", "addon.messages.contactname": "local_moodlemobileapp", "addon.messages.contacts": "message", @@ -164,12 +165,15 @@ "addon.messages.errorwhileretrievingcontacts": "local_moodlemobileapp", "addon.messages.errorwhileretrievingdiscussions": "local_moodlemobileapp", "addon.messages.errorwhileretrievingmessages": "local_moodlemobileapp", + "addon.messages.groupmessages": "message", "addon.messages.message": "message", "addon.messages.messagenotsent": "local_moodlemobileapp", "addon.messages.messagepreferences": "message", "addon.messages.messages": "message", "addon.messages.newmessage": "message", "addon.messages.newmessages": "local_moodlemobileapp", + "addon.messages.nofavourites": "message", + "addon.messages.nogroupmessages": "message", "addon.messages.nomessages": "message", "addon.messages.nousersfound": "local_moodlemobileapp", "addon.messages.removecontact": "message", @@ -182,6 +186,7 @@ "addon.messages.unblockuser": "message", "addon.messages.unblockuserconfirm": "message", "addon.messages.warningmessagenotsent": "local_moodlemobileapp", + "addon.messages.you": "message", "addon.mod_assign.acceptsubmissionstatement": "local_moodlemobileapp", "addon.mod_assign.addattempt": "assign", "addon.mod_assign.addnewattempt": "assign", @@ -1271,6 +1276,7 @@ "core.errorsync": "local_moodlemobileapp", "core.errorsyncblocked": "local_moodlemobileapp", "core.explanationdigitalminor": "moodle", + "core.favourites": "moodle", "core.filename": "repository", "core.filenameexist": "local_moodlemobileapp", "core.fileuploader.addfiletext": "repository", diff --git a/src/addon/messages/lang/en.json b/src/addon/messages/lang/en.json index 0edcae15a..ed076fb7d 100644 --- a/src/addon/messages/lang/en.json +++ b/src/addon/messages/lang/en.json @@ -7,6 +7,7 @@ "contactableprivacy_coursemember": "My contacts and anyone in my courses", "contactableprivacy_onlycontacts": "My contacts only", "contactableprivacy_site": "Anyone on the site", + "contactblocked": "Contact blocked", "contactlistempty": "The contact list is empty", "contactname": "Contact name", "contacts": "Contacts", @@ -16,12 +17,15 @@ "errorwhileretrievingcontacts": "Error while retrieving contacts from the server.", "errorwhileretrievingdiscussions": "Error while retrieving discussions from the server.", "errorwhileretrievingmessages": "Error while retrieving messages from the server.", + "groupmessages": "Group messages", "messagenotsent": "The message was not sent. Please try again later.", "message": "Message", "messagepreferences": "Message preferences", "messages": "Messages", "newmessage": "New message", "newmessages": "New messages", + "nofavourites": "No favourites", + "nogroupmessages": "No group messages", "nomessages": "No messages", "nousersfound": "No users found", "removecontact": "Remove contact", @@ -33,5 +37,6 @@ "type_strangers": "Others", "unblockuser": "Unblock user", "unblockuserconfirm": "Are you sure you want to unblock {{$a}}?", - "warningmessagenotsent": "Couldn't send message(s) to user {{user}}. {{error}}" + "warningmessagenotsent": "Couldn't send message(s) to user {{user}}. {{error}}", + "you": "You:" } \ No newline at end of file diff --git a/src/addon/messages/pages/group-conversations/group-conversations.html b/src/addon/messages/pages/group-conversations/group-conversations.html new file mode 100644 index 000000000..c92ff7845 --- /dev/null +++ b/src/addon/messages/pages/group-conversations/group-conversations.html @@ -0,0 +1,119 @@ + + + {{ 'addon.messages.messages' | translate }} + + + + + + + + + + + + + + + + + + + +

{{ 'core.searchresults' | translate }}

+ {{ search.results.length }} +
+ + + + + +

+

+
+
+ + + + + + + + {{ 'core.favourites' | translate }} ({{ favourites.count }}) + + +
+ + + + +

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

+
+
+ + + + + + {{ 'addon.messages.groupmessages' | translate }} ({{ group.count }}) + + +
+ + + + +

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

+
+
+ + + + + {{ 'addon.messages.messages' | translate }} ({{ individual.count }}) + + +
+ + + + +

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

+
+
+
+ + + +
+
+
+ + + + + + + + +

+

+ + +

+ + {{ conversation.unreadcount }} + {{conversation.lastmessagedate | coreDateDayOrTime}} + +

+

+

+ {{ 'addon.messages.you' | translate }} +

+
+
diff --git a/src/addon/messages/pages/group-conversations/group-conversations.module.ts b/src/addon/messages/pages/group-conversations/group-conversations.module.ts new file mode 100644 index 000000000..c3eb2d407 --- /dev/null +++ b/src/addon/messages/pages/group-conversations/group-conversations.module.ts @@ -0,0 +1,35 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { NgModule } from '@angular/core'; +import { IonicPageModule } from 'ionic-angular'; +import { TranslateModule } from '@ngx-translate/core'; +import { AddonMessagesGroupConversationsPage } from './group-conversations'; +import { CoreComponentsModule } from '@components/components.module'; +import { CoreDirectivesModule } from '@directives/directives.module'; +import { CorePipesModule } from '@pipes/pipes.module'; + +@NgModule({ + declarations: [ + AddonMessagesGroupConversationsPage, + ], + imports: [ + CoreComponentsModule, + CoreDirectivesModule, + CorePipesModule, + IonicPageModule.forChild(AddonMessagesGroupConversationsPage), + TranslateModule.forChild() + ], +}) +export class AddonMessagesGroupConversationsPageModule {} diff --git a/src/addon/messages/pages/group-conversations/group-conversations.scss b/src/addon/messages/pages/group-conversations/group-conversations.scss new file mode 100644 index 000000000..26a039907 --- /dev/null +++ b/src/addon/messages/pages/group-conversations/group-conversations.scss @@ -0,0 +1,17 @@ +ion-app.app-root page-addon-messages-group-conversations { + h2 { + display: flex; + justify-content: space-between; + + .note { + margin: 0; + align-self: flex-end; + display: inline-flex; + font-size: initial; + } + } + + core-format-text.addon-message-last-message { + display: inline; + } +} diff --git a/src/addon/messages/pages/group-conversations/group-conversations.ts b/src/addon/messages/pages/group-conversations/group-conversations.ts new file mode 100644 index 000000000..091df7e7a --- /dev/null +++ b/src/addon/messages/pages/group-conversations/group-conversations.ts @@ -0,0 +1,367 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core'; +import { IonicPage, Platform, NavParams } from 'ionic-angular'; +import { TranslateService } from '@ngx-translate/core'; +import { CoreEventsProvider } from '@providers/events'; +import { CoreSitesProvider } from '@providers/sites'; +import { AddonMessagesProvider } from '../../providers/messages'; +import { CoreDomUtilsProvider } from '@providers/utils/dom'; +import { CoreUtilsProvider } from '@providers/utils/utils'; +import { CoreAppProvider } from '@providers/app'; +import { AddonPushNotificationsDelegate } from '@addon/pushnotifications/providers/delegate'; +import { CoreSplitViewComponent } from '@components/split-view/split-view'; + +/** + * Page that displays the list of conversations, including group conversations. + */ +@IonicPage({ segment: 'addon-messages-group-conversations' }) +@Component({ + selector: 'page-addon-messages-group-conversations', + templateUrl: 'group-conversations.html', +}) +export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { + @ViewChild(CoreSplitViewComponent) splitviewCtrl: CoreSplitViewComponent; + + loaded = false; + loadingMessage: string; + selectedConversation: number; + search = { + enabled: false, + showResults: false, + results: [], + loading: '', + text: '' + }; + favourites: any = { + type: null, + favourites: true + }; + group: any = { + type: AddonMessagesProvider.MESSAGE_CONVERSATION_TYPE_GROUP, + favourites: false + }; + individual: any = { + type: AddonMessagesProvider.MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, + favourites: false + }; + + protected loadingString: string; + protected siteId: string; + protected currentUserId: number; + protected conversationId: number; + protected newMessagesObserver: any; + protected pushObserver: any; + protected appResumeSubscription: any; + protected readChangedObserver: any; + protected cronObserver: any; + + constructor(private eventsProvider: CoreEventsProvider, sitesProvider: CoreSitesProvider, translate: TranslateService, + private messagesProvider: AddonMessagesProvider, private domUtils: CoreDomUtilsProvider, navParams: NavParams, + private appProvider: CoreAppProvider, platform: Platform, utils: CoreUtilsProvider, + pushNotificationsDelegate: AddonPushNotificationsDelegate) { + + this.search.loading = translate.instant('core.searching'); + this.loadingString = translate.instant('core.loading'); + this.siteId = sitesProvider.getCurrentSiteId(); + this.currentUserId = sitesProvider.getCurrentSiteUserId(); + this.conversationId = navParams.get('conversationId') || false; + + // Update conversations when new message is received. + this.newMessagesObserver = eventsProvider.on(AddonMessagesProvider.NEW_MESSAGE_EVENT, (data) => { + if (data.conversationId) { + // Search the conversation to update. + const conversation = this.findConversation(data.conversationId); + + if (typeof conversation == 'undefined') { + // Probably a new conversation, refresh the list. + this.loaded = false; + this.refreshData().finally(() => { + this.loaded = true; + }); + } else { + // An existing conversation has a new message, update the last message. + conversation.lastmessage = data.message; + conversation.lastmessagedate = data.timecreated; + } + } + }, this.siteId); + + // Update discussions 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. + conversation.unreadcount = 0; + + // Discussions changed, invalidate them. + this.messagesProvider.invalidateConversations(); + } + } + }, this.siteId); + + // Update discussions when cron read is executed. + this.cronObserver = eventsProvider.on(AddonMessagesProvider.READ_CRON_EVENT, (data) => { + this.refreshData(); + }, this.siteId); + + // Refresh the view when the app is resumed. + this.appResumeSubscription = platform.resume.subscribe(() => { + if (!this.loaded) { + return; + } + this.loaded = false; + this.refreshData().finally(() => { + this.loaded = true; + }); + }); + + // 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. + if (utils.isFalseOrZero(notification.notif) && notification.site == this.siteId) { + this.refreshData(); + } + }); + } + + /** + * Component loaded. + */ + ngOnInit(): void { + if (this.conversationId) { + // There is a discussion to load, open the discussion in a new state. + this.gotoConversation(this.conversationId, null); + } + + this.fetchData().then(() => { + if (!this.conversationId && this.splitviewCtrl.isOn()) { + // Load the first conversation. + let conversation; + + 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 (conversation) { + this.gotoConversation(conversation.id, conversation.userid); + } + } + }); + } + + /** + * Fetch conversations. + * + * @return {Promise} Promise resolved when done. + */ + protected fetchData(): Promise { + this.loadingMessage = this.loadingString; + this.search.enabled = this.messagesProvider.isSearchMessagesEnabled(); + + // Load the first conversations of each type. + const promises = []; + + promises.push(this.fetchDataForOption(this.favourites, false)); + promises.push(this.fetchDataForOption(this.group, false)); + promises.push(this.fetchDataForOption(this.individual, false)); + + return Promise.all(promises).then(() => { + if (typeof this.favourites.expanded == 'undefined') { + // The expanded status hasn't been initialized. Do it now. + 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; + } + }).catch((error) => { + this.domUtils.showErrorModalDefault(error, 'addon.messages.errorwhileretrievingdiscussions', true); + }).finally(() => { + this.loaded = true; + }); + } + + /** + * 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. + * @return {Promise} Promise resolved when done. + */ + fetchDataForOption(option: any, loadingMore?: boolean): Promise { + const limitFrom = loadingMore ? option.conversations.length : 0; + + return this.messagesProvider.getConversations(option.type, option.favourites, limitFrom).then((data) => { + if (loadingMore) { + option.conversations = option.conversations.concat(data.conversations); + } else { + option.count = data.canLoadMore ? AddonMessagesProvider.LIMIT_MESSAGES + '+' : data.conversations.length; + option.conversations = data.conversations; + } + + option.unread = 0; // @todo. + option.canLoadMore = data.canLoadMore; + }); + } + + /** + * Find a conversation in the list of loaded conversations. + * + * @param {number} conversationId The conversation ID to search. + * @return {any} Conversation. + */ + protected findConversation(conversationId: number): any { + const conversations = (this.favourites.conversations || []).concat(this.group.conversations || []) + .concat(this.individual.conversations || []); + + return conversations.find((conv) => { + return conv.id == conversationId; + }); + } + + /** + * Navigate to contacts view. + */ + gotoContacts(): void { + this.splitviewCtrl.getMasterNav().push('AddonMessagesContactsPage'); + } + + /** + * Navigate to a particular conversation. + * + * @param {number} conversationId Conversation Id to load. + * @param {number} userId User of the conversation. @todo This will probably be removed when group messaging is fully supported. + * @param {number} [messageId] Message to scroll after loading the discussion. Used when searching. + */ + gotoConversation(conversationId: number, userId: number, messageId?: number): void { + this.selectedConversation = conversationId; + + const params = { + userId: userId + }; + if (messageId) { + params['message'] = messageId; + } + this.splitviewCtrl.push('AddonMessagesDiscussionPage', params); + } + + /** + * Navigate to message settings. + */ + gotoSettings(): void { + this.splitviewCtrl.push('AddonMessagesSettingsPage'); + } + + /** + * Function to load more conversations. + * + * @param {any} option The option to fetch data for. + * @param {any} [infiniteComplete] Infinite scroll complete function. Only used from core-infinite-loading. + * @return {Promise} Resolved when done. + */ + loadMoreConversations(option: any, infiniteComplete?: any): Promise { + return this.fetchDataForOption(option, true).catch((error) => { + this.domUtils.showErrorModalDefault(error, 'addon.messages.errorwhileretrievingdiscussions', true); + option.canLoadMore = false; + }).finally(() => { + infiniteComplete && infiniteComplete(); + }); + } + + /** + * Refresh the data. + * + * @param {any} [refresher] Refresher. + * @return {Promise} Promise resolved when done. + */ + refreshData(refresher?: any): Promise { + return this.messagesProvider.invalidateConversations().then(() => { + return this.fetchData().finally(() => { + if (refresher) { + // Actions to take if refresh comes from the user. + this.eventsProvider.trigger(AddonMessagesProvider.READ_CHANGED_EVENT, undefined, this.siteId); + refresher.complete(); + } + }); + }); + } + + /** + * Toogle the visibility of an option (expand/collapse). + * + * @param {any} option The option to expand/collapse. + */ + toggle(option: any): void { + if (option.expanded) { + // Already expanded, close it. + option.expanded = false; + } else { + // Collapse all and expand the clicked one. + this.favourites.expanded = false; + this.group.expanded = false; + this.individual.expanded = false; + option.expanded = true; + } + } + + /** + * Clear search and show conversations again. + */ + clearSearch(): void { + this.loaded = false; + this.search.showResults = false; + this.search.text = ''; // Reset searched string. + this.fetchData().finally(() => { + this.loaded = true; + }); + } + + /** + * Search messages cotaining text. + * + * @param {string} query Text to search for. + * @return {Promise} Resolved when done. + */ + searchMessage(query: string): Promise { + this.appProvider.closeKeyboard(); + this.loaded = false; + this.loadingMessage = this.search.loading; + + return this.messagesProvider.searchMessages(query).then((searchResults) => { + this.search.showResults = true; + this.search.results = searchResults; + }).catch((error) => { + this.domUtils.showErrorModalDefault(error, 'addon.messages.errorwhileretrievingmessages', true); + }).finally(() => { + this.loaded = true; + }); + } + + /** + * Page destroyed. + */ + ngOnDestroy(): void { + this.newMessagesObserver && this.newMessagesObserver.unsubscribe(); + this.appResumeSubscription && this.appResumeSubscription.unsubscribe(); + this.pushObserver && this.pushObserver.unsubscribe(); + this.readChangedObserver && this.readChangedObserver.off(); + this.cronObserver && this.cronObserver.off(); + } +} diff --git a/src/addon/messages/providers/mainmenu-handler.ts b/src/addon/messages/providers/mainmenu-handler.ts index 2fde797bf..8ba2fedbd 100644 --- a/src/addon/messages/providers/mainmenu-handler.ts +++ b/src/addon/messages/providers/mainmenu-handler.ts @@ -90,6 +90,9 @@ export class AddonMessagesMainMenuHandler implements CoreMainMenuHandler, CoreCr * @return {CoreMainMenuHandlerToDisplay} Data needed to render the handler. */ getDisplayData(): CoreMainMenuHandlerToDisplay { + this.handler.page = this.messagesProvider.isGroupMessagingEnabled() ? + 'AddonMessagesGroupConversationsPage' : 'AddonMessagesIndexPage'; + if (this.handler.loading) { this.updateBadge(); } diff --git a/src/addon/messages/providers/messages.ts b/src/addon/messages/providers/messages.ts index 20e2db394..b8df8c02e 100644 --- a/src/addon/messages/providers/messages.ts +++ b/src/addon/messages/providers/messages.ts @@ -28,8 +28,8 @@ import { CoreEmulatorHelperProvider } from '@core/emulator/providers/helper'; @Injectable() export class AddonMessagesProvider { protected ROOT_CACHE_KEY = 'mmaMessages:'; - protected LIMIT_MESSAGES = 50; protected LIMIT_SEARCH_MESSAGES = 50; + protected LIMIT_MESSAGES = AddonMessagesProvider.LIMIT_MESSAGES; static NEW_MESSAGE_EVENT = 'addon_messages_new_message_event'; static READ_CHANGED_EVENT = 'addon_messages_read_changed_event'; static READ_CRON_EVENT = 'addon_messages_read_cron_event'; @@ -40,6 +40,9 @@ export class AddonMessagesProvider { static MESSAGE_PRIVACY_COURSEMEMBER = 0; // Privacy setting for being messaged by anyone within courses user is member of. static MESSAGE_PRIVACY_ONLYCONTACTS = 1; // Privacy setting for being messaged only by contacts. static MESSAGE_PRIVACY_SITE = 2; // Privacy setting for being messaged by anyone on the site. + static MESSAGE_CONVERSATION_TYPE_INDIVIDUAL = 1; // An individual conversation. + static MESSAGE_CONVERSATION_TYPE_GROUP = 2; // A group conversation. + static LIMIT_MESSAGES = 50; protected logger; @@ -184,6 +187,37 @@ export class AddonMessagesProvider { return this.ROOT_CACHE_KEY + 'discussions'; } + /** + * Get cache key for get conversations. + * + * @param {number} userId User ID. + * @param {number} [type] Filter by type. + * @param {boolean} [favourites] Filter favourites. + * @return {string} Cache key. + */ + protected getCacheKeyForConversations(userId: number, type?: number, favourites?: boolean): string { + return this.getCommonCacheKeyForUserConversations(userId) + ':' + type + ':' + favourites; + } + + /** + * Get common cache key for get user conversations. + * + * @param {number} userId User ID. + * @return {string} Cache key. + */ + protected getCommonCacheKeyForUserConversations(userId: number): string { + return this.getRootCacheKeyForConversations() + userId; + } + + /** + * Get root cache key for get conversations. + * + * @return {string} Cache key. + */ + protected getRootCacheKeyForConversations(): string { + return this.ROOT_CACHE_KEY + 'conversations:'; + } + /** * Get all the contacts of the current user. * @@ -263,6 +297,78 @@ 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 {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. + */ + getConversations(type?: number, favourites?: boolean, limitFrom: number = 0, siteId?: string, userId?: number) + : Promise<{conversations: any[], canLoadMore: boolean}> { + + return this.sitesProvider.getSite(siteId).then((site) => { + userId = userId || site.getUserId(); + + const preSets = { + cacheKey: this.getCacheKeyForConversations(userId, type, favourites) + }, + params: any = { + userid: userId, + limitfrom: limitFrom, + limitnum: this.LIMIT_MESSAGES + 1, + }; + + if (typeof type != 'undefined' && type != null) { + params.type = type; + } + if (typeof favourites != 'undefined' && favourites != null) { + params.favourites = favourites ? 1 : 0; + } + + return site.read('core_message_get_conversations', params, preSets).then((response) => { + // Format the conversations, adding some calculated fields. + const conversations = response.conversations.map((conversation) => { + const numMessages = conversation.messages.length, + lastMessage = numMessages ? conversation.messages[numMessages - 1] : null; + + conversation.lastmessage = lastMessage ? lastMessage.text : null; + conversation.lastmessagedate = lastMessage ? lastMessage.timecreated : null; + conversation.sentfromcurrentuser = lastMessage ? lastMessage.useridfrom == userId : null; + + if (conversation.type == AddonMessagesProvider.MESSAGE_CONVERSATION_TYPE_INDIVIDUAL) { + const otherUser = conversation.members.reduce((carry, member) => { + if (!carry && member.id != userId) { + carry = member; + } + + return carry; + }, null); + + conversation.name = conversation.name ? conversation.name : otherUser.fullname; + conversation.imageurl = conversation.imageurl ? conversation.imageurl : otherUser.profileimageurl; + conversation.userid = otherUser.id; + conversation.showonlinestatus = otherUser.showonlinestatus; + conversation.isonline = otherUser.isonline; + conversation.isblocked = otherUser.isblocked; + } + + return conversation; + }); + + return { + conversations: conversations, + canLoadMore: response.conversations.length > this.LIMIT_MESSAGES + }; + }); + }); + } + /** * Return the current user's discussion with another user. * @@ -346,7 +452,8 @@ export class AddonMessagesProvider { } /** - * Get the discussions of the current user. + * Get the discussions of the current user. This function is used in Moodle sites older than 3.6. + * If the site is 3.6 or higher, please use getConversations. * * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Resolved with an object where the keys are the user ID of the other user. @@ -705,6 +812,21 @@ export class AddonMessagesProvider { }); } + /** + * Invalidate contacts cache. + * + * @param {string} [siteId] Site ID. If not defined, current site. + * @param {number} [userId] User ID. If not defined, current user in the site. + * @return {Promise} Resolved when done. + */ + invalidateConversations(siteId?: string, userId?: number): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + userId = userId || site.getUserId(); + + return site.invalidateWsCacheForKeyStartingWith(this.getCommonCacheKeyForUserConversations(userId)); + }); + } + /** * Invalidate discussion cache. * @@ -788,6 +910,16 @@ export class AddonMessagesProvider { }); } + /** + * Returns whether or not group messaging is supported. + * + * @return {boolean} If related WS is avalaible on current site. + * @since 3.6 + */ + isGroupMessagingEnabled(): boolean { + return this.sitesProvider.wsAvailableInCurrentSite('core_message_get_conversations'); + } + /** * Returns whether or not we can mark all messages as read. * diff --git a/src/assets/lang/en.json b/src/assets/lang/en.json index 0ad6c06b7..591747ca6 100644 --- a/src/assets/lang/en.json +++ b/src/assets/lang/en.json @@ -155,6 +155,7 @@ "addon.messages.contactableprivacy_coursemember": "My contacts and anyone in my courses", "addon.messages.contactableprivacy_onlycontacts": "My contacts only", "addon.messages.contactableprivacy_site": "Anyone on the site", + "addon.messages.contactblocked": "Contact blocked", "addon.messages.contactlistempty": "The contact list is empty", "addon.messages.contactname": "Contact name", "addon.messages.contacts": "Contacts", @@ -164,12 +165,15 @@ "addon.messages.errorwhileretrievingcontacts": "Error while retrieving contacts from the server.", "addon.messages.errorwhileretrievingdiscussions": "Error while retrieving discussions from the server.", "addon.messages.errorwhileretrievingmessages": "Error while retrieving messages from the server.", + "addon.messages.groupmessages": "Group messages", "addon.messages.message": "Message", "addon.messages.messagenotsent": "The message was not sent. Please try again later.", "addon.messages.messagepreferences": "Message preferences", "addon.messages.messages": "Messages", "addon.messages.newmessage": "New message", "addon.messages.newmessages": "New messages", + "addon.messages.nofavourites": "No favourites", + "addon.messages.nogroupmessages": "No group messages", "addon.messages.nomessages": "No messages", "addon.messages.nousersfound": "No users found", "addon.messages.removecontact": "Remove contact", @@ -182,6 +186,7 @@ "addon.messages.unblockuser": "Unblock user", "addon.messages.unblockuserconfirm": "Are you sure you want to unblock {{$a}}?", "addon.messages.warningmessagenotsent": "Couldn't send message(s) to user {{user}}. {{error}}", + "addon.messages.you": "You:", "addon.mod_assign.acceptsubmissionstatement": "Please accept the submission statement.", "addon.mod_assign.addattempt": "Allow another attempt", "addon.mod_assign.addnewattempt": "Add a new attempt", @@ -1271,6 +1276,7 @@ "core.errorsync": "An error occurred while synchronising. Please try again.", "core.errorsyncblocked": "This {{$a}} cannot be synchronised right now because of an ongoing process. Please try again later. If the problem persists, try restarting the app.", "core.explanationdigitalminor": "This information is required to determine if your age is over the digital age of consent. This is the age when an individual can consent to terms and conditions and their data being legally stored and processed.", + "core.favourites": "Favourites", "core.filename": "Filename", "core.filenameexist": "File name already exists: {{$a}}", "core.fileuploader.addfiletext": "Add file", diff --git a/src/lang/en.json b/src/lang/en.json index 83b309c24..70dd8010f 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -90,6 +90,7 @@ "errorsync": "An error occurred while synchronising. Please try again.", "errorsyncblocked": "This {{$a}} cannot be synchronised right now because of an ongoing process. Please try again later. If the problem persists, try restarting the app.", "explanationdigitalminor": "This information is required to determine if your age is over the digital age of consent. This is the age when an individual can consent to terms and conditions and their data being legally stored and processed.", + "favourites": "Favourites", "filename": "Filename", "filenameexist": "File name already exists: {{$a}}", "folder": "Folder",