1285 lines
		
	
	
		
			50 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			1285 lines
		
	
	
		
			50 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| // (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, OnDestroy, ViewChild, Optional } from '@angular/core';
 | |
| import { IonicPage, NavParams, NavController, Content, ModalController } 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 { AddonMessagesOfflineProvider } from '../../providers/messages-offline';
 | |
| import { AddonMessagesSyncProvider } from '../../providers/sync';
 | |
| import { CoreUserProvider } from '@core/user/providers/user';
 | |
| import { CoreDomUtilsProvider } from '@providers/utils/dom';
 | |
| import { CoreUtilsProvider } from '@providers/utils/utils';
 | |
| import { CoreLoggerProvider } from '@providers/logger';
 | |
| import { CoreAppProvider } from '@providers/app';
 | |
| import { coreSlideInOut } from '@classes/animations';
 | |
| import { CoreSplitViewComponent } from '@components/split-view/split-view';
 | |
| import { Md5 } from 'ts-md5/dist/md5';
 | |
| import * as moment from 'moment';
 | |
| 
 | |
| /**
 | |
|  * Page that displays a message discussion page.
 | |
|  */
 | |
| @IonicPage({ segment: 'addon-messages-discussion' })
 | |
| @Component({
 | |
|     selector: 'page-addon-messages-discussion',
 | |
|     templateUrl: 'discussion.html',
 | |
|     animations: [coreSlideInOut]
 | |
| })
 | |
| export class AddonMessagesDiscussionPage implements OnDestroy {
 | |
|     @ViewChild(Content) content: Content;
 | |
| 
 | |
|     siteId: string;
 | |
|     protected fetching: boolean;
 | |
|     protected polling;
 | |
|     protected logger;
 | |
| 
 | |
|     protected unreadMessageFrom = 0;
 | |
|     protected messagesBeingSent = 0;
 | |
|     protected pagesLoaded = 1;
 | |
|     protected lastMessage = {text: '', timecreated: 0};
 | |
|     protected keepMessageMap = {};
 | |
|     protected syncObserver: any;
 | |
|     protected oldContentHeight = 0;
 | |
|     protected keyboardObserver: any;
 | |
|     protected scrollBottom = true;
 | |
|     protected viewDestroyed = false;
 | |
|     protected memberInfoObserver: any;
 | |
|     protected showLoadingModal = false; // Whether to show a loading modal while fetching data.
 | |
| 
 | |
|     conversationId: number; // Conversation ID. Undefined if it's a new individual conversation.
 | |
|     conversation: any; // The conversation object (if it exists).
 | |
|     userId: number; // User ID you're talking to (only if group messaging not enabled or it's a new individual conversation).
 | |
|     currentUserId: number;
 | |
|     title: string;
 | |
|     showInfo: boolean;
 | |
|     conversationImage: string;
 | |
|     loaded = false;
 | |
|     showKeyboard = false;
 | |
|     canLoadMore = false;
 | |
|     loadMoreError = false;
 | |
|     messages = [];
 | |
|     showDelete = false;
 | |
|     canDelete = false;
 | |
|     groupMessagingEnabled: boolean;
 | |
|     isGroup = false;
 | |
|     members: any = {}; // Members that wrote a message, indexed by ID.
 | |
|     favouriteIcon = 'fa-star';
 | |
|     deleteIcon = 'trash';
 | |
|     blockIcon = 'close-circle';
 | |
|     addRemoveIcon = 'add';
 | |
|     otherMember: any; // Other member information (individual conversations only).
 | |
|     footerType: 'message' | 'blocked' | 'requiresContact' | 'requestSent' | 'requestReceived' | 'unable';
 | |
|     requestContactSent = false;
 | |
|     requestContactReceived = false;
 | |
| 
 | |
|     constructor(private eventsProvider: CoreEventsProvider, sitesProvider: CoreSitesProvider, navParams: NavParams,
 | |
|             private userProvider: CoreUserProvider, private navCtrl: NavController, private messagesSync: AddonMessagesSyncProvider,
 | |
|             private domUtils: CoreDomUtilsProvider, private messagesProvider: AddonMessagesProvider, logger: CoreLoggerProvider,
 | |
|             private utils: CoreUtilsProvider, private appProvider: CoreAppProvider, private translate: TranslateService,
 | |
|             @Optional() private svComponent: CoreSplitViewComponent, private messagesOffline: AddonMessagesOfflineProvider,
 | |
|             private modalCtrl: ModalController) {
 | |
| 
 | |
|         this.siteId = sitesProvider.getCurrentSiteId();
 | |
|         this.currentUserId = sitesProvider.getCurrentSiteUserId();
 | |
|         this.groupMessagingEnabled = this.messagesProvider.isGroupMessagingEnabled();
 | |
| 
 | |
|         this.logger = logger.getInstance('AddonMessagesDiscussionPage');
 | |
| 
 | |
|         this.conversationId = navParams.get('conversationId');
 | |
|         this.userId = navParams.get('userId');
 | |
|         this.showKeyboard = navParams.get('showKeyboard');
 | |
| 
 | |
|         // Refresh data if this discussion is synchronized automatically.
 | |
|         this.syncObserver = eventsProvider.on(AddonMessagesSyncProvider.AUTO_SYNCED, (data) => {
 | |
|             if ((data.userId && data.userId == this.userId) ||
 | |
|                     (data.conversationId && data.conversationId == this.conversationId)) {
 | |
|                 // Fetch messages.
 | |
|                 this.fetchMessages();
 | |
| 
 | |
|                 // Show first warning if any.
 | |
|                 if (data.warnings && data.warnings[0]) {
 | |
|                     this.domUtils.showErrorModal(data.warnings[0]);
 | |
|                 }
 | |
|             }
 | |
|         }, this.siteId);
 | |
| 
 | |
|         // Refresh data if info of a mamber of the conversation have changed.
 | |
|         this.memberInfoObserver = eventsProvider.on(AddonMessagesProvider.MEMBER_INFO_CHANGED_EVENT, (data) => {
 | |
|             if (data.userId && (this.members[data.userId] || this.otherMember && data.userId == this.otherMember.id)) {
 | |
|                 this.fetchData();
 | |
|             }
 | |
|         }, this.siteId);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Adds a new message to the message list.
 | |
|      *
 | |
|      * @param {any} message Message to be added.
 | |
|      * @param {boolean} [keep=true] If set the keep flag or not.
 | |
|      */
 | |
|     protected addMessage(message: any, keep: boolean = true): void {
 | |
|         // Use text instead of message ID because ID changes when a message is read.
 | |
|         message.hash = Md5.hashAsciiStr(message.text || '') + '#' + message.timecreated + '#' + message.useridfrom;
 | |
|         if (typeof this.keepMessageMap[message.hash] === 'undefined') {
 | |
|             // Message not added to the list. Add it now.
 | |
|             this.messages.push(message);
 | |
|         }
 | |
|         // Message needs to be kept in the list.
 | |
|         this.keepMessageMap[message.hash] = keep;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Remove a message if it shouldn't be in the list anymore.
 | |
|      *
 | |
|      * @param {string} hash Hash of the message to be removed.
 | |
|      */
 | |
|     protected removeMessage(hash: any): void {
 | |
|         if (this.keepMessageMap[hash]) {
 | |
|             // Selected to keep it, clear the flag.
 | |
|             this.keepMessageMap[hash] = false;
 | |
| 
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         delete this.keepMessageMap[hash];
 | |
| 
 | |
|         const position = this.messages.findIndex((message) => {
 | |
|             return message.hash == hash;
 | |
|         });
 | |
|         if (position >= 0) {
 | |
|             this.messages.splice(position, 1);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Runs when the page has loaded. This event only happens once per page being created.
 | |
|      * If a page leaves but is cached, then this event will not fire again on a subsequent viewing.
 | |
|      * Setup code for the page.
 | |
|      */
 | |
|     ionViewDidLoad(): void {
 | |
|         // Disable the profile button if we're already coming from a profile.
 | |
|         const backViewPage = this.navCtrl.getPrevious() && this.navCtrl.getPrevious().component.name;
 | |
|         this.showInfo = !backViewPage || backViewPage !== 'CoreUserProfilePage';
 | |
| 
 | |
|         // Recalculate footer position when keyboard is shown or hidden.
 | |
|         this.keyboardObserver = this.eventsProvider.on(CoreEventsProvider.KEYBOARD_CHANGE, (kbHeight) => {
 | |
|             this.content.resize();
 | |
|         });
 | |
| 
 | |
|         this.fetchData();
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Convenience function to fetch the conversation data.
 | |
|      *
 | |
|      * @return {Promise<any>} Resolved when done.
 | |
|      */
 | |
|     protected fetchData(): Promise<any> {
 | |
|         let loader;
 | |
|         if (this.showLoadingModal) {
 | |
|             loader = this.domUtils.showModalLoading();
 | |
|         }
 | |
| 
 | |
|         if (!this.groupMessagingEnabled && this.userId) {
 | |
|             // Get the user profile to retrieve the user fullname and image.
 | |
|             this.userProvider.getProfile(this.userId, undefined, true).then((user) => {
 | |
|                 if (!this.title) {
 | |
|                     this.title = user.fullname;
 | |
|                 }
 | |
|                 this.conversationImage = user.profileimageurl;
 | |
|             });
 | |
|         }
 | |
| 
 | |
|         // Synchronize messages if needed.
 | |
|         return this.messagesSync.syncDiscussion(this.conversationId, this.userId).catch(() => {
 | |
|             // Ignore errors.
 | |
|         }).then((warnings): Promise<any> => {
 | |
|             if (warnings && warnings[0]) {
 | |
|                 this.domUtils.showErrorModal(warnings[0]);
 | |
|             }
 | |
| 
 | |
|             if (this.groupMessagingEnabled) {
 | |
|                 // Get the conversation ID if it exists and we don't have it yet.
 | |
|                 return this.getConversation(this.conversationId, this.userId).then((exists) => {
 | |
|                     const promises = [];
 | |
| 
 | |
|                     if (exists) {
 | |
|                         // Fetch the messages for the first time.
 | |
|                         promises.push(this.fetchMessages());
 | |
|                     }
 | |
| 
 | |
|                     if (this.userId) {
 | |
|                         promises.push(this.messagesProvider.getMemberInfo(this.userId).then((member) => {
 | |
|                             this.otherMember = member;
 | |
|                             if (!exists && member) {
 | |
|                                 this.conversationImage = member.profileimageurl;
 | |
|                                 this.title = member.fullname;
 | |
|                             }
 | |
|                         }));
 | |
|                     } else {
 | |
|                         this.otherMember = null;
 | |
|                     }
 | |
| 
 | |
|                     return Promise.all(promises);
 | |
|                 });
 | |
|             } else {
 | |
|                 this.otherMember = null;
 | |
| 
 | |
|                 // Fetch the messages for the first time.
 | |
|                 return this.fetchMessages().then(() => {
 | |
|                     if (!this.title && this.messages.length) {
 | |
|                         // Didn't receive the fullname via argument. Try to get it from messages.
 | |
|                         // It's possible that name cannot be resolved when no messages were yet exchanged.
 | |
|                         if (this.messages[0].useridto != this.currentUserId) {
 | |
|                             this.title = this.messages[0].usertofullname || '';
 | |
|                         } else {
 | |
|                             this.title = this.messages[0].userfromfullname || '';
 | |
|                         }
 | |
|                     }
 | |
|                 });
 | |
|             }
 | |
|         }).catch((error) => {
 | |
|             this.domUtils.showErrorModalDefault(error, 'addon.messages.errorwhileretrievingmessages', true);
 | |
|         }).finally(() => {
 | |
|             this.checkCanDelete();
 | |
|             this.resizeContent();
 | |
|             this.loaded = true;
 | |
|             this.setPolling(); // Make sure we're polling messages.
 | |
|             this.setContactRequestInfo();
 | |
|             this.setFooterType();
 | |
|             loader && loader.dismiss();
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Runs when the page has fully entered and is now the active page.
 | |
|      * This event will fire, whether it was the first load or a cached page.
 | |
|      */
 | |
|     ionViewDidEnter(): void {
 | |
|         this.setPolling();
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Runs when the page is about to leave and no longer be the active page.
 | |
|      */
 | |
|     ionViewWillLeave(): void {
 | |
|         this.unsetPolling();
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Convenience function to fetch messages.
 | |
|      *
 | |
|      * @return {Promise<any>} Resolved when done.
 | |
|      */
 | |
|     protected fetchMessages(): Promise<any> {
 | |
|         this.loadMoreError = false;
 | |
| 
 | |
|         if (this.messagesBeingSent > 0) {
 | |
|             // We do not poll while a message is being sent or we could confuse the user.
 | |
|             // Otherwise, his message would disappear from the list, and he'd have to wait for the interval to check for messages.
 | |
|             return Promise.reject(null);
 | |
|         } else if (this.fetching) {
 | |
|             // Already fetching.
 | |
|             return Promise.reject(null);
 | |
|         } else if (this.groupMessagingEnabled && !this.conversationId) {
 | |
|             // Don't have enough data to fetch messages.
 | |
|             return Promise.reject(null);
 | |
|         }
 | |
| 
 | |
|         if (this.conversationId) {
 | |
|             this.logger.debug(`Polling new messages for conversation '${this.conversationId}'`);
 | |
|         } else {
 | |
|             this.logger.debug(`Polling new messages for discussion with user '${this.userId}'`);
 | |
|         }
 | |
| 
 | |
|         this.fetching = true;
 | |
| 
 | |
|         // Wait for synchronization process to finish.
 | |
|         return this.messagesSync.waitForSyncConversation(this.conversationId, this.userId).then(() => {
 | |
|             // Fetch messages. Invalidate the cache before fetching.
 | |
|             if (this.groupMessagingEnabled) {
 | |
|                 return this.messagesProvider.invalidateConversationMessages(this.conversationId).catch(() => {
 | |
|                     // Ignore errors.
 | |
|                 }).then(() => {
 | |
|                     return this.getConversationMessages(this.pagesLoaded);
 | |
|                 });
 | |
|             } else {
 | |
|                 return this.messagesProvider.invalidateDiscussionCache(this.userId).catch(() => {
 | |
|                     // Ignore errors.
 | |
|                 }).then(() => {
 | |
|                     return this.getDiscussionMessages(this.pagesLoaded);
 | |
|                 });
 | |
|             }
 | |
|         }).then((messages) => {
 | |
|             this.loadMessages(messages);
 | |
|         }).finally(() => {
 | |
|             this.fetching = false;
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Format and load a list of messages into the view.
 | |
|      *
 | |
|      * @param {any[]} messages Messages to load.
 | |
|      */
 | |
|     protected loadMessages(messages: any[]): void {
 | |
|         if (this.viewDestroyed) {
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         // Check if we are at the bottom to scroll it after render.
 | |
|         this.scrollBottom = this.domUtils.getScrollHeight(this.content) - this.domUtils.getScrollTop(this.content) ===
 | |
|             this.domUtils.getContentHeight(this.content);
 | |
| 
 | |
|         if (this.messagesBeingSent > 0) {
 | |
|             // Ignore polling due to a race condition.
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         // Add new messages to the list and mark the messages that should still be displayed.
 | |
|         messages.forEach((message) => {
 | |
|             this.addMessage(message);
 | |
|         });
 | |
| 
 | |
|         // Remove messages that shouldn't be in the list anymore.
 | |
|         for (const hash in this.keepMessageMap) {
 | |
|             this.removeMessage(hash);
 | |
|         }
 | |
| 
 | |
|         // Sort the messages.
 | |
|         this.messagesProvider.sortMessages(this.messages);
 | |
| 
 | |
|         // Calculate which messages need to display the date or user data.
 | |
|         this.messages.forEach((message, index): any => {
 | |
|             message.showDate = this.showDate(message, this.messages[index - 1]);
 | |
|             message.showUserData = this.showUserData(message, this.messages[index - 1]);
 | |
|         });
 | |
| 
 | |
|         // Notify that there can be a new message.
 | |
|         this.notifyNewMessage();
 | |
| 
 | |
|         // Mark retrieved messages as read if they are not.
 | |
|         this.markMessagesAsRead();
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Get the conversation.
 | |
|      *
 | |
|      * @param {number} conversationId Conversation ID.
 | |
|      * @param {number} userId User ID.
 | |
|      * @return {Promise<boolean>} Promise resolved with a boolean: whether the conversation exists or not.
 | |
|      */
 | |
|     protected getConversation(conversationId: number, userId: number): Promise<boolean> {
 | |
|         let promise,
 | |
|             fallbackConversation;
 | |
| 
 | |
|         // Try to get the conversationId if we don't have it.
 | |
|         if (conversationId) {
 | |
|             promise = Promise.resolve(conversationId);
 | |
|         } else {
 | |
|             promise = this.messagesProvider.getConversationBetweenUsers(userId).then((conversation) => {
 | |
|                 fallbackConversation = conversation;
 | |
| 
 | |
|                 return conversation.id;
 | |
|             });
 | |
|         }
 | |
| 
 | |
|         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 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;
 | |
|                         this.blockIcon = this.otherMember && this.otherMember.isblocked ? 'close-circle' : 'checkmark-circle';
 | |
|                         this.addRemoveIcon = this.otherMember && this.otherMember.iscontact ? 'remove' : 'add';
 | |
|                     }
 | |
| 
 | |
|                     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);
 | |
|                 }
 | |
|             });
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Get the messages of the conversation. Used if group messaging is supported.
 | |
|      *
 | |
|      * @param {number} pagesToLoad Number of "pages" to load.
 | |
|      * @param  {number} [offset=0] Offset for message list.
 | |
|      * @return {Promise<any[]>} Promise resolved with the list of messages.
 | |
|      */
 | |
|     protected getConversationMessages(pagesToLoad: number, offset: number = 0): Promise<any[]> {
 | |
|         const excludePending = offset > 0;
 | |
| 
 | |
|         return this.messagesProvider.getConversationMessages(this.conversationId, excludePending, offset).then((result) => {
 | |
|             pagesToLoad--;
 | |
| 
 | |
|             // Treat members. Don't use CoreUtilsProvider.arrayToObject because we don't want to override the existing object.
 | |
|             if (result.members) {
 | |
|                 result.members.forEach((member) => {
 | |
|                     this.members[member.id] = member;
 | |
|                 });
 | |
|             }
 | |
| 
 | |
|             if (pagesToLoad > 0 && result.canLoadMore) {
 | |
|                 offset += AddonMessagesProvider.LIMIT_MESSAGES;
 | |
| 
 | |
|                 // Get more messages.
 | |
|                 return this.getConversationMessages(pagesToLoad, offset).then((nextMessages) => {
 | |
|                     return result.messages.concat(nextMessages);
 | |
|                 });
 | |
|             } else {
 | |
|                 // No more messages to load, return them.
 | |
|                 this.canLoadMore = result.canLoadMore;
 | |
| 
 | |
|                 return result.messages;
 | |
|             }
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Get a discussion. Can load several "pages".
 | |
|      *
 | |
|      * @param  {number}  pagesToLoad          Number of pages to load.
 | |
|      * @param  {number}  [lfReceivedUnread=0] Number of unread received messages already fetched, so fetch will be done from this.
 | |
|      * @param  {number}  [lfReceivedRead=0]   Number of read received messages already fetched, so fetch will be done from this.
 | |
|      * @param  {number}  [lfSentUnread=0]     Number of unread sent messages already fetched, so fetch will be done from this.
 | |
|      * @param  {number}  [lfSentRead=0]       Number of read sent messages already fetched, so fetch will be done from this.
 | |
|      * @return {Promise<any>}  Resolved when done.
 | |
|      */
 | |
|     protected getDiscussionMessages(pagesToLoad: number, lfReceivedUnread: number = 0, lfReceivedRead: number = 0,
 | |
|             lfSentUnread: number = 0, lfSentRead: number = 0): Promise<any> {
 | |
| 
 | |
|         // Only get offline messages if we're loading the first "page".
 | |
|         const excludePending = lfReceivedUnread > 0 || lfReceivedRead > 0 || lfSentUnread > 0 || lfSentRead > 0;
 | |
| 
 | |
|         // Get next messages.
 | |
|         return this.messagesProvider.getDiscussion(this.userId, excludePending, lfReceivedUnread, lfReceivedRead, lfSentUnread,
 | |
|                 lfSentRead).then((result) => {
 | |
| 
 | |
|             pagesToLoad--;
 | |
|             if (pagesToLoad > 0 && result.canLoadMore) {
 | |
|                 // More pages to load. Calculate new limit froms.
 | |
|                 result.messages.forEach((message) => {
 | |
|                     if (!message.pending) {
 | |
|                         if (message.useridfrom == this.userId) {
 | |
|                             if (message.read) {
 | |
|                                 lfReceivedRead++;
 | |
|                             } else {
 | |
|                                 lfReceivedUnread++;
 | |
|                             }
 | |
|                         } else {
 | |
|                             if (message.read) {
 | |
|                                 lfSentRead++;
 | |
|                             } else {
 | |
|                                 lfSentUnread++;
 | |
|                             }
 | |
|                         }
 | |
|                     }
 | |
|                 });
 | |
| 
 | |
|                 // Get next messages.
 | |
|                 return this.getDiscussionMessages(pagesToLoad, lfReceivedUnread, lfReceivedRead, lfSentUnread, lfSentRead)
 | |
|                         .then((nextMessages) => {
 | |
|                     return result.messages.concat(nextMessages);
 | |
|                 });
 | |
|             } else {
 | |
|                 // No more messages to load, return them.
 | |
|                 this.canLoadMore = result.canLoadMore;
 | |
| 
 | |
|                 return result.messages;
 | |
|             }
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Mark messages as read.
 | |
|      */
 | |
|     protected markMessagesAsRead(): void {
 | |
|         let readChanged = false;
 | |
|         const promises = [];
 | |
| 
 | |
|         if (this.messagesProvider.isMarkAllMessagesReadEnabled()) {
 | |
|             let messageUnreadFound = false;
 | |
| 
 | |
|             // Mark all messages at a time if there is any unread message.
 | |
|             if (this.groupMessagingEnabled) {
 | |
|                 messageUnreadFound = this.conversation && this.conversation.unreadcount > 0 && this.conversationId > 0;
 | |
|             } else {
 | |
|                 for (const x in this.messages) {
 | |
|                     const message = this.messages[x];
 | |
|                     // If an unread message is found, mark all messages as read.
 | |
|                     if (message.useridfrom != this.currentUserId && message.read == 0) {
 | |
|                         messageUnreadFound = true;
 | |
|                         break;
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             if (messageUnreadFound) {
 | |
|                 this.setUnreadLabelPosition();
 | |
| 
 | |
|                 let promise;
 | |
| 
 | |
|                 if (this.groupMessagingEnabled) {
 | |
|                     promise = this.messagesProvider.markAllConversationMessagesRead(this.conversationId);
 | |
|                 } else {
 | |
|                     promise = this.messagesProvider.markAllMessagesRead(this.userId).then(() => {
 | |
|                         // Mark all messages as read.
 | |
|                         this.messages.forEach((message) => {
 | |
|                             message.read = 1;
 | |
|                         });
 | |
|                     });
 | |
|                 }
 | |
| 
 | |
|                 promises.push(promise.then(() => {
 | |
|                     readChanged = true;
 | |
|                 }));
 | |
|             }
 | |
|         } else {
 | |
|             this.setUnreadLabelPosition();
 | |
|             // Mark each message as read one by one.
 | |
|             this.messages.forEach((message) => {
 | |
|                 // If the message is unread, call this.messagesProvider.markMessageRead.
 | |
|                 if (message.useridfrom != this.currentUserId && message.read == 0) {
 | |
|                     promises.push(this.messagesProvider.markMessageRead(message.id).then(() => {
 | |
|                         readChanged = true;
 | |
|                         message.read = 1;
 | |
|                     }));
 | |
|                 }
 | |
|             });
 | |
|         }
 | |
| 
 | |
|         Promise.all(promises).finally(() => {
 | |
|             if (readChanged) {
 | |
|                 this.eventsProvider.trigger(AddonMessagesProvider.READ_CHANGED_EVENT, {
 | |
|                     conversationId: this.conversationId,
 | |
|                     userId: this.userId
 | |
|                 }, this.siteId);
 | |
|             }
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Notify the last message found so discussions list controller can tell if last message should be updated.
 | |
|      */
 | |
|     protected notifyNewMessage(): void {
 | |
|         const last = this.messages[this.messages.length - 1];
 | |
| 
 | |
|         let trigger = false;
 | |
|         if (!last) {
 | |
|             this.lastMessage = {text: '', timecreated: 0};
 | |
|             trigger = true;
 | |
|         } else if (last.text !== this.lastMessage.text || last.timecreated !== this.lastMessage.timecreated) {
 | |
|             this.lastMessage = {text: last.text, timecreated: last.timecreated};
 | |
|             trigger = true;
 | |
|         }
 | |
| 
 | |
|         if (trigger) {
 | |
|             // Update discussions last message.
 | |
|             this.eventsProvider.trigger(AddonMessagesProvider.NEW_MESSAGE_EVENT, {
 | |
|                 conversationId: this.conversationId,
 | |
|                 userId: this.userId,
 | |
|                 message: this.lastMessage.text,
 | |
|                 timecreated: this.lastMessage.timecreated
 | |
|             }, this.siteId);
 | |
| 
 | |
|             // Update navBar links and buttons.
 | |
|             const newCanDelete = (last && last.id && this.messages.length == 1) || this.messages.length > 1;
 | |
|             if (this.canDelete != newCanDelete) {
 | |
|                 this.checkCanDelete();
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Set the place where the unread label position has to be.
 | |
|      */
 | |
|     protected setUnreadLabelPosition(): void {
 | |
|         if (this.unreadMessageFrom != 0) {
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         if (this.groupMessagingEnabled) {
 | |
|             // Use the unreadcount from the conversation to calculate where should the label be placed.
 | |
|             if (this.conversation && this.conversation.unreadcount > 0 && this.messages) {
 | |
|                 // Iterate over messages to find the right message using the unreadcount. Skip offline messages and own messages.
 | |
|                 let found = 0;
 | |
| 
 | |
|                 for (let i = this.messages.length - 1; i >= 0; i--) {
 | |
|                     const message = this.messages[i];
 | |
|                     if (!message.pending && message.useridfrom != this.currentUserId) {
 | |
|                         found++;
 | |
|                         if (found == this.conversation.unreadcount) {
 | |
|                             this.unreadMessageFrom = parseInt(message.id, 10);
 | |
|                             break;
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         } else {
 | |
|             let previousMessageRead = false;
 | |
| 
 | |
|             for (const x in this.messages) {
 | |
|                 const message = this.messages[x];
 | |
|                 if (message.useridfrom != this.currentUserId) {
 | |
|                     const unreadFrom = message.read == 0 && previousMessageRead;
 | |
| 
 | |
|                     if (unreadFrom) {
 | |
|                         // Save where the label is placed.
 | |
|                         this.unreadMessageFrom = parseInt(message.id, 10);
 | |
|                         break;
 | |
|                     }
 | |
| 
 | |
|                     previousMessageRead = message.read != 0;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         // Do not update the message unread from label on next refresh.
 | |
|         if (this.unreadMessageFrom == 0) {
 | |
|             // Using negative to indicate the label is not placed but should not be placed.
 | |
|             this.unreadMessageFrom = -1;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Check if there's any message in the list that can be deleted.
 | |
|      */
 | |
|     protected checkCanDelete(): void {
 | |
|         // All messages being sent should be at the end of the list.
 | |
|         const first = this.messages[0];
 | |
|         this.canDelete = first && !first.sending;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Hide unread label when sending messages.
 | |
|      */
 | |
|     protected hideUnreadLabel(): void {
 | |
|         if (this.unreadMessageFrom > 0) {
 | |
|             this.unreadMessageFrom = -1;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Wait until fetching is false.
 | |
|      * @return {Promise<void>} Resolved when done.
 | |
|      */
 | |
|     protected waitForFetch(): Promise<void> {
 | |
|         if (!this.fetching) {
 | |
|             return Promise.resolve();
 | |
|         }
 | |
| 
 | |
|         const deferred = this.utils.promiseDefer();
 | |
| 
 | |
|         setTimeout(() => {
 | |
|             return this.waitForFetch().finally(() => {
 | |
|                 deferred.resolve();
 | |
|             });
 | |
|         }, 400);
 | |
| 
 | |
|         return deferred.promise;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Set a polling to get new messages every certain time.
 | |
|      */
 | |
|     protected setPolling(): void {
 | |
|         if (this.groupMessagingEnabled && !this.conversationId) {
 | |
|             // Don't have enough data to poll messages.
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         if (!this.polling) {
 | |
|             // Start polling.
 | |
|             this.polling = setInterval(() => {
 | |
|                 this.fetchMessages().catch(() => {
 | |
|                     // Ignore errors.
 | |
|                 });
 | |
|             }, AddonMessagesProvider.POLL_INTERVAL);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Unset polling.
 | |
|      */
 | |
|     protected unsetPolling(): void {
 | |
|         if (this.polling) {
 | |
|             this.logger.debug(`Cancelling polling for conversation with user '${this.userId}'`);
 | |
|             clearInterval(this.polling);
 | |
|             this.polling = undefined;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Copy message to clipboard.
 | |
|      *
 | |
|      * @param {any} message Message to be copied.
 | |
|      */
 | |
|     copyMessage(message: any): void {
 | |
|         this.utils.copyToClipboard(message.smallmessage || message.text || '');
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Function to delete a message.
 | |
|      *
 | |
|      * @param {any} message  Message object to delete.
 | |
|      * @param {number} index Index where the message is to delete it from the view.
 | |
|      */
 | |
|     deleteMessage(message: any, index: number): void {
 | |
|         const langKey = message.pending ? 'core.areyousure' : 'addon.messages.deletemessageconfirmation';
 | |
|         this.domUtils.showConfirm(this.translate.instant(langKey)).then(() => {
 | |
|             const modal = this.domUtils.showModalLoading('core.deleting', true);
 | |
| 
 | |
|             return this.messagesProvider.deleteMessage(message).then(() => {
 | |
|                  // Remove message from the list without having to wait for re-fetch.
 | |
|                 this.messages.splice(index, 1);
 | |
|                 this.removeMessage(message.hash);
 | |
|                 this.notifyNewMessage();
 | |
| 
 | |
|                 this.fetchMessages(); // Re-fetch messages to update cached data.
 | |
|             }).finally(() => {
 | |
|                 modal.dismiss();
 | |
|             });
 | |
|         }).catch((error) => {
 | |
|             this.domUtils.showErrorModalDefault(error, 'addon.messages.errordeletemessage', true);
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Function to load previous messages.
 | |
|      *
 | |
|      * @param {any} [infiniteComplete] Infinite scroll complete function. Only used from core-infinite-loading.
 | |
|      * @return {Promise<any>} Resolved when done.
 | |
|      */
 | |
|     loadPrevious(infiniteComplete?: any): Promise<any> {
 | |
|         // If there is an ongoing fetch, wait for it to finish.
 | |
|         return this.waitForFetch().finally(() => {
 | |
|             this.pagesLoaded++;
 | |
| 
 | |
|             this.fetchMessages().catch((error) => {
 | |
|                 this.loadMoreError = true; // Set to prevent infinite calls with infinite-loading.
 | |
|                 this.pagesLoaded--;
 | |
|                 this.domUtils.showErrorModalDefault(error, 'addon.messages.errorwhileretrievingmessages', true);
 | |
|             }).finally(() => {
 | |
|                 infiniteComplete && infiniteComplete();
 | |
|             });
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Content or scroll has been resized. For content, only call it if it's been added on top.
 | |
|      */
 | |
|     resizeContent(): void {
 | |
|         let top = this.content.getContentDimensions().scrollTop;
 | |
|         this.content.resize();
 | |
| 
 | |
|         // Wait for new content height to be calculated.
 | |
|         setTimeout(() => {
 | |
|             // Visible content size changed, maintain the bottom position.
 | |
|             if (!this.viewDestroyed && this.content && this.domUtils.getContentHeight(this.content) != this.oldContentHeight) {
 | |
|                 if (!top) {
 | |
|                     top = this.content.getContentDimensions().scrollTop;
 | |
|                 }
 | |
| 
 | |
|                 top += this.oldContentHeight - this.domUtils.getContentHeight(this.content);
 | |
|                 this.oldContentHeight = this.domUtils.getContentHeight(this.content);
 | |
| 
 | |
|                 this.domUtils.scrollTo(this.content, 0, top, 0);
 | |
|             }
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Scroll bottom when render has finished.
 | |
|      */
 | |
|     scrollToBottom(): void {
 | |
|         // Check if scroll is at bottom. If so, scroll bottom after rendering since there might be something new.
 | |
|         if (this.scrollBottom) {
 | |
|              // Need a timeout to leave time to the view to be rendered.
 | |
|             setTimeout(() => {
 | |
|                 if (!this.viewDestroyed) {
 | |
|                     this.domUtils.scrollToBottom(this.content, 0);
 | |
|                 }
 | |
|             });
 | |
|             this.scrollBottom = false;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Sends a message to the server.
 | |
|      *
 | |
|      * @param {string} text Message text.
 | |
|      */
 | |
|     sendMessage(text: string): void {
 | |
|         let message;
 | |
| 
 | |
|         this.hideUnreadLabel();
 | |
| 
 | |
|         this.showDelete = false;
 | |
|         this.scrollBottom = true;
 | |
| 
 | |
|         message = {
 | |
|             pending: true,
 | |
|             sending: true,
 | |
|             useridfrom: this.currentUserId,
 | |
|             smallmessage: text,
 | |
|             text: text,
 | |
|             timecreated: new Date().getTime()
 | |
|         };
 | |
|         message.showDate = this.showDate(message, this.messages[this.messages.length - 1]);
 | |
|         this.addMessage(message, false);
 | |
| 
 | |
|         this.messagesBeingSent++;
 | |
| 
 | |
|         // If there is an ongoing fetch, wait for it to finish.
 | |
|         // Otherwise, if a message is sent while fetching it could disappear until the next fetch.
 | |
|         this.waitForFetch().finally(() => {
 | |
|             let promise;
 | |
| 
 | |
|             if (this.conversationId) {
 | |
|                 promise = this.messagesProvider.sendMessageToConversation(this.conversation, text);
 | |
|             } else {
 | |
|                 promise = this.messagesProvider.sendMessage(this.userId, text);
 | |
|             }
 | |
| 
 | |
|             promise.then((data) => {
 | |
|                 let promise;
 | |
| 
 | |
|                 this.messagesBeingSent--;
 | |
| 
 | |
|                 if (data.sent) {
 | |
|                     if (!this.conversationId && data.message && data.message.conversationid) {
 | |
|                         // Message sent to a new conversation, try to load the conversation.
 | |
|                         promise = this.getConversation(data.message.conversationid, this.userId).then(() => {
 | |
|                             // Now fetch messages.
 | |
|                             return this.fetchMessages();
 | |
|                         }).finally(() => {
 | |
|                             // Start polling messages now that the conversation exists.
 | |
|                             this.setPolling();
 | |
|                         });
 | |
|                     } else {
 | |
|                         // Message was sent, fetch messages right now.
 | |
|                         promise = this.fetchMessages();
 | |
|                     }
 | |
|                 } else {
 | |
|                     promise = Promise.reject(null);
 | |
|                 }
 | |
| 
 | |
|                 promise.catch(() => {
 | |
|                     // Fetch failed or is offline message, mark the message as sent.
 | |
|                     // If fetch is successful there's no need to mark it because the fetch will already show the message received.
 | |
|                     message.sending = false;
 | |
|                     if (data.sent) {
 | |
|                         // Message sent to server, not pending anymore.
 | |
|                         message.pending = false;
 | |
|                     } else if (data.message) {
 | |
|                         message.timecreated = data.message.timecreated;
 | |
|                     }
 | |
| 
 | |
|                     this.notifyNewMessage();
 | |
|                 });
 | |
|             }).catch((error) => {
 | |
|                 this.messagesBeingSent--;
 | |
| 
 | |
|                 // Only close the keyboard if an error happens.
 | |
|                 // We want the user to be able to send multiple messages without the keyboard being closed.
 | |
|                 this.appProvider.closeKeyboard();
 | |
| 
 | |
|                 this.domUtils.showErrorModalDefault(error, 'addon.messages.messagenotsent', true);
 | |
|                 this.removeMessage(message.hash);
 | |
|             });
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Check date should be shown on message list for the current message.
 | |
|      * If date has changed from previous to current message it should be shown.
 | |
|      *
 | |
|      * @param {any} message       Current message where to show the date.
 | |
|      * @param {any} [prevMessage] Previous message where to compare the date with.
 | |
|      * @return {boolean}  If date has changed and should be shown.
 | |
|      */
 | |
|     showDate(message: any, prevMessage?: any): boolean {
 | |
|         if (!prevMessage) {
 | |
|             // First message, show it.
 | |
|             return true;
 | |
|         }
 | |
| 
 | |
|         // Check if day has changed.
 | |
|         return !moment(message.timecreated).isSame(prevMessage.timecreated, 'day');
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Check if the user info should be displayed for the current message.
 | |
|      * User data is only displayed for group conversations if the previous message was from another user.
 | |
|      *
 | |
|      * @param {any} message Current message where to show the user info.
 | |
|      * @param {any} [prevMessage] Previous message.
 | |
|      * @return {boolean} Whether user data should be shown.
 | |
|      */
 | |
|     showUserData(message: any, prevMessage?: any): boolean {
 | |
|         return this.isGroup && message.useridfrom != this.currentUserId && this.members[message.useridfrom] &&
 | |
|             (!prevMessage || prevMessage.useridfrom != message.useridfrom || message.showDate);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Toggles delete state.
 | |
|      */
 | |
|     toggleDelete(): void {
 | |
|         this.showDelete = !this.showDelete;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * View info. If it's an individual conversation, go to the user profile.
 | |
|      * If it's a group conversation, view info about the group.
 | |
|      */
 | |
|     viewInfo(): void {
 | |
|         if (this.isGroup) {
 | |
|             // Display the group information.
 | |
|             const modal = this.modalCtrl.create('AddonMessagesConversationInfoPage', {
 | |
|                 conversationId: this.conversationId
 | |
|             });
 | |
| 
 | |
|             modal.present();
 | |
|             modal.onDidDismiss((userId) => {
 | |
|                 if (typeof userId != 'undefined') {
 | |
|                     // Open user conversation.
 | |
|                     if (this.svComponent) {
 | |
|                         // Notify the left pane to load it, this way the right conversation will be highlighted.
 | |
|                         this.eventsProvider.trigger(AddonMessagesProvider.OPEN_CONVERSATION_EVENT, {userId: userId}, this.siteId);
 | |
|                     } else {
 | |
|                         // Open the discussion in a new view.
 | |
|                         this.navCtrl.push('AddonMessagesDiscussionPage', {userId: userId});
 | |
|                     }
 | |
|                 }
 | |
|             });
 | |
|         } else {
 | |
|             // Open the user profile.
 | |
|             const navCtrl = this.svComponent ? this.svComponent.getMasterNav() : this.navCtrl;
 | |
|             navCtrl.push('CoreUserProfilePage', { userId: this.userId });
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * 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();
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Calculate whether there are pending contact requests.
 | |
|      */
 | |
|     protected setContactRequestInfo(): void {
 | |
|         this.requestContactSent = false;
 | |
|         this.requestContactReceived = false;
 | |
|         if (this.otherMember && !this.otherMember.iscontact) {
 | |
|             this.requestContactSent = this.otherMember.contactrequests.some((request) => {
 | |
|                 return request.userid == this.currentUserId && request.requesteduserid == this.otherMember.id;
 | |
|             });
 | |
|             this.requestContactReceived = this.otherMember.contactrequests.some((request) => {
 | |
|                 return request.userid == this.otherMember.id && request.requesteduserid == this.currentUserId;
 | |
|             });
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Calculate what to display in the footer.
 | |
|      */
 | |
|     protected setFooterType(): void {
 | |
|         if (!this.otherMember) {
 | |
|             // Group conversation or group messaging not available.
 | |
|             this.footerType = 'message';
 | |
|         } else if (this.otherMember.isblocked) {
 | |
|             this.footerType = 'blocked';
 | |
|         } else if (this.requestContactReceived) {
 | |
|             this.footerType = 'requestReceived';
 | |
|         } else if (this.otherMember.canmessage) {
 | |
|             this.footerType = 'message';
 | |
|         } else if (this.requestContactSent) {
 | |
|             this.footerType = 'requestSent';
 | |
|         } else if (this.otherMember.requirescontact) {
 | |
|             this.footerType = 'requiresContact';
 | |
|         } else {
 | |
|             this.footerType = 'unable';
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Displays a confirmation modal to block the user of the individual conversation.
 | |
|      *
 | |
|      * @return {Promise<any>} Promise resolved when user is blocked or dialog is cancelled.
 | |
|      */
 | |
|     blockUser(): Promise<any> {
 | |
|         if (!this.otherMember) {
 | |
|             // Should never happen.
 | |
|             return Promise.reject(null);
 | |
|         }
 | |
| 
 | |
|         const template = this.translate.instant('addon.messages.blockuserconfirm', {$a: this.otherMember.fullname});
 | |
|         const okText = this.translate.instant('addon.messages.blockuser');
 | |
| 
 | |
|         return this.domUtils.showConfirm(template, undefined, okText).then(() => {
 | |
|             this.blockIcon = 'spinner';
 | |
| 
 | |
|             const modal = this.domUtils.showModalLoading('core.sending', true);
 | |
|             this.showLoadingModal = true;
 | |
| 
 | |
|             return this.messagesProvider.blockContact(this.otherMember.id).finally(() => {
 | |
|                 modal.dismiss();
 | |
|                 this.showLoadingModal = false;
 | |
|             });
 | |
|         }).catch((error) => {
 | |
|             this.domUtils.showErrorModalDefault(error, 'core.error', true);
 | |
|         }).finally(() => {
 | |
|             this.blockIcon = this.otherMember.isblocked ? 'close-circle' : 'checkmark-circle';
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * 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.');
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Displays a confirmation modal to unblock the user of the individual conversation.
 | |
|      *
 | |
|      * @return {Promise<any>} Promise resolved when user is unblocked or dialog is cancelled.
 | |
|      */
 | |
|     unblockUser(): Promise<any> {
 | |
|         if (!this.otherMember) {
 | |
|             // Should never happen.
 | |
|             return Promise.reject(null);
 | |
|         }
 | |
| 
 | |
|         const template = this.translate.instant('addon.messages.unblockuserconfirm', {$a: this.otherMember.fullname});
 | |
|         const okText = this.translate.instant('addon.messages.unblockuser');
 | |
| 
 | |
|         return this.domUtils.showConfirm(template, undefined, okText).then(() => {
 | |
|             this.blockIcon = 'spinner';
 | |
| 
 | |
|             const modal = this.domUtils.showModalLoading('core.sending', true);
 | |
|             this.showLoadingModal = true;
 | |
| 
 | |
|             return this.messagesProvider.unblockContact(this.otherMember.id).finally(() => {
 | |
|                 modal.dismiss();
 | |
|                 this.showLoadingModal = false;
 | |
|             });
 | |
|         }).catch((error) => {
 | |
|             this.domUtils.showErrorModalDefault(error, 'core.error', true);
 | |
|         }).finally(() => {
 | |
|             this.blockIcon = this.otherMember.isblocked ? 'close-circle' : 'checkmark-circle';
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Displays a confirmation modal to send a contact request to the other user of the individual conversation.
 | |
|      *
 | |
|      * @return {Promise<any>} Promise resolved when the request is sent or the dialog is cancelled.
 | |
|      */
 | |
|     createContactRequest(): Promise<any> {
 | |
|         if (!this.otherMember) {
 | |
|             // Should never happen.
 | |
|             return Promise.reject(null);
 | |
|         }
 | |
| 
 | |
|         const template = this.translate.instant('addon.messages.addcontactconfirm', { $a: this.otherMember.fullname });
 | |
|         const okText = this.translate.instant('core.add');
 | |
| 
 | |
|         return this.domUtils.showConfirm(template, undefined, okText).then(() => {
 | |
|             this.addRemoveIcon = 'spinner';
 | |
| 
 | |
|             const modal = this.domUtils.showModalLoading('core.sending', true);
 | |
|             this.showLoadingModal = true;
 | |
| 
 | |
|             return this.messagesProvider.createContactRequest(this.otherMember.id).finally(() => {
 | |
|                 modal.dismiss();
 | |
|                 this.showLoadingModal = false;
 | |
|             });
 | |
|         }).catch((error) => {
 | |
|             this.domUtils.showErrorModalDefault(error, 'core.error', true);
 | |
|         }).finally(() => {
 | |
|             this.addRemoveIcon = this.otherMember.iscontact ? 'remove' : 'add';
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Confirms the contact request of the other user of the individual conversation.
 | |
|      *
 | |
|      * @return {Promise<any>} Promise resolved when the request is confirmed.
 | |
|      */
 | |
|     confirmContactRequest(): Promise<any> {
 | |
|         if (!this.otherMember) {
 | |
|             // Should never happen.
 | |
|             return Promise.reject(null);
 | |
|         }
 | |
| 
 | |
|         const modal = this.domUtils.showModalLoading('core.sending', true);
 | |
|         this.showLoadingModal = true;
 | |
| 
 | |
|         return this.messagesProvider.confirmContactRequest(this.otherMember.id).finally(() => {
 | |
|             modal.dismiss();
 | |
|             this.showLoadingModal = false;
 | |
|         }).catch((error) => {
 | |
|             this.domUtils.showErrorModalDefault(error, 'core.error', true);
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Declines the contact request of the other user of the individual conversation.
 | |
|      *
 | |
|      * @return {Promise<any>} Promise resolved when the request is confirmed.
 | |
|      */
 | |
|     declineContactRequest(): Promise<any> {
 | |
|         if (!this.otherMember) {
 | |
|             // Should never happen.
 | |
|             return Promise.reject(null);
 | |
|         }
 | |
| 
 | |
|         const modal = this.domUtils.showModalLoading('core.sending', true);
 | |
|         this.showLoadingModal = true;
 | |
| 
 | |
|         return this.messagesProvider.declineContactRequest(this.otherMember.id).finally(() => {
 | |
|             modal.dismiss();
 | |
|             this.showLoadingModal = false;
 | |
|         }).catch((error) => {
 | |
|             this.domUtils.showErrorModalDefault(error, 'core.error', true);
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Displays a confirmation modal to remove the other user of the conversation from contacts.
 | |
|      *
 | |
|      * @return {Promise<any>} Promise resolved when the request is sent or the dialog is cancelled.
 | |
|      */
 | |
|     removeContact(): Promise<any> {
 | |
|         if (!this.otherMember) {
 | |
|             // Should never happen.
 | |
|             return Promise.reject(null);
 | |
|         }
 | |
| 
 | |
|         const template = this.translate.instant('addon.messages.removecontactconfirm', { $a: this.otherMember.fullname });
 | |
|         const okText = this.translate.instant('core.remove');
 | |
| 
 | |
|         return this.domUtils.showConfirm(template, undefined, okText).then(() => {
 | |
|             this.addRemoveIcon = 'spinner';
 | |
| 
 | |
|             const modal = this.domUtils.showModalLoading('core.sending', true);
 | |
|             this.showLoadingModal = true;
 | |
| 
 | |
|             return this.messagesProvider.removeContact(this.otherMember.id).finally(() => {
 | |
|                 modal.dismiss();
 | |
|                 this.showLoadingModal = false;
 | |
|             });
 | |
|         }).catch((error) => {
 | |
|             this.domUtils.showErrorModalDefault(error, 'core.error', true);
 | |
|         }).finally(() => {
 | |
|             this.addRemoveIcon = this.otherMember.iscontact ? 'remove' : 'add';
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Page destroyed.
 | |
|      */
 | |
|     ngOnDestroy(): void {
 | |
|         // Unset again, just in case.
 | |
|         this.unsetPolling();
 | |
|         this.syncObserver && this.syncObserver.off();
 | |
|         this.keyboardObserver && this.keyboardObserver.off();
 | |
|         this.memberInfoObserver && this.memberInfoObserver.off();
 | |
|         this.viewDestroyed = true;
 | |
|     }
 | |
| }
 |