// (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 / 1000; } } }, 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); } 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); } } }); } /** * Fetch conversations. * * @return {Promise<any>} Promise resolved when done. */ protected fetchData(): Promise<any> { 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<any>} Promise resolved when done. */ fetchDataForOption(option: any, loadingMore?: boolean): Promise<void> { 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. Only if there is no conversationId. * @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 = { conversationId: conversationId, 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<any>} Resolved when done. */ loadMoreConversations(option: any, infiniteComplete?: any): Promise<any> { 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<any>} Promise resolved when done. */ refreshData(refresher?: any): Promise<any> { 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<any>} Resolved when done. */ searchMessage(query: string): Promise<any> { 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.off(); this.appResumeSubscription && this.appResumeSubscription.unsubscribe(); this.pushObserver && this.pushObserver.unsubscribe(); this.readChangedObserver && this.readChangedObserver.off(); this.cronObserver && this.cronObserver.off(); } }