MOBILE-2632 message: Get conversation messages
parent
601508aefc
commit
a6f5543870
|
@ -165,7 +165,9 @@
|
||||||
"addon.messages.errorwhileretrievingcontacts": "local_moodlemobileapp",
|
"addon.messages.errorwhileretrievingcontacts": "local_moodlemobileapp",
|
||||||
"addon.messages.errorwhileretrievingdiscussions": "local_moodlemobileapp",
|
"addon.messages.errorwhileretrievingdiscussions": "local_moodlemobileapp",
|
||||||
"addon.messages.errorwhileretrievingmessages": "local_moodlemobileapp",
|
"addon.messages.errorwhileretrievingmessages": "local_moodlemobileapp",
|
||||||
|
"addon.messages.groupinfo": "message",
|
||||||
"addon.messages.groupmessages": "message",
|
"addon.messages.groupmessages": "message",
|
||||||
|
"addon.messages.info": "message",
|
||||||
"addon.messages.message": "message",
|
"addon.messages.message": "message",
|
||||||
"addon.messages.messagenotsent": "local_moodlemobileapp",
|
"addon.messages.messagenotsent": "local_moodlemobileapp",
|
||||||
"addon.messages.messagepreferences": "message",
|
"addon.messages.messagepreferences": "message",
|
||||||
|
@ -178,6 +180,7 @@
|
||||||
"addon.messages.nousersfound": "local_moodlemobileapp",
|
"addon.messages.nousersfound": "local_moodlemobileapp",
|
||||||
"addon.messages.removecontact": "message",
|
"addon.messages.removecontact": "message",
|
||||||
"addon.messages.removecontactconfirm": "local_moodlemobileapp",
|
"addon.messages.removecontactconfirm": "local_moodlemobileapp",
|
||||||
|
"addon.messages.showdeletemessages": "local_moodlemobileapp",
|
||||||
"addon.messages.type_blocked": "local_moodlemobileapp",
|
"addon.messages.type_blocked": "local_moodlemobileapp",
|
||||||
"addon.messages.type_offline": "local_moodlemobileapp",
|
"addon.messages.type_offline": "local_moodlemobileapp",
|
||||||
"addon.messages.type_online": "local_moodlemobileapp",
|
"addon.messages.type_online": "local_moodlemobileapp",
|
||||||
|
|
|
@ -17,7 +17,9 @@
|
||||||
"errorwhileretrievingcontacts": "Error while retrieving contacts from the server.",
|
"errorwhileretrievingcontacts": "Error while retrieving contacts from the server.",
|
||||||
"errorwhileretrievingdiscussions": "Error while retrieving discussions from the server.",
|
"errorwhileretrievingdiscussions": "Error while retrieving discussions from the server.",
|
||||||
"errorwhileretrievingmessages": "Error while retrieving messages from the server.",
|
"errorwhileretrievingmessages": "Error while retrieving messages from the server.",
|
||||||
|
"groupinfo": "Group info",
|
||||||
"groupmessages": "Group messages",
|
"groupmessages": "Group messages",
|
||||||
|
"info": "Info",
|
||||||
"messagenotsent": "The message was not sent. Please try again later.",
|
"messagenotsent": "The message was not sent. Please try again later.",
|
||||||
"message": "Message",
|
"message": "Message",
|
||||||
"messagepreferences": "Message preferences",
|
"messagepreferences": "Message preferences",
|
||||||
|
@ -30,6 +32,7 @@
|
||||||
"nousersfound": "No users found",
|
"nousersfound": "No users found",
|
||||||
"removecontact": "Remove contact",
|
"removecontact": "Remove contact",
|
||||||
"removecontactconfirm": "Contact will be removed from your contacts list.",
|
"removecontactconfirm": "Contact will be removed from your contacts list.",
|
||||||
|
"showdeletemessages": "Show delete messages",
|
||||||
"type_blocked": "Blocked",
|
"type_blocked": "Blocked",
|
||||||
"type_offline": "Offline",
|
"type_offline": "Offline",
|
||||||
"type_online": "Online",
|
"type_online": "Online",
|
||||||
|
|
|
@ -1,15 +1,17 @@
|
||||||
<ion-header>
|
<ion-header>
|
||||||
<ion-navbar core-back-button>
|
<ion-navbar core-back-button>
|
||||||
<ion-title><core-format-text [text]="title"></core-format-text></ion-title>
|
<ion-title>
|
||||||
|
<img *ngIf="conversationImage" class="core-bar-button-image" [src]="conversationImage">
|
||||||
|
<core-format-text [text]="title"></core-format-text>
|
||||||
|
</ion-title>
|
||||||
<ion-buttons end></ion-buttons>
|
<ion-buttons end></ion-buttons>
|
||||||
</ion-navbar>
|
</ion-navbar>
|
||||||
<core-navbar-buttons end>
|
<core-navbar-buttons end>
|
||||||
<button ion-button icon-only clear="true" (click)="toggleDelete()" [hidden]="!canDelete">
|
<core-context-menu>
|
||||||
<ion-icon name="trash"></ion-icon>
|
<core-context-menu-item [hidden]="!showInfo || isGroup" [priority]="1000" [content]="'addon.messages.info' | translate" (action)="viewInfo()" [iconAction]="'information-circle'"></core-context-menu-item>
|
||||||
</button>
|
<core-context-menu-item [hidden]="!showInfo || !isGroup" [priority]="999" [content]="'addon.messages.groupinfo' | translate" (action)="viewInfo()" [iconAction]="'information-circle'"></core-context-menu-item>
|
||||||
<a [hidden]="!showProfileLink" core-user-link [userId]="userId" [attr.aria-label]=" 'core.user.viewprofile' | translate">
|
<core-context-menu-item [hidden]="!canDelete" [priority]="800" [content]="'addon.messages.showdeletemessages' | translate" (action)="toggleDelete()" [iconAction]="'trash'"></core-context-menu-item>
|
||||||
<img class="button core-bar-button-image" [src]="profileLink" core-external-content onError="this.src='assets/img/user-avatar.png'">
|
</core-context-menu>
|
||||||
</a>
|
|
||||||
</core-navbar-buttons>
|
</core-navbar-buttons>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
<ion-content class="has-footer">
|
<ion-content class="has-footer">
|
||||||
|
@ -22,12 +24,12 @@
|
||||||
<ion-label>{{ message.timecreated | coreFormatDate: "LL" }}</ion-label>
|
<ion-label>{{ message.timecreated | coreFormatDate: "LL" }}</ion-label>
|
||||||
</ion-chip>
|
</ion-chip>
|
||||||
|
|
||||||
<ion-chip class="addon-messages-unreadfrom" *ngIf="message.unreadFrom" color="light">
|
<ion-chip class="addon-messages-unreadfrom" *ngIf="unreadMessageFrom && message.id == unreadMessageFrom" color="light">
|
||||||
<ion-label>{{ 'addon.messages.newmessages' | translate:{$a: title} }}</ion-label>
|
<ion-label>{{ 'addon.messages.newmessages' | translate:{$a: title} }}</ion-label>
|
||||||
<ion-icon name="arrow-round-down"></ion-icon>
|
<ion-icon name="arrow-round-down"></ion-icon>
|
||||||
</ion-chip>
|
</ion-chip>
|
||||||
|
|
||||||
<ion-item text-wrap (longPress)="copyMessage(message.smallmessage)" class="addon-message" [class.addon-message-mine]="message.useridfrom == currentUserId" [@coreSlideInOut]="message.useridfrom == currentUserId ? '' : 'fromLeft'">
|
<ion-item text-wrap (longPress)="copyMessage(message)" class="addon-message" [class.addon-message-mine]="message.useridfrom == currentUserId" [@coreSlideInOut]="message.useridfrom == currentUserId ? '' : 'fromLeft'">
|
||||||
<!-- Some messages have <p> and some others don't. Add a <p> so they all have same styles. -->
|
<!-- Some messages have <p> and some others don't. Add a <p> so they all have same styles. -->
|
||||||
<p class="addon-message-text">
|
<p class="addon-message-text">
|
||||||
<core-format-text (afterRender)="last && scrollToBottom()" [text]="message.text"></core-format-text>
|
<core-format-text (afterRender)="last && scrollToBottom()" [text]="message.text"></core-format-text>
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { Component, OnDestroy, ViewChild } from '@angular/core';
|
import { Component, OnDestroy, ViewChild, Optional } from '@angular/core';
|
||||||
import { IonicPage, NavParams, NavController, Content } from 'ionic-angular';
|
import { IonicPage, NavParams, NavController, Content } from 'ionic-angular';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { CoreEventsProvider } from '@providers/events';
|
import { CoreEventsProvider } from '@providers/events';
|
||||||
|
@ -25,6 +25,7 @@ import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||||
import { CoreLoggerProvider } from '@providers/logger';
|
import { CoreLoggerProvider } from '@providers/logger';
|
||||||
import { CoreAppProvider } from '@providers/app';
|
import { CoreAppProvider } from '@providers/app';
|
||||||
import { coreSlideInOut } from '@classes/animations';
|
import { coreSlideInOut } from '@classes/animations';
|
||||||
|
import { CoreSplitViewComponent } from '@components/split-view/split-view';
|
||||||
import { Md5 } from 'ts-md5/dist/md5';
|
import { Md5 } from 'ts-md5/dist/md5';
|
||||||
import * as moment from 'moment';
|
import * as moment from 'moment';
|
||||||
|
|
||||||
|
@ -53,12 +54,16 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
|
||||||
protected syncObserver: any;
|
protected syncObserver: any;
|
||||||
protected oldContentHeight = 0;
|
protected oldContentHeight = 0;
|
||||||
protected keyboardObserver: any;
|
protected keyboardObserver: any;
|
||||||
|
protected scrollBottom = true;
|
||||||
|
protected viewDestroyed = false;
|
||||||
|
|
||||||
userId: number;
|
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;
|
currentUserId: number;
|
||||||
title: string;
|
title: string;
|
||||||
profileLink: string;
|
showInfo: boolean;
|
||||||
showProfileLink: boolean;
|
conversationImage: string;
|
||||||
loaded = false;
|
loaded = false;
|
||||||
showKeyboard = false;
|
showKeyboard = false;
|
||||||
canLoadMore = false;
|
canLoadMore = false;
|
||||||
|
@ -66,26 +71,30 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
|
||||||
messages = [];
|
messages = [];
|
||||||
showDelete = false;
|
showDelete = false;
|
||||||
canDelete = false;
|
canDelete = false;
|
||||||
scrollBottom = true;
|
groupMessagingEnabled: boolean;
|
||||||
viewDestroyed = false;
|
isGroup = false;
|
||||||
|
|
||||||
constructor(private eventsProvider: CoreEventsProvider, sitesProvider: CoreSitesProvider, navParams: NavParams,
|
constructor(private eventsProvider: CoreEventsProvider, sitesProvider: CoreSitesProvider, navParams: NavParams,
|
||||||
private userProvider: CoreUserProvider, private navCtrl: NavController, private messagesSync: AddonMessagesSyncProvider,
|
private userProvider: CoreUserProvider, private navCtrl: NavController, private messagesSync: AddonMessagesSyncProvider,
|
||||||
private domUtils: CoreDomUtilsProvider, private messagesProvider: AddonMessagesProvider, logger: CoreLoggerProvider,
|
private domUtils: CoreDomUtilsProvider, private messagesProvider: AddonMessagesProvider, logger: CoreLoggerProvider,
|
||||||
private utils: CoreUtilsProvider, private appProvider: CoreAppProvider, private translate: TranslateService) {
|
private utils: CoreUtilsProvider, private appProvider: CoreAppProvider, private translate: TranslateService,
|
||||||
|
@Optional() private svComponent: CoreSplitViewComponent) {
|
||||||
this.siteId = sitesProvider.getCurrentSiteId();
|
this.siteId = sitesProvider.getCurrentSiteId();
|
||||||
this.currentUserId = sitesProvider.getCurrentSiteUserId();
|
this.currentUserId = sitesProvider.getCurrentSiteUserId();
|
||||||
|
this.groupMessagingEnabled = this.messagesProvider.isGroupMessagingEnabled();
|
||||||
|
|
||||||
this.logger = logger.getInstance('AddonMessagesDiscussionPage');
|
this.logger = logger.getInstance('AddonMessagesDiscussionPage');
|
||||||
|
|
||||||
|
this.conversationId = navParams.get('conversationId');
|
||||||
this.userId = navParams.get('userId');
|
this.userId = navParams.get('userId');
|
||||||
this.showKeyboard = navParams.get('showKeyboard');
|
this.showKeyboard = navParams.get('showKeyboard');
|
||||||
|
|
||||||
// Refresh data if this discussion is synchronized automatically.
|
// Refresh data if this discussion is synchronized automatically.
|
||||||
this.syncObserver = eventsProvider.on(AddonMessagesSyncProvider.AUTO_SYNCED, (data) => {
|
this.syncObserver = eventsProvider.on(AddonMessagesSyncProvider.AUTO_SYNCED, (data) => {
|
||||||
if (data.userId == this.userId) {
|
if ((data.userId && data.userId == this.userId) ||
|
||||||
|
(data.conversationId && data.conversationId == this.conversationId)) {
|
||||||
// Fetch messages.
|
// Fetch messages.
|
||||||
this.fetchData();
|
this.fetchMessages();
|
||||||
|
|
||||||
// Show first warning if any.
|
// Show first warning if any.
|
||||||
if (data.warnings && data.warnings[0]) {
|
if (data.warnings && data.warnings[0]) {
|
||||||
|
@ -102,8 +111,8 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
|
||||||
* @param {boolean} [keep=true] If set the keep flag or not.
|
* @param {boolean} [keep=true] If set the keep flag or not.
|
||||||
*/
|
*/
|
||||||
protected addMessage(message: any, keep: boolean = true): void {
|
protected addMessage(message: any, keep: boolean = true): void {
|
||||||
// Use smallmessage instead of message ID because ID changes when a message is read.
|
// Use text instead of message ID because ID changes when a message is read.
|
||||||
message.hash = Md5.hashAsciiStr(message.smallmessage) + '#' + message.timecreated + '#' + message.useridfrom;
|
message.hash = Md5.hashAsciiStr(message.text || '') + '#' + message.timecreated + '#' + message.useridfrom;
|
||||||
if (typeof this.keepMessageMap[message.hash] === 'undefined') {
|
if (typeof this.keepMessageMap[message.hash] === 'undefined') {
|
||||||
// Message not added to the list. Add it now.
|
// Message not added to the list. Add it now.
|
||||||
this.messages.push(message);
|
this.messages.push(message);
|
||||||
|
@ -143,15 +152,17 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
|
||||||
ionViewDidLoad(): void {
|
ionViewDidLoad(): void {
|
||||||
// Disable the profile button if we're already coming from a profile.
|
// Disable the profile button if we're already coming from a profile.
|
||||||
const backViewPage = this.navCtrl.getPrevious() && this.navCtrl.getPrevious().component.name;
|
const backViewPage = this.navCtrl.getPrevious() && this.navCtrl.getPrevious().component.name;
|
||||||
this.showProfileLink = !backViewPage || backViewPage !== 'CoreUserProfilePage';
|
this.showInfo = !backViewPage || backViewPage !== 'CoreUserProfilePage';
|
||||||
|
|
||||||
// Get the user profile to retrieve the user fullname and image.
|
if (!this.groupMessagingEnabled && this.userId) {
|
||||||
this.userProvider.getProfile(this.userId, undefined, true).then((user) => {
|
// Get the user profile to retrieve the user fullname and image.
|
||||||
if (!this.title) {
|
this.userProvider.getProfile(this.userId, undefined, true).then((user) => {
|
||||||
this.title = user.fullname;
|
if (!this.title) {
|
||||||
}
|
this.title = user.fullname;
|
||||||
this.profileLink = user.profileimageurl;
|
}
|
||||||
});
|
this.conversationImage = user.profileimageurl;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Synchronize messages if needed.
|
// Synchronize messages if needed.
|
||||||
this.messagesSync.syncDiscussion(this.userId).catch(() => {
|
this.messagesSync.syncDiscussion(this.userId).catch(() => {
|
||||||
|
@ -161,24 +172,34 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
|
||||||
this.domUtils.showErrorModal(warnings[0]);
|
this.domUtils.showErrorModal(warnings[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch the messages for the first time.
|
if (this.groupMessagingEnabled) {
|
||||||
return this.fetchData().then(() => {
|
// Get the conversation ID if it exists and we don't have it yet.
|
||||||
if (!this.title && this.messages.length) {
|
return this.getConversation().then((exists) => {
|
||||||
// Didn't receive the fullname via argument. Try to get it from messages.
|
if (exists) {
|
||||||
// It's possible that name cannot be resolved when no messages were yet exchanged.
|
// Fetch the messages for the first time.
|
||||||
if (this.messages[0].useridto != this.currentUserId) {
|
return this.fetchMessages();
|
||||||
this.title = this.messages[0].usertofullname || '';
|
|
||||||
} else {
|
|
||||||
this.title = this.messages[0].userfromfullname || '';
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}).catch((error) => {
|
} else {
|
||||||
this.domUtils.showErrorModalDefault(error, 'addon.messages.errorwhileretrievingmessages', true);
|
// Fetch the messages for the first time.
|
||||||
}).finally(() => {
|
return this.fetchMessages().then(() => {
|
||||||
this.checkCanDelete();
|
if (!this.title && this.messages.length) {
|
||||||
this.resizeContent();
|
// Didn't receive the fullname via argument. Try to get it from messages.
|
||||||
this.loaded = true;
|
// 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;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Recalculate footer position when keyboard is shown or hidden.
|
// Recalculate footer position when keyboard is shown or hidden.
|
||||||
|
@ -204,12 +225,12 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience function to fetch messages.
|
* Convenience function to fetch messages.
|
||||||
|
*
|
||||||
* @return {Promise<any>} Resolved when done.
|
* @return {Promise<any>} Resolved when done.
|
||||||
*/
|
*/
|
||||||
protected fetchData(): Promise<any> {
|
protected fetchMessages(): Promise<any> {
|
||||||
this.loadMoreError = false;
|
this.loadMoreError = false;
|
||||||
|
|
||||||
this.logger.debug(`Polling new messages for discussion with user '${this.userId}'`);
|
|
||||||
if (this.messagesBeingSent > 0) {
|
if (this.messagesBeingSent > 0) {
|
||||||
// We do not poll while a message is being sent or we could confuse the user.
|
// 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.
|
// Otherwise, his message would disappear from the list, and he'd have to wait for the interval to check for messages.
|
||||||
|
@ -217,6 +238,15 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
|
||||||
} else if (this.fetching) {
|
} else if (this.fetching) {
|
||||||
// Already fetching.
|
// Already fetching.
|
||||||
return Promise.reject(null);
|
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;
|
this.fetching = true;
|
||||||
|
@ -224,11 +254,19 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
|
||||||
// Wait for synchronization process to finish.
|
// Wait for synchronization process to finish.
|
||||||
return this.messagesSync.waitForSync(this.userId).then(() => {
|
return this.messagesSync.waitForSync(this.userId).then(() => {
|
||||||
// Fetch messages. Invalidate the cache before fetching.
|
// Fetch messages. Invalidate the cache before fetching.
|
||||||
return this.messagesProvider.invalidateDiscussionCache(this.userId).catch(() => {
|
if (this.groupMessagingEnabled) {
|
||||||
// Ignore errors.
|
return this.messagesProvider.invalidateConversationMessages(this.conversationId).catch(() => {
|
||||||
});
|
// Ignore errors.
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
return this.getDiscussion(this.pagesLoaded);
|
return this.getConversationMessages(this.pagesLoaded);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return this.messagesProvider.invalidateDiscussionCache(this.userId).catch(() => {
|
||||||
|
// Ignore errors.
|
||||||
|
}).then(() => {
|
||||||
|
return this.getDiscussionMessages(this.pagesLoaded);
|
||||||
|
});
|
||||||
|
}
|
||||||
}).then((messages) => {
|
}).then((messages) => {
|
||||||
if (this.viewDestroyed) {
|
if (this.viewDestroyed) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
|
@ -261,11 +299,83 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
|
||||||
|
|
||||||
// Mark retrieved messages as read if they are not.
|
// Mark retrieved messages as read if they are not.
|
||||||
this.markMessagesAsRead();
|
this.markMessagesAsRead();
|
||||||
|
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
this.fetching = false;
|
this.fetching = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the conversation.
|
||||||
|
*
|
||||||
|
* @return {Promise<boolean>} Promise resolved with a boolean: whether the conversation exists or not.
|
||||||
|
*/
|
||||||
|
protected getConversation(): Promise<boolean> {
|
||||||
|
let promise;
|
||||||
|
|
||||||
|
if (this.conversationId) {
|
||||||
|
// Retrieve the conversation. Invalidate data first to get the right unreadcount.
|
||||||
|
promise = this.messagesProvider.invalidateConversation(this.conversationId).then(() => {
|
||||||
|
return this.messagesProvider.getConversation(this.conversationId);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// We don't have the conversation ID, check if it exists.
|
||||||
|
promise = this.messagesProvider.getConversationBetweenUsers(this.userId).catch((error) => {
|
||||||
|
if (error.errorcode == 'conversationdoesntexist') {
|
||||||
|
// Conversation does not exist, return undefined.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.reject(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return promise.then((conversation) => {
|
||||||
|
this.conversation = conversation;
|
||||||
|
if (conversation) {
|
||||||
|
this.title = conversation.name;
|
||||||
|
this.conversationImage = conversation.imageurl;
|
||||||
|
this.isGroup = conversation.type == AddonMessagesProvider.MESSAGE_CONVERSATION_TYPE_GROUP;
|
||||||
|
if (!this.isGroup) {
|
||||||
|
this.userId = conversation.userid;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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--;
|
||||||
|
|
||||||
|
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".
|
* Get a discussion. Can load several "pages".
|
||||||
*
|
*
|
||||||
|
@ -276,8 +386,8 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
|
||||||
* @param {number} [lfSentRead=0] Number of read 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.
|
* @return {Promise<any>} Resolved when done.
|
||||||
*/
|
*/
|
||||||
protected getDiscussion(pagesToLoad: number, lfReceivedUnread: number = 0, lfReceivedRead: number = 0, lfSentUnread: number = 0,
|
protected getDiscussionMessages(pagesToLoad: number, lfReceivedUnread: number = 0, lfReceivedRead: number = 0,
|
||||||
lfSentRead: number = 0): Promise<any> {
|
lfSentUnread: number = 0, lfSentRead: number = 0): Promise<any> {
|
||||||
|
|
||||||
// Only get offline messages if we're loading the first "page".
|
// Only get offline messages if we're loading the first "page".
|
||||||
const excludePending = lfReceivedUnread > 0 || lfReceivedRead > 0 || lfSentUnread > 0 || lfSentRead > 0;
|
const excludePending = lfReceivedUnread > 0 || lfReceivedRead > 0 || lfSentUnread > 0 || lfSentRead > 0;
|
||||||
|
@ -308,7 +418,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Get next messages.
|
// Get next messages.
|
||||||
return this.getDiscussion(pagesToLoad, lfReceivedUnread, lfReceivedRead, lfSentUnread, lfSentRead)
|
return this.getDiscussionMessages(pagesToLoad, lfReceivedUnread, lfReceivedRead, lfSentUnread, lfSentRead)
|
||||||
.then((nextMessages) => {
|
.then((nextMessages) => {
|
||||||
return result.messages.concat(nextMessages);
|
return result.messages.concat(nextMessages);
|
||||||
});
|
});
|
||||||
|
@ -330,23 +440,39 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
|
||||||
|
|
||||||
if (this.messagesProvider.isMarkAllMessagesReadEnabled()) {
|
if (this.messagesProvider.isMarkAllMessagesReadEnabled()) {
|
||||||
let messageUnreadFound = false;
|
let messageUnreadFound = false;
|
||||||
// Mark all messages at a time if one messages is unread.
|
|
||||||
for (const x in this.messages) {
|
// Mark all messages at a time if there is any unread message.
|
||||||
const message = this.messages[x];
|
if (this.groupMessagingEnabled) {
|
||||||
// If an unread message is found, mark all messages as read.
|
messageUnreadFound = this.conversation && this.conversation.unreadcount > 0 && this.conversationId > 0;
|
||||||
if (message.useridfrom != this.currentUserId && message.read == 0) {
|
} else {
|
||||||
messageUnreadFound = true;
|
for (const x in this.messages) {
|
||||||
break;
|
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) {
|
if (messageUnreadFound) {
|
||||||
this.setUnreadLabelPosition();
|
this.setUnreadLabelPosition();
|
||||||
promises.push(this.messagesProvider.markAllMessagesRead(this.userId).then(() => {
|
|
||||||
readChanged = true;
|
let promise;
|
||||||
// Mark all messages as read.
|
|
||||||
this.messages.forEach((message) => {
|
if (this.groupMessagingEnabled) {
|
||||||
message.read = 1;
|
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 {
|
} else {
|
||||||
|
@ -366,6 +492,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
|
||||||
Promise.all(promises).finally(() => {
|
Promise.all(promises).finally(() => {
|
||||||
if (readChanged) {
|
if (readChanged) {
|
||||||
this.eventsProvider.trigger(AddonMessagesProvider.READ_CHANGED_EVENT, {
|
this.eventsProvider.trigger(AddonMessagesProvider.READ_CHANGED_EVENT, {
|
||||||
|
conversationId: this.conversationId,
|
||||||
userId: this.userId
|
userId: this.userId
|
||||||
}, this.siteId);
|
}, this.siteId);
|
||||||
}
|
}
|
||||||
|
@ -390,6 +517,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
|
||||||
if (trigger) {
|
if (trigger) {
|
||||||
// Update discussions last message.
|
// Update discussions last message.
|
||||||
this.eventsProvider.trigger(AddonMessagesProvider.NEW_MESSAGE_EVENT, {
|
this.eventsProvider.trigger(AddonMessagesProvider.NEW_MESSAGE_EVENT, {
|
||||||
|
conversationId: this.conversationId,
|
||||||
userId: this.userId,
|
userId: this.userId,
|
||||||
message: this.lastMessage.text,
|
message: this.lastMessage.text,
|
||||||
timecreated: this.lastMessage.timecreated
|
timecreated: this.lastMessage.timecreated
|
||||||
|
@ -411,21 +539,39 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let previousMessageRead = false;
|
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 (const x in this.messages) {
|
for (let i = this.messages.length - 1; i >= 0; i--) {
|
||||||
const message = this.messages[x];
|
const message = this.messages[i];
|
||||||
if (message.useridfrom != this.currentUserId) {
|
if (!message.pending && message.useridfrom != this.currentUserId) {
|
||||||
// Place unread from message label only once.
|
found++;
|
||||||
message.unreadFrom = message.read == 0 && previousMessageRead;
|
if (found == this.conversation.unreadcount) {
|
||||||
|
this.unreadMessageFrom = parseInt(message.id, 10);
|
||||||
if (message.unreadFrom) {
|
break;
|
||||||
// Save where the label is placed.
|
}
|
||||||
this.unreadMessageFrom = parseInt(message.id, 10);
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let previousMessageRead = false;
|
||||||
|
|
||||||
previousMessageRead = message.read != 0;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -450,15 +596,6 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
|
||||||
*/
|
*/
|
||||||
protected hideUnreadLabel(): void {
|
protected hideUnreadLabel(): void {
|
||||||
if (this.unreadMessageFrom > 0) {
|
if (this.unreadMessageFrom > 0) {
|
||||||
for (const x in this.messages) {
|
|
||||||
const message = this.messages[x];
|
|
||||||
if (message.id == this.unreadMessageFrom) {
|
|
||||||
message.unreadFrom = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Label hidden.
|
|
||||||
this.unreadMessageFrom = -1;
|
this.unreadMessageFrom = -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -487,10 +624,15 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
|
||||||
* Set a polling to get new messages every certain time.
|
* Set a polling to get new messages every certain time.
|
||||||
*/
|
*/
|
||||||
protected setPolling(): void {
|
protected setPolling(): void {
|
||||||
|
if (this.groupMessagingEnabled && !this.conversationId) {
|
||||||
|
// Don't have enough data to poll messages.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.polling) {
|
if (!this.polling) {
|
||||||
// Start polling.
|
// Start polling.
|
||||||
this.polling = setInterval(() => {
|
this.polling = setInterval(() => {
|
||||||
this.fetchData().catch(() => {
|
this.fetchMessages().catch(() => {
|
||||||
// Ignore errors.
|
// Ignore errors.
|
||||||
});
|
});
|
||||||
}, AddonMessagesProvider.POLL_INTERVAL);
|
}, AddonMessagesProvider.POLL_INTERVAL);
|
||||||
|
@ -509,12 +651,12 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copy message to clipboard
|
* Copy message to clipboard.
|
||||||
*
|
*
|
||||||
* @param {string} text Message text to be copied.
|
* @param {any} message Message to be copied.
|
||||||
*/
|
*/
|
||||||
copyMessage(text: string): void {
|
copyMessage(message: any): void {
|
||||||
this.utils.copyToClipboard(text);
|
this.utils.copyToClipboard(message.smallmessage || message.text || '');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -534,7 +676,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
|
||||||
this.removeMessage(message.hash);
|
this.removeMessage(message.hash);
|
||||||
this.notifyNewMessage();
|
this.notifyNewMessage();
|
||||||
|
|
||||||
this.fetchData(); // Re-fetch messages to update cached data.
|
this.fetchMessages(); // Re-fetch messages to update cached data.
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
modal.dismiss();
|
modal.dismiss();
|
||||||
});
|
});
|
||||||
|
@ -554,9 +696,8 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
|
||||||
return this.waitForFetch().finally(() => {
|
return this.waitForFetch().finally(() => {
|
||||||
this.pagesLoaded++;
|
this.pagesLoaded++;
|
||||||
|
|
||||||
this.fetchData().catch((error) => {
|
this.fetchMessages().catch((error) => {
|
||||||
this.loadMoreError = true; // Set to prevent infinite calls with infinite-loading.
|
this.loadMoreError = true; // Set to prevent infinite calls with infinite-loading.
|
||||||
|
|
||||||
this.pagesLoaded--;
|
this.pagesLoaded--;
|
||||||
this.domUtils.showErrorModalDefault(error, 'addon.messages.errorwhileretrievingmessages', true);
|
this.domUtils.showErrorModalDefault(error, 'addon.messages.errorwhileretrievingmessages', true);
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
|
@ -638,7 +779,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
|
||||||
|
|
||||||
if (data.sent) {
|
if (data.sent) {
|
||||||
// Message was sent, fetch messages right now.
|
// Message was sent, fetch messages right now.
|
||||||
promise = this.fetchData();
|
promise = this.fetchMessages();
|
||||||
} else {
|
} else {
|
||||||
promise = Promise.reject(null);
|
promise = Promise.reject(null);
|
||||||
}
|
}
|
||||||
|
@ -697,6 +838,19 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
|
||||||
this.showDelete = !this.showDelete;
|
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) {
|
||||||
|
// @todo
|
||||||
|
} else {
|
||||||
|
const navCtrl = this.svComponent ? this.svComponent.getMasterNav() : this.navCtrl;
|
||||||
|
navCtrl.push('CoreUserProfilePage', { userId: this.userId });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Page destroyed.
|
* Page destroyed.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -8,6 +8,8 @@
|
||||||
<button ion-button icon-only (click)="gotoSettings($event)" [attr.aria-label]="'addon.messages.messagepreferences' | translate">
|
<button ion-button icon-only (click)="gotoSettings($event)" [attr.aria-label]="'addon.messages.messagepreferences' | translate">
|
||||||
<ion-icon name="cog"></ion-icon>
|
<ion-icon name="cog"></ion-icon>
|
||||||
</button>
|
</button>
|
||||||
|
<!-- Add an empty context menu so discussion page can add items in split view, otherwise the menu disappears in some cases. -->
|
||||||
|
<core-context-menu></core-context-menu>
|
||||||
</ion-buttons>
|
</ion-buttons>
|
||||||
</ion-navbar>
|
</ion-navbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
|
@ -94,7 +94,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
|
||||||
} else {
|
} else {
|
||||||
// An existing conversation has a new message, update the last message.
|
// An existing conversation has a new message, update the last message.
|
||||||
conversation.lastmessage = data.message;
|
conversation.lastmessage = data.message;
|
||||||
conversation.lastmessagedate = data.timecreated;
|
conversation.lastmessagedate = data.timecreated / 1000;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, this.siteId);
|
}, this.siteId);
|
||||||
|
@ -145,7 +145,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
if (this.conversationId) {
|
if (this.conversationId) {
|
||||||
// There is a discussion to load, open the discussion in a new state.
|
// There is a discussion to load, open the discussion in a new state.
|
||||||
this.gotoConversation(this.conversationId, null);
|
this.gotoConversation(this.conversationId);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.fetchData().then(() => {
|
this.fetchData().then(() => {
|
||||||
|
@ -162,7 +162,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (conversation) {
|
if (conversation) {
|
||||||
this.gotoConversation(conversation.id, conversation.userid);
|
this.gotoConversation(conversation.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -247,13 +247,14 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
|
||||||
* Navigate to a particular conversation.
|
* Navigate to a particular conversation.
|
||||||
*
|
*
|
||||||
* @param {number} conversationId Conversation Id to load.
|
* @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} userId User of the conversation. Only if there is no conversationId.
|
||||||
* @param {number} [messageId] Message to scroll after loading the discussion. Used when searching.
|
* @param {number} [messageId] Message to scroll after loading the discussion. Used when searching.
|
||||||
*/
|
*/
|
||||||
gotoConversation(conversationId: number, userId: number, messageId?: number): void {
|
gotoConversation(conversationId: number, userId?: number, messageId?: number): void {
|
||||||
this.selectedConversation = conversationId;
|
this.selectedConversation = conversationId;
|
||||||
|
|
||||||
const params = {
|
const params = {
|
||||||
|
conversationId: conversationId,
|
||||||
userId: userId
|
userId: userId
|
||||||
};
|
};
|
||||||
if (messageId) {
|
if (messageId) {
|
||||||
|
@ -358,7 +359,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
|
||||||
* Page destroyed.
|
* Page destroyed.
|
||||||
*/
|
*/
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
this.newMessagesObserver && this.newMessagesObserver.unsubscribe();
|
this.newMessagesObserver && this.newMessagesObserver.off();
|
||||||
this.appResumeSubscription && this.appResumeSubscription.unsubscribe();
|
this.appResumeSubscription && this.appResumeSubscription.unsubscribe();
|
||||||
this.pushObserver && this.pushObserver.unsubscribe();
|
this.pushObserver && this.pushObserver.unsubscribe();
|
||||||
this.readChangedObserver && this.readChangedObserver.off();
|
this.readChangedObserver && this.readChangedObserver.off();
|
||||||
|
|
|
@ -139,6 +139,41 @@ export class AddonMessagesProvider {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format a conversation.
|
||||||
|
*
|
||||||
|
* @param {any} conversation Conversation to format.
|
||||||
|
* @param {number} userId User ID viewing the conversation.
|
||||||
|
* @return {any} Formatted conversation.
|
||||||
|
*/
|
||||||
|
protected formatConversation(conversation: any, userId: number): any {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the cache key for blocked contacts.
|
* Get the cache key for blocked contacts.
|
||||||
*
|
*
|
||||||
|
@ -187,6 +222,39 @@ export class AddonMessagesProvider {
|
||||||
return this.ROOT_CACHE_KEY + 'discussions';
|
return this.ROOT_CACHE_KEY + 'discussions';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get cache key for get conversations.
|
||||||
|
*
|
||||||
|
* @param {number} userId User ID.
|
||||||
|
* @param {number} conversationId Conversation ID.
|
||||||
|
* @return {string} Cache key.
|
||||||
|
*/
|
||||||
|
protected getCacheKeyForConversation(userId: number, conversationId: number): string {
|
||||||
|
return this.ROOT_CACHE_KEY + 'conversation:' + userId + ':' + conversationId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get cache key for get conversations between users.
|
||||||
|
*
|
||||||
|
* @param {number} userId User ID.
|
||||||
|
* @param {number} otherUserId Other user ID.
|
||||||
|
* @return {string} Cache key.
|
||||||
|
*/
|
||||||
|
protected getCacheKeyForConversationBetweenUsers(userId: number, otherUserId: number): string {
|
||||||
|
return this.ROOT_CACHE_KEY + 'conversationBetweenUsers:' + userId + ':' + otherUserId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get cache key for get conversation messages.
|
||||||
|
*
|
||||||
|
* @param {number} userId User ID.
|
||||||
|
* @param {number} conversationId Conversation ID.
|
||||||
|
* @return {string} Cache key.
|
||||||
|
*/
|
||||||
|
protected getCacheKeyForConversationMessages(userId: number, conversationId: number): string {
|
||||||
|
return this.ROOT_CACHE_KEY + 'conversationMessages:' + userId + ':' + conversationId;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get cache key for get conversations.
|
* Get cache key for get conversations.
|
||||||
*
|
*
|
||||||
|
@ -297,6 +365,182 @@ export class AddonMessagesProvider {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a conversation by the conversation ID.
|
||||||
|
*
|
||||||
|
* @param {number} conversationId Conversation ID to fetch.
|
||||||
|
* @param {boolean} [includeContactRequests] Include contact requests.
|
||||||
|
* @param {boolean} [includePrivacyInfo] Include privacy info.
|
||||||
|
* @param {number} [messageOffset=0] Offset for messages list.
|
||||||
|
* @param {number} [messageLimit=1] Limit of messages. Defaults to 1 (last message).
|
||||||
|
* We recommend getConversationMessages to get them.
|
||||||
|
* @param {number} [memberOffset=0] Offset for members list.
|
||||||
|
* @param {number} [memberLimit=2] Limit of members. Defaults to 2 (to be able to know the other user in individual ones).
|
||||||
|
* We recommend getConversationMembers to get them.
|
||||||
|
* @param {boolean} [newestFirst=true] Whether to order messages by newest first.
|
||||||
|
* @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<any>} Promise resolved with the response.
|
||||||
|
* @since 3.6
|
||||||
|
*/
|
||||||
|
getConversation(conversationId: number, includeContactRequests?: boolean, includePrivacyInfo?: boolean,
|
||||||
|
messageOffset: number = 0, messageLimit: number = 1, memberOffset: number = 0, memberLimit: number = 2,
|
||||||
|
newestFirst: boolean = true, siteId?: string, userId?: number): Promise<any> {
|
||||||
|
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
userId = userId || site.getUserId();
|
||||||
|
|
||||||
|
const preSets = {
|
||||||
|
cacheKey: this.getCacheKeyForConversation(userId, conversationId)
|
||||||
|
},
|
||||||
|
params: any = {
|
||||||
|
userid: userId,
|
||||||
|
conversationid: conversationId,
|
||||||
|
includecontactrequests: includeContactRequests ? 1 : 0,
|
||||||
|
includeprivacyinfo: includePrivacyInfo ? 1 : 0,
|
||||||
|
messageoffset: messageOffset,
|
||||||
|
messagelimit: messageLimit,
|
||||||
|
memberoffset: memberOffset,
|
||||||
|
memberlimit: memberLimit,
|
||||||
|
newestmessagesfirst: newestFirst ? 1 : 0
|
||||||
|
};
|
||||||
|
|
||||||
|
return site.read('core_message_get_conversation', params, preSets).then((conversation) => {
|
||||||
|
return this.formatConversation(conversation, userId);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a conversation between two users.
|
||||||
|
*
|
||||||
|
* @param {number} otherUserId The other user ID.
|
||||||
|
* @param {boolean} [includeContactRequests] Include contact requests.
|
||||||
|
* @param {boolean} [includePrivacyInfo] Include privacy info.
|
||||||
|
* @param {number} [messageOffset=0] Offset for messages list.
|
||||||
|
* @param {number} [messageLimit=1] Limit of messages. Defaults to 1 (last message).
|
||||||
|
* We recommend getConversationMessages to get them.
|
||||||
|
* @param {number} [memberOffset=0] Offset for members list.
|
||||||
|
* @param {number} [memberLimit=2] Limit of members. Defaults to 2 (to be able to know the other user in individual ones).
|
||||||
|
* We recommend getConversationMembers to get them.
|
||||||
|
* @param {boolean} [newestFirst=true] Whether to order messages by newest first.
|
||||||
|
* @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<any>} Promise resolved with the response.
|
||||||
|
* @since 3.6
|
||||||
|
*/
|
||||||
|
getConversationBetweenUsers(otherUserId: number, includeContactRequests?: boolean, includePrivacyInfo?: boolean,
|
||||||
|
messageOffset: number = 0, messageLimit: number = 1, memberOffset: number = 0, memberLimit: number = 2,
|
||||||
|
newestFirst: boolean = true, siteId?: string, userId?: number): Promise<any> {
|
||||||
|
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
userId = userId || site.getUserId();
|
||||||
|
|
||||||
|
const preSets = {
|
||||||
|
cacheKey: this.getCacheKeyForConversationBetweenUsers(userId, otherUserId)
|
||||||
|
},
|
||||||
|
params: any = {
|
||||||
|
userid: userId,
|
||||||
|
otheruserid: otherUserId,
|
||||||
|
includecontactrequests: includeContactRequests ? 1 : 0,
|
||||||
|
includeprivacyinfo: includePrivacyInfo ? 1 : 0,
|
||||||
|
messageoffset: messageOffset,
|
||||||
|
messagelimit: messageLimit,
|
||||||
|
memberoffset: memberOffset,
|
||||||
|
memberlimit: memberLimit,
|
||||||
|
newestmessagesfirst: newestFirst ? 1 : 0
|
||||||
|
};
|
||||||
|
|
||||||
|
return site.read('core_message_get_conversation_between_users', params, preSets).then((conversation) => {
|
||||||
|
return this.formatConversation(conversation, userId);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a conversation by the conversation ID.
|
||||||
|
*
|
||||||
|
* @param {number} conversationId Conversation ID to fetch.
|
||||||
|
* @param {boolean} excludePending True to exclude messages pending to be sent.
|
||||||
|
* @param {number} [limitFrom=0] Offset for messages list.
|
||||||
|
* @param {number} [limitTo] Limit of messages.
|
||||||
|
* @param {boolean} [newestFirst=true] Whether to order messages by newest first.
|
||||||
|
* @param {number} [timeFrom] The timestamp from which the messages were created.
|
||||||
|
* @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<any>} Promise resolved with the response.
|
||||||
|
* @since 3.6
|
||||||
|
*/
|
||||||
|
getConversationMessages(conversationId: number, excludePending: boolean, limitFrom: number = 0, limitTo?: number,
|
||||||
|
newestFirst: boolean = true, timeFrom: number = 0, siteId?: string, userId?: number): Promise<any> {
|
||||||
|
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
userId = userId || site.getUserId();
|
||||||
|
|
||||||
|
if (typeof limitTo == 'undefined' || limitTo === null) {
|
||||||
|
limitTo = this.LIMIT_MESSAGES;
|
||||||
|
}
|
||||||
|
|
||||||
|
const preSets = {
|
||||||
|
cacheKey: this.getCacheKeyForConversationMessages(userId, conversationId)
|
||||||
|
},
|
||||||
|
params: any = {
|
||||||
|
currentuserid: userId,
|
||||||
|
convid: conversationId,
|
||||||
|
limitfrom: limitFrom,
|
||||||
|
limitnum: limitTo < 1 ? limitTo : limitTo + 1, // If there is a limit, get 1 more than requested.
|
||||||
|
newest: newestFirst ? 1 : 0,
|
||||||
|
timefrom: timeFrom
|
||||||
|
};
|
||||||
|
|
||||||
|
if (limitFrom > 0) {
|
||||||
|
// Do not use cache when retrieving older messages.
|
||||||
|
// This is to prevent storing too much data and to prevent inconsistencies between "pages" loaded.
|
||||||
|
preSets['getFromCache'] = false;
|
||||||
|
preSets['saveToCache'] = false;
|
||||||
|
preSets['emergencyCache'] = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return site.read('core_message_get_conversation_messages', params, preSets).then((result) => {
|
||||||
|
if (limitTo < 1) {
|
||||||
|
result.canLoadMore = false;
|
||||||
|
result.messages = result.messages;
|
||||||
|
} else {
|
||||||
|
result.canLoadMore = result.messages.length > limitTo;
|
||||||
|
result.messages = result.messages.slice(0, limitTo);
|
||||||
|
}
|
||||||
|
|
||||||
|
result.messages.forEach((message) => {
|
||||||
|
// Convert time to milliseconds.
|
||||||
|
message.timecreated = message.timecreated ? message.timecreated * 1000 : 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.appProvider.isDesktop() && params.useridto == userId && limitFrom === 0) {
|
||||||
|
// Store the last received message (we cannot know if it's unread or not). Don't block the user for this.
|
||||||
|
this.storeLastReceivedMessageIfNeeded(conversationId, result.messages[0], site.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (excludePending) {
|
||||||
|
// No need to get offline messages, return the ones we have.
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get offline messages.
|
||||||
|
return this.messagesOffline.getMessages(userId).then((offlineMessages) => {
|
||||||
|
// Mark offline messages as pending.
|
||||||
|
offlineMessages.forEach((message) => {
|
||||||
|
message.pending = true;
|
||||||
|
message.text = message.smallmessage;
|
||||||
|
});
|
||||||
|
|
||||||
|
result.messages = result.messages.concat(offlineMessages);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the discussions of a certain user. This function is used in Moodle sites higher than 3.6.
|
* 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.
|
* If the site is older than 3.6, please use getDiscussions.
|
||||||
|
@ -308,6 +552,7 @@ export class AddonMessagesProvider {
|
||||||
* @param {string} [siteId] Site ID. If not defined, use current site.
|
* @param {string} [siteId] Site ID. If not defined, use current site.
|
||||||
* @param {number} [userId] User ID. If not defined, current user in the site.
|
* @param {number} [userId] User ID. If not defined, current user in the site.
|
||||||
* @return {Promise<any>} Promise resolved with the conversations.
|
* @return {Promise<any>} Promise resolved with the conversations.
|
||||||
|
* @since 3.6
|
||||||
*/
|
*/
|
||||||
getConversations(type?: number, favourites?: boolean, limitFrom: number = 0, siteId?: string, userId?: number)
|
getConversations(type?: number, favourites?: boolean, limitFrom: number = 0, siteId?: string, userId?: number)
|
||||||
: Promise<{conversations: any[], canLoadMore: boolean}> {
|
: Promise<{conversations: any[], canLoadMore: boolean}> {
|
||||||
|
@ -333,32 +578,8 @@ export class AddonMessagesProvider {
|
||||||
|
|
||||||
return site.read('core_message_get_conversations', params, preSets).then((response) => {
|
return site.read('core_message_get_conversations', params, preSets).then((response) => {
|
||||||
// Format the conversations, adding some calculated fields.
|
// Format the conversations, adding some calculated fields.
|
||||||
const conversations = response.conversations.map((conversation) => {
|
const conversations = response.conversations.slice(0, this.LIMIT_MESSAGES).map((conversation) => {
|
||||||
const numMessages = conversation.messages.length,
|
return this.formatConversation(conversation, userId);
|
||||||
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 {
|
return {
|
||||||
|
@ -813,7 +1034,55 @@ export class AddonMessagesProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invalidate contacts cache.
|
* Invalidate conversation.
|
||||||
|
*
|
||||||
|
* @param {number} conversationId Conversation ID.
|
||||||
|
* @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<any>} Resolved when done.
|
||||||
|
*/
|
||||||
|
invalidateConversation(conversationId: number, siteId?: string, userId?: number): Promise<any> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
userId = userId || site.getUserId();
|
||||||
|
|
||||||
|
return site.invalidateWsCacheForKey(this.getCacheKeyForConversation(conversationId, userId));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidate conversation between users.
|
||||||
|
*
|
||||||
|
* @param {number} otherUserId Other user ID.
|
||||||
|
* @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<any>} Resolved when done.
|
||||||
|
*/
|
||||||
|
invalidateConversationBetweenUsers(otherUserId: number, siteId?: string, userId?: number): Promise<any> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
userId = userId || site.getUserId();
|
||||||
|
|
||||||
|
return site.invalidateWsCacheForKey(this.getCacheKeyForConversationBetweenUsers(userId, otherUserId));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidate conversation messages cache.
|
||||||
|
*
|
||||||
|
* @param {number} conversationId Conversation ID.
|
||||||
|
* @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<any>} Resolved when done.
|
||||||
|
*/
|
||||||
|
invalidateConversationMessages(conversationId: number, siteId?: string, userId?: number): Promise<any> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
userId = userId || site.getUserId();
|
||||||
|
|
||||||
|
return site.invalidateWsCacheForKey(this.getCacheKeyForConversationMessages(userId, conversationId));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidate conversations cache.
|
||||||
*
|
*
|
||||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
* @param {number} [userId] User ID. If not defined, current user in the site.
|
* @param {number} [userId] User ID. If not defined, current user in the site.
|
||||||
|
@ -1003,6 +1272,25 @@ export class AddonMessagesProvider {
|
||||||
return this.sitesProvider.getCurrentSite().write('core_message_mark_message_read', params);
|
return this.sitesProvider.getCurrentSite().write('core_message_mark_message_read', params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark all messages of a conversation as read.
|
||||||
|
*
|
||||||
|
* @param {number} conversationId Conversation ID.
|
||||||
|
* @returns {Promise<any>} Promise resolved if success.
|
||||||
|
* @since 3.6
|
||||||
|
*/
|
||||||
|
markAllConversationMessagesRead(conversationId?: number): Promise<any> {
|
||||||
|
const params = {
|
||||||
|
userid: this.sitesProvider.getCurrentSiteUserId(),
|
||||||
|
conversationid: conversationId
|
||||||
|
},
|
||||||
|
preSets = {
|
||||||
|
responseExpected: false
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.sitesProvider.getCurrentSite().write('core_message_mark_all_conversation_messages_as_read', params, preSets);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mark all messages of a discussion as read.
|
* Mark all messages of a discussion as read.
|
||||||
*
|
*
|
||||||
|
@ -1238,17 +1526,17 @@ export class AddonMessagesProvider {
|
||||||
/**
|
/**
|
||||||
* Store the last received message if it's newer than the last stored.
|
* Store the last received message if it's newer than the last stored.
|
||||||
*
|
*
|
||||||
* @param {number} userIdFrom ID of the useridfrom retrieved, 0 for all users.
|
* @param {number} convIdOrUserIdFrom Conversation ID (3.6+) or ID of the useridfrom retrieved (3.5-), 0 for all users.
|
||||||
* @param {any} message Last message received.
|
* @param {any} message Last message received.
|
||||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
* @return {Promise<any>} Promise resolved when done.
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
protected storeLastReceivedMessageIfNeeded(userIdFrom: number, message: any, siteId?: string): Promise<any> {
|
protected storeLastReceivedMessageIfNeeded(convIdOrUserIdFrom: number, message: any, siteId?: string): Promise<any> {
|
||||||
const component = AddonMessagesProvider.PUSH_SIMULATION_COMPONENT;
|
const component = AddonMessagesProvider.PUSH_SIMULATION_COMPONENT;
|
||||||
|
|
||||||
// Get the last received message.
|
// Get the last received message.
|
||||||
return this.emulatorHelper.getLastReceivedNotification(component, siteId).then((lastMessage) => {
|
return this.emulatorHelper.getLastReceivedNotification(component, siteId).then((lastMessage) => {
|
||||||
if (userIdFrom > 0 && (!message || !lastMessage)) {
|
if (convIdOrUserIdFrom > 0 && (!message || !lastMessage)) {
|
||||||
// Seeing a single discussion. No received message or cannot know if it really is the last received message. Stop.
|
// Seeing a single discussion. No received message or cannot know if it really is the last received message. Stop.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -165,7 +165,9 @@
|
||||||
"addon.messages.errorwhileretrievingcontacts": "Error while retrieving contacts from the server.",
|
"addon.messages.errorwhileretrievingcontacts": "Error while retrieving contacts from the server.",
|
||||||
"addon.messages.errorwhileretrievingdiscussions": "Error while retrieving discussions 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.errorwhileretrievingmessages": "Error while retrieving messages from the server.",
|
||||||
|
"addon.messages.groupinfo": "Group info",
|
||||||
"addon.messages.groupmessages": "Group messages",
|
"addon.messages.groupmessages": "Group messages",
|
||||||
|
"addon.messages.info": "Info",
|
||||||
"addon.messages.message": "Message",
|
"addon.messages.message": "Message",
|
||||||
"addon.messages.messagenotsent": "The message was not sent. Please try again later.",
|
"addon.messages.messagenotsent": "The message was not sent. Please try again later.",
|
||||||
"addon.messages.messagepreferences": "Message preferences",
|
"addon.messages.messagepreferences": "Message preferences",
|
||||||
|
@ -178,6 +180,7 @@
|
||||||
"addon.messages.nousersfound": "No users found",
|
"addon.messages.nousersfound": "No users found",
|
||||||
"addon.messages.removecontact": "Remove contact",
|
"addon.messages.removecontact": "Remove contact",
|
||||||
"addon.messages.removecontactconfirm": "Contact will be removed from your contacts list.",
|
"addon.messages.removecontactconfirm": "Contact will be removed from your contacts list.",
|
||||||
|
"addon.messages.showdeletemessages": "Show delete messages",
|
||||||
"addon.messages.type_blocked": "Blocked",
|
"addon.messages.type_blocked": "Blocked",
|
||||||
"addon.messages.type_offline": "Offline",
|
"addon.messages.type_offline": "Offline",
|
||||||
"addon.messages.type_online": "Online",
|
"addon.messages.type_online": "Online",
|
||||||
|
|
Loading…
Reference in New Issue