Merge pull request #1852 from dpalou/MOBILE-2962

Mobile 2962
main
Juan Leyva 2019-04-26 15:56:43 +02:00 committed by GitHub
commit 3418ed53c4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 272 additions and 45 deletions

View File

@ -179,6 +179,7 @@
"addon.messages.contacts": "message",
"addon.messages.decline": "message",
"addon.messages.deleteallconfirm": "message",
"addon.messages.deleteallselfconfirm": "message",
"addon.messages.deleteconversation": "message",
"addon.messages.deletemessage": "local_moodlemobileapp",
"addon.messages.deletemessageconfirmation": "local_moodlemobileapp",
@ -217,6 +218,8 @@
"addon.messages.searchnocontactsfound": "message",
"addon.messages.searchnomessagesfound": "message",
"addon.messages.searchnononcontactsfound": "message",
"addon.messages.selfconversation": "message",
"addon.messages.selfconversationdefaultmessage": "message",
"addon.messages.sendcontactrequest": "message",
"addon.messages.showdeletemessages": "local_moodlemobileapp",
"addon.messages.type_blocked": "local_moodlemobileapp",

View File

@ -18,6 +18,7 @@
"contacts": "Contacts",
"decline": "Decline",
"deleteallconfirm": "Are you sure you would like to delete this entire conversation? This will not delete it for other conversation participants.",
"deleteallselfconfirm": "Are you sure you would like to delete this entire personal conversation?",
"deleteconversation": "Delete conversation",
"deletemessage": "Delete message",
"deletemessageconfirmation": "Are you sure you want to delete this message? It will only be deleted from your messaging history and will still be viewable by the user who sent or received the message.",
@ -56,6 +57,8 @@
"searchnocontactsfound": "No contacts found",
"searchnomessagesfound": "No messages found",
"searchnononcontactsfound": "No non contacts found",
"selfconversation": "Personal space",
"selfconversationdefaultmessage": "Save draft messages, links, notes etc. to access later.",
"sendcontactrequest": "Send contact request",
"showdeletemessages": "Show delete messages",
"type_blocked": "Blocked",

View File

@ -10,15 +10,15 @@
</ion-navbar>
<core-navbar-buttons end>
<core-context-menu>
<core-context-menu-item [hidden]="!showInfo || isGroup" [priority]="1000" [content]="'addon.messages.info' | translate" (action)="viewInfo()" iconAction="information-circle"></core-context-menu-item>
<core-context-menu-item [hidden]="!showInfo || !isGroup" [priority]="1000" [content]="'addon.messages.groupinfo' | translate" (action)="viewInfo()" iconAction="information-circle"></core-context-menu-item>
<core-context-menu-item [hidden]="isSelf || !showInfo || isGroup" [priority]="1000" [content]="'addon.messages.info' | translate" (action)="viewInfo()" iconAction="information-circle"></core-context-menu-item>
<core-context-menu-item [hidden]="isSelf || !showInfo || !isGroup" [priority]="1000" [content]="'addon.messages.groupinfo' | translate" (action)="viewInfo()" iconAction="information-circle"></core-context-menu-item>
<core-context-menu-item [hidden]="!groupMessagingEnabled || !conversation" [priority]="800" [content]="(conversation && conversation.isfavourite ? 'addon.messages.removefromfavourites' : 'addon.messages.addtofavourites') | translate" (action)="changeFavourite($event)" [closeOnClick]="false" [iconAction]="favouriteIcon"></core-context-menu-item>
<core-context-menu-item [hidden]="!otherMember || otherMember.isblocked" [priority]="700" [content]="'addon.messages.blockuser' | translate" (action)="blockUser()" [iconAction]="blockIcon"></core-context-menu-item>
<core-context-menu-item [hidden]="!otherMember || !otherMember.isblocked" [priority]="700" [content]="'addon.messages.unblockuser' | translate" (action)="unblockUser()" [iconAction]="blockIcon"></core-context-menu-item>
<core-context-menu-item [hidden]="isSelf || !otherMember || otherMember.isblocked" [priority]="700" [content]="'addon.messages.blockuser' | translate" (action)="blockUser()" [iconAction]="blockIcon"></core-context-menu-item>
<core-context-menu-item [hidden]="isSelf || !otherMember || !otherMember.isblocked" [priority]="700" [content]="'addon.messages.unblockuser' | translate" (action)="unblockUser()" [iconAction]="blockIcon"></core-context-menu-item>
<core-context-menu-item [hidden]="!canDelete" [priority]="400" [content]="'addon.messages.showdeletemessages' | translate" (action)="toggleDelete()" [iconAction]="(showDelete ? 'checkbox-outline' : 'square-outline')"></core-context-menu-item>
<core-context-menu-item [hidden]="!groupMessagingEnabled || !conversationId || isGroup" [priority]="200" [content]="'addon.messages.deleteconversation' | translate" (action)="deleteConversation($event)" [closeOnClick]="false" [iconAction]="deleteIcon"></core-context-menu-item>
<core-context-menu-item [hidden]="!otherMember || otherMember.iscontact || requestContactSent || requestContactReceived" [priority]="100" [content]="'addon.messages.addtoyourcontacts' | translate" (action)="createContactRequest()" [iconAction]="addRemoveIcon"></core-context-menu-item>
<core-context-menu-item [hidden]="!otherMember || !otherMember.iscontact" [priority]="100" [content]="'addon.messages.removefromyourcontacts' | translate" (action)="removeContact()" [iconAction]="addRemoveIcon"></core-context-menu-item>
<core-context-menu-item [hidden]="isSelf || !otherMember || otherMember.iscontact || requestContactSent || requestContactReceived" [priority]="100" [content]="'addon.messages.addtoyourcontacts' | translate" (action)="createContactRequest()" [iconAction]="addRemoveIcon"></core-context-menu-item>
<core-context-menu-item [hidden]="isSelf || !otherMember || !otherMember.iscontact" [priority]="100" [content]="'addon.messages.removefromyourcontacts' | translate" (action)="removeContact()" [iconAction]="addRemoveIcon"></core-context-menu-item>
</core-context-menu>
</core-navbar-buttons>
</ion-header>
@ -26,6 +26,12 @@
<core-loading [hideUntil]="loaded">
<!-- Load previous messages. -->
<core-infinite-loading [enabled]="canLoadMore" (action)="loadPrevious($event)" position="top" [error]="loadMoreError"></core-infinite-loading>
<ng-container *ngIf="isSelf && !canLoadMore">
<p text-center>{{ 'addon.messages.selfconversation' | translate }}</p>
<p text-center><i>{{ 'addon.messages.selfconversationdefaultmessage' | translate }}</i></p>
</ng-container>
<ion-list class="addon-messages-discussion-container safe-area-page" [class.addon-messages-discussion-group]="isGroup" [attr.aria-live]="'polite'">
<ng-container *ngFor="let message of messages; index as index; last as last">
<h6 text-center *ngIf="message.showDate" class="addon-messages-date">

View File

@ -27,6 +27,7 @@ import { CoreLoggerProvider } from '@providers/logger';
import { CoreAppProvider } from '@providers/app';
import { coreSlideInOut } from '@classes/animations';
import { CoreSplitViewComponent } from '@components/split-view/split-view';
import { CoreInfiniteLoadingComponent } from '@components/infinite-loading/infinite-loading';
import { Md5 } from 'ts-md5/dist/md5';
import * as moment from 'moment';
@ -41,6 +42,7 @@ import * as moment from 'moment';
})
export class AddonMessagesDiscussionPage implements OnDestroy {
@ViewChild(Content) content: Content;
@ViewChild(CoreInfiniteLoadingComponent) infinite: CoreInfiniteLoadingComponent;
siteId: string;
protected fetching: boolean;
@ -85,6 +87,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
footerType: 'message' | 'blocked' | 'requiresContact' | 'requestSent' | 'requestReceived' | 'unable';
requestContactSent = false;
requestContactReceived = false;
isSelf = false;
constructor(private eventsProvider: CoreEventsProvider, sitesProvider: CoreSitesProvider, navParams: NavParams,
private userProvider: CoreUserProvider, private navCtrl: NavController, private messagesSync: AddonMessagesSyncProvider,
@ -380,6 +383,9 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
message.showUserData = this.showUserData(message, this.messages[index - 1]);
});
// Call resize to recalculate the dimensions.
this.content && this.content.resize();
// Notify that there can be a new message.
this.notifyNewMessage();
@ -402,7 +408,13 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
if (conversationId) {
promise = Promise.resolve(conversationId);
} else {
promise = this.messagesProvider.getConversationBetweenUsers(userId, undefined, true).then((conversation) => {
if (userId == this.currentUserId && this.messagesProvider.isSelfConversationEnabled()) {
promise = this.messagesProvider.getSelfConversation();
} else {
promise = this.messagesProvider.getConversationBetweenUsers(userId, undefined, true);
}
promise = promise.then((conversation) => {
fallbackConversation = conversation;
return conversation.id;
@ -434,6 +446,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
if (!this.isGroup) {
this.userId = conversation.userid;
}
this.isSelf = conversation.type == AddonMessagesProvider.MESSAGE_CONVERSATION_TYPE_SELF;
return true;
} else {
@ -442,7 +455,9 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
});
}, (error) => {
// Probably conversation does not exist or user is offline. Try to load offline messages.
return this.messagesOffline.getMessages(userId).then((messages) => {
this.isSelf = userId == this.currentUserId;
return this.messagesOffline.getMessages(userId).then((messages): any => {
if (messages && messages.length) {
// We have offline messages, this probably means that the conversation didn't exist. Don't display error.
messages.forEach((message) => {
@ -455,6 +470,8 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
// Display the error.
return Promise.reject(error);
}
return false;
});
});
}
@ -813,11 +830,28 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
* @return {Promise<any>} Resolved when done.
*/
loadPrevious(infiniteComplete?: any): Promise<any> {
let infiniteHeight = this.infinite ? this.infinite.getHeight() : 0;
const scrollHeight = this.domUtils.getScrollHeight(this.content);
// If there is an ongoing fetch, wait for it to finish.
return this.waitForFetch().finally(() => {
this.pagesLoaded++;
this.fetchMessages().catch((error) => {
this.fetchMessages().then(() => {
// Try to keep the scroll position.
const scrollBottom = scrollHeight - this.domUtils.getScrollTop(this.content);
if (this.canLoadMore && infiniteHeight && this.infinite) {
// The height of the infinite is different while spinner is shown. Add that difference.
infiniteHeight = infiniteHeight - this.infinite.getHeight();
} else if (!this.canLoadMore) {
// Can't load more, take into account the full height of the infinite loading since it will disappear now.
infiniteHeight = infiniteHeight || (this.infinite ? this.infinite.getHeight() : 0);
}
this.keepScroll(scrollHeight, scrollBottom, infiniteHeight);
}).catch((error) => {
this.loadMoreError = true; // Set to prevent infinite calls with infinite-loading.
this.pagesLoaded--;
this.domUtils.showErrorModalDefault(error, 'addon.messages.errorwhileretrievingmessages', true);
@ -827,6 +861,31 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
});
}
/**
* Keep scroll position after loading previous messages.
* We don't use resizeContent because the approach used is different and it isn't easy to calculate these positions.
*/
protected keepScroll(oldScrollHeight: number, oldScrollBottom: number, infiniteHeight: number, retries?: number): void {
retries = retries || 0;
setTimeout(() => {
const newScrollHeight = this.domUtils.getScrollHeight(this.content);
if (newScrollHeight == oldScrollHeight) {
// Height hasn't changed yet. Retry if max retries haven't been reached.
if (retries <= 10) {
this.keepScroll(oldScrollHeight, oldScrollBottom, infiniteHeight, retries + 1);
}
return;
}
const scrollTo = newScrollHeight - oldScrollBottom + infiniteHeight;
this.domUtils.scrollTo(this.content, 0, scrollTo, 0);
}, 30);
}
/**
* Content or scroll has been resized. For content, only call it if it's been added on top.
*/
@ -1123,7 +1182,9 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
* @param {Function} [done] Function to call when done.
*/
deleteConversation(done?: () => void): void {
this.domUtils.showConfirm(this.translate.instant('addon.messages.deleteallconfirm')).then(() => {
const confirmMessage = 'addon.messages.' + (this.isSelf ? 'deleteallselfconfirm' : 'deleteallconfirm');
this.domUtils.showConfirm(this.translate.instant(confirmMessage)).then(() => {
this.deleteIcon = 'spinner';
return this.messagesProvider.deleteConversation(this.conversation.id).then(() => {

View File

@ -91,12 +91,12 @@
<ng-template #conversationsTemplate let-conversations="conversations">
<a ion-item text-wrap *ngFor="let conversation of conversations" [title]="conversation.name" (click)="gotoConversation(conversation.id, conversation.userid)" [class.core-split-item-selected]="(conversation.id && conversation.id == selectedConversationId) || (conversation.userid && conversation.userid == selectedUserId)" class="addon-message-discussion" id="addon-message-conversation-{{ conversation.id ? conversation.id : 'user-' + conversation.userid }}">
<!-- Group conversation image. -->
<ion-avatar item-start *ngIf="conversation.type != typeIndividual">
<ion-avatar item-start *ngIf="conversation.type == typeGroup">
<img [src]="conversation.imageurl" [alt]="conversation.name" core-external-content onError="this.src='assets/img/group-avatar.png'">
</ion-avatar>
<!-- Avatar for individual conversations. -->
<ion-avatar *ngIf="conversation.type == typeIndividual" core-user-avatar [user]="conversation.otherUser" [linkProfile]="false" [checkOnline]="conversation.showonlinestatus" item-start></ion-avatar>
<ion-avatar *ngIf="conversation.type != typeGroup" core-user-avatar [user]="conversation.otherUser" [linkProfile]="false" [checkOnline]="conversation.showonlinestatus" item-start></ion-avatar>
<h2>
<core-format-text [text]="conversation.name"></core-format-text>
@ -109,7 +109,7 @@
<p *ngIf="conversation.subname"><core-format-text [text]="conversation.subname"></core-format-text></p>
<p class="addon-message-last-message">
<span *ngIf="conversation.sentfromcurrentuser" class="addon-message-last-message-user">{{ 'addon.messages.you' | translate }}</span>
<core-format-text *ngIf="conversation.type != typeIndividual && conversation.members[0]" [text]="conversation.members[0].fullname + ':'" class="addon-message-last-message-user"></core-format-text>
<core-format-text *ngIf="!conversation.sentfromcurrentuser && conversation.type == typeGroup && conversation.members[0]" [text]="conversation.members[0].fullname + ':'" class="addon-message-last-message-user"></core-format-text>
<core-format-text clean="true" singleLine="true" [text]="conversation.lastmessage" class="addon-message-last-message-text"></core-format-text>
</p>
</a>

View File

@ -63,7 +63,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
count: 0,
unread: 0
};
typeIndividual = AddonMessagesProvider.MESSAGE_CONVERSATION_TYPE_INDIVIDUAL;
typeGroup = AddonMessagesProvider.MESSAGE_CONVERSATION_TYPE_GROUP;
currentListEl: HTMLElement;
protected loadingString: string;
@ -182,7 +182,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
// Update unread conversation counts.
this.cronObserver = eventsProvider.on(AddonMessagesProvider.UNREAD_CONVERSATION_COUNTS_EVENT, (data) => {
this.favourites.unread = data.favourites;
this.individual.unread = data.individual;
this.individual.unread = data.individual + data.self; // Self is only returned if it's not favourite.
this.group.unread = data.group;
}, this.siteId);
@ -400,7 +400,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
return this.messagesProvider.getConversationCounts(this.siteId);
}).then((counts) => {
this.favourites.count = counts.favourites;
this.individual.count = counts.individual;
this.individual.count = counts.individual + counts.self; // Self is only returned if it's not favourite.
this.group.count = counts.group;
});
}

View File

@ -54,7 +54,7 @@ export class AddonMessagesMainMenuHandler implements CoreMainMenuHandler, CoreCr
pushNotificationsDelegate: CorePushNotificationsDelegate, private emulatorHelper: CoreEmulatorHelperProvider) {
eventsProvider.on(AddonMessagesProvider.UNREAD_CONVERSATION_COUNTS_EVENT, (data) => {
this.unreadCount = data.favourites + data.individual + data.group;
this.unreadCount = data.favourites + data.individual + data.group + data.self;
this.orMore = data.orMore;
this.updateBadge(data.siteId);
});

View File

@ -47,6 +47,7 @@ export class AddonMessagesProvider {
static MESSAGE_PRIVACY_SITE = 2; // Privacy setting for being messaged by anyone on the site.
static MESSAGE_CONVERSATION_TYPE_INDIVIDUAL = 1; // An individual conversation.
static MESSAGE_CONVERSATION_TYPE_GROUP = 2; // A group conversation.
static MESSAGE_CONVERSATION_TYPE_SELF = 3; // A self conversation.
static LIMIT_CONTACTS = 50;
static LIMIT_MESSAGES = 50;
static LIMIT_INITIAL_USER_SEARCH = 3;
@ -297,14 +298,15 @@ export class AddonMessagesProvider {
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;
}
if (conversation.type != AddonMessagesProvider.MESSAGE_CONVERSATION_TYPE_GROUP) {
const isIndividual = conversation.type == AddonMessagesProvider.MESSAGE_CONVERSATION_TYPE_INDIVIDUAL,
otherUser = conversation.members.reduce((carry, member) => {
if (!carry && ((isIndividual && member.id != userId) || (!isIndividual && member.id == userId))) {
carry = member;
}
return carry;
}, null);
return carry;
}, null);
conversation.name = conversation.name ? conversation.name : otherUser.fullname;
conversation.imageurl = conversation.imageurl ? conversation.imageurl : otherUser.profileimageurl;
@ -478,6 +480,16 @@ export class AddonMessagesProvider {
return this.ROOT_CACHE_KEY + 'memberInfo:' + userId + ':' + otherUserId;
}
/**
* Get cache key for get self conversation.
*
* @param {number} userId User ID.
* @return {string} Cache key.
*/
protected getCacheKeyForSelfConversation(userId: number): string {
return this.ROOT_CACHE_KEY + 'selfconversation:' + userId;
}
/**
* Get common cache key for get user conversations.
*
@ -963,7 +975,21 @@ export class AddonMessagesProvider {
params.favourites = favourites ? 1 : 0;
}
return site.read('core_message_get_conversations', params, preSets).then((response) => {
if (site.isVersionGreaterEqualThan('3.7') && type != AddonMessagesProvider.MESSAGE_CONVERSATION_TYPE_GROUP) {
// Add self conversation to the list.
params.mergeself = 1;
}
return site.read('core_message_get_conversations', params, preSets).catch((error) => {
if (params.mergeself) {
// Try again without the new param. Maybe the user is offline and he has a previous request cached.
delete params.mergeself;
return site.read('core_message_get_conversations', params, preSets);
}
return Promise.reject(error);
}).then((response) => {
// Format the conversations, adding some calculated fields.
const conversations = response.conversations.slice(0, this.LIMIT_MESSAGES).map((conversation) => {
return this.formatConversation(conversation, userId);
@ -988,11 +1014,11 @@ export class AddonMessagesProvider {
* Get conversation counts by type.
*
* @param {string} [siteId] Site ID. If not defined, use current site.
* @return {Promise<favourites: number, individual: number, group: number>} Promise resolved with favourite, individual and
* group conversation counts.
* @return {Promise<favourites: number, individual: number, group: number, self: number>} Promise resolved with favourite,
* individual, group and self conversation counts.
* @since 3.6
*/
getConversationCounts(siteId?: string): Promise<{favourites: number, individual: number, group: number}> {
getConversationCounts(siteId?: string): Promise<{favourites: number, individual: number, group: number, self: number}> {
return this.sitesProvider.getSite(siteId).then((site) => {
const preSets = {
@ -1003,7 +1029,8 @@ export class AddonMessagesProvider {
const counts = {
favourites: result.favourites,
individual: result.types[AddonMessagesProvider.MESSAGE_CONVERSATION_TYPE_INDIVIDUAL],
group: result.types[AddonMessagesProvider.MESSAGE_CONVERSATION_TYPE_GROUP]
group: result.types[AddonMessagesProvider.MESSAGE_CONVERSATION_TYPE_GROUP],
self: result.types[AddonMessagesProvider.MESSAGE_CONVERSATION_TYPE_SELF] || 0
};
return counts;
@ -1359,6 +1386,40 @@ export class AddonMessagesProvider {
});
}
/**
* Get a self conversation.
*
* @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 {boolean} [newestFirst=true] Whether to order messages by newest first.
* @param {string} [siteId] Site ID. If not defined, use current site.
* @param {string} [userId] User ID to get the self conversation for. If not defined, current user in the site.
* @return {Promise<any>} Promise resolved with the response.
* @since 3.7
*/
getSelfConversation(messageOffset: number = 0, messageLimit: number = 1, newestFirst: boolean = true, siteId?: string,
userId?: number): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => {
userId = userId || site.getUserId();
const preSets = {
cacheKey: this.getCacheKeyForSelfConversation(userId)
},
params: any = {
userid: userId,
messageoffset: messageOffset,
messagelimit: messageLimit,
newestmessagesfirst: newestFirst ? 1 : 0
};
return site.read('core_message_get_self_conversation', params, preSets).then((conversation) => {
return this.formatConversation(conversation, userId);
});
});
}
/**
* Get unread conversation counts by type.
*
@ -1366,10 +1427,10 @@ export class AddonMessagesProvider {
* @return {Promise<any>} Resolved with the unread favourite, individual and group conversation counts.
*/
getUnreadConversationCounts(siteId?: string):
Promise<{favourites: number, individual: number, group: number, orMore?: boolean}> {
Promise<{favourites: number, individual: number, group: number, self: number, orMore?: boolean}> {
return this.sitesProvider.getSite(siteId).then((site) => {
let promise: Promise<{favourites: number, individual: number, group: number, orMore?: boolean}>;
let promise: Promise<{favourites: number, individual: number, group: number, self: number, orMore?: boolean}>;
if (this.isGroupMessagingEnabled()) {
// @since 3.6
@ -1381,7 +1442,8 @@ export class AddonMessagesProvider {
return {
favourites: result.favourites,
individual: result.types[AddonMessagesProvider.MESSAGE_CONVERSATION_TYPE_INDIVIDUAL],
group: result.types[AddonMessagesProvider.MESSAGE_CONVERSATION_TYPE_GROUP]
group: result.types[AddonMessagesProvider.MESSAGE_CONVERSATION_TYPE_GROUP],
self: result.types[AddonMessagesProvider.MESSAGE_CONVERSATION_TYPE_SELF] || 0
};
});
@ -1396,7 +1458,7 @@ export class AddonMessagesProvider {
};
promise = site.read('core_message_get_unread_conversations_count', params, preSets).then((count) => {
return { favourites: 0, individual: count, group: 0 };
return { favourites: 0, individual: count, group: 0, self: 0 };
});
} else {
// Fallback call.
@ -1421,6 +1483,7 @@ export class AddonMessagesProvider {
favourites: 0,
individual: count,
group: 0,
self: 0,
orMore: count > this.LIMIT_MESSAGES
};
});
@ -1718,6 +1781,21 @@ export class AddonMessagesProvider {
]);
}
/**
* Invalidate a self conversation.
*
* @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.
*/
invalidateSelfConversation(siteId?: string, userId?: number): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => {
userId = userId || site.getUserId();
return site.invalidateWsCacheForKey(this.getCacheKeyForSelfConversation(userId));
});
}
/**
* Invalidate unread conversation counts cache.
*
@ -1882,6 +1960,34 @@ export class AddonMessagesProvider {
return this.sitesProvider.wsAvailableInCurrentSite('core_message_data_for_messagearea_search_messages');
}
/**
* Returns whether or not self conversation is supported in a certain site.
*
* @param {CoreSite} [site] Site. If not defined, current site.
* @return {boolean} If related WS is available on the site.
* @since 3.7
*/
isSelfConversationEnabled(site?: CoreSite): boolean {
site = site || this.sitesProvider.getCurrentSite();
return site.wsAvailable('core_message_get_self_conversation');
}
/**
* Returns whether or not self conversation is supported in a certain site.
*
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<boolean>} Promise resolved with boolean: whether related WS is available on a certain site.
* @since 3.7
*/
isSelfConversationEnabledInSite(siteId?: string): Promise<boolean> {
return this.sitesProvider.getSite(siteId).then((site) => {
return this.isSelfConversationEnabled(site);
}).catch(() => {
return false;
});
}
/**
* Mark message as read.
*

View File

@ -49,7 +49,14 @@ export class AddonMessagesSendMessageUserHandler implements CoreUserProfileHandl
* @return {boolean|Promise<boolean>} Promise resolved with true if enabled, resolved with false otherwise.
*/
isEnabledForUser(user: any, courseId: number, navOptions?: any, admOptions?: any): boolean | Promise<boolean> {
return user.id != this.sitesProvider.getCurrentSiteUserId();
const currentSite = this.sitesProvider.getCurrentSite();
if (!currentSite) {
return false;
}
// From 3.7 you can send messages to yourself.
return user.id != currentSite.getUserId() || currentSite.isVersionGreaterEqualThan('3.7');
}
/**

View File

@ -179,6 +179,7 @@
"addon.messages.contacts": "Contacts",
"addon.messages.decline": "Decline",
"addon.messages.deleteallconfirm": "Are you sure you would like to delete this entire conversation? This will not delete it for other conversation participants.",
"addon.messages.deleteallselfconfirm": "Are you sure you would like to delete this entire personal conversation?",
"addon.messages.deleteconversation": "Delete conversation",
"addon.messages.deletemessage": "Delete message",
"addon.messages.deletemessageconfirmation": "Are you sure you want to delete this message? It will only be deleted from your messaging history and will still be viewable by the user who sent or received the message.",
@ -217,6 +218,8 @@
"addon.messages.searchnocontactsfound": "No contacts found",
"addon.messages.searchnomessagesfound": "No messages found",
"addon.messages.searchnononcontactsfound": "No non contacts found",
"addon.messages.selfconversation": "Personal space",
"addon.messages.selfconversationdefaultmessage": "Save draft messages, links, notes etc. to access later.",
"addon.messages.sendcontactrequest": "Send contact request",
"addon.messages.showdeletemessages": "Show delete messages",
"addon.messages.type_blocked": "Blocked",

View File

@ -1,5 +1,5 @@
<ng-container *ngIf="!loadingMore && position != 'top'">
<div *ngIf="enabled || error" padding-horizontal>
<div *ngIf="enabled || error" padding-horizontal #bottombutton>
<button *ngIf="!error" ion-button block (click)="loadMore()" color="light">
{{ 'core.loadmore' | translate }}
</button>
@ -9,12 +9,12 @@
</div>
</ng-container>
<ion-infinite-scroll [enabled]="enabled && !error && !loadingMore" (ionInfinite)="loadMore($event)" [position]="position">
<ion-infinite-scroll [enabled]="enabled && !error && !loadingMore" (ionInfinite)="loadMore($event)" [position]="position" #infinitescroll>
<ion-infinite-scroll-content></ion-infinite-scroll-content>
</ion-infinite-scroll>
<ng-container *ngIf="!loadingMore && position == 'top'">
<div *ngIf="enabled || error" padding-horizontal>
<div *ngIf="enabled || error" padding-horizontal #topbutton>
<button *ngIf="!error" ion-button block (click)="loadMore()" color="light">
{{ 'core.loadmore' | translate }}
</button>
@ -24,6 +24,6 @@
</div>
</ng-container>
<div *ngIf="loadingMore" padding text-center>
<div *ngIf="loadingMore" padding text-center #spinnercontainer>
<ion-spinner></ion-spinner>
</div>

View File

@ -12,8 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component, Input, Output, EventEmitter, OnChanges, SimpleChange, Optional } from '@angular/core';
import { Component, Input, Output, EventEmitter, OnChanges, SimpleChange, Optional, ViewChild, ElementRef } from '@angular/core';
import { InfiniteScroll, Content } from 'ionic-angular';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
/**
* Component to show a infinite loading trigger and spinner while more data is being loaded.
@ -31,11 +32,16 @@ export class CoreInfiniteLoadingComponent implements OnChanges {
@Input() position = 'bottom';
@Output() action: EventEmitter<() => void>; // Will emit an event when triggered.
@ViewChild('topbutton') topButton: ElementRef;
@ViewChild('infinitescroll') infiniteEl: ElementRef;
@ViewChild('bottombutton') bottomButton: ElementRef;
@ViewChild('spinnercontainer') spinnerContainer: ElementRef;
loadingMore = false; // Hide button and avoid loading more.
protected infiniteScroll: InfiniteScroll;
constructor(@Optional() private content: Content) {
constructor(@Optional() private content: Content, private domUtils: CoreDomUtilsProvider) {
this.action = new EventEmitter();
}
@ -77,6 +83,18 @@ export class CoreInfiniteLoadingComponent implements OnChanges {
* Complete loading.
*/
complete(): void {
if (this.position == 'top') {
// Wait a bit before allowing loading more, otherwise it could be re-triggered automatically when it shouldn't.
setTimeout(this.completeLoadMore.bind(this), 400);
} else {
this.completeLoadMore();
}
}
/**
* Complete loading.
*/
protected completeLoadMore(): void {
this.loadingMore = false;
this.infiniteScroll && this.infiniteScroll.complete();
this.infiniteScroll = undefined;
@ -89,4 +107,28 @@ export class CoreInfiniteLoadingComponent implements OnChanges {
});
}
/**
* Get the height of the element.
*
* @return {number} Height.
*/
getHeight(): number {
return this.getElementHeight(this.topButton) + this.getElementHeight(this.infiniteEl) +
this.getElementHeight(this.bottomButton) + this.getElementHeight(this.spinnerContainer);
}
/**
* Get the height of an element.
*
* @param {ElementRef} element Element ref.
* @return {number} Height.
*/
protected getElementHeight(element: ElementRef): number {
if (element && element.nativeElement) {
return this.domUtils.getElementHeight(element.nativeElement, true, true, true);
}
return 0;
}
}

View File

@ -44,7 +44,6 @@ export class CoreUserAvatarComponent implements OnInit, OnChanges, OnDestroy {
// Variable to check if we consider this user online or not.
// @TODO: Use setting when available (see MDL-63972) so we can use site setting.
protected timetoshowusers = 300000; // Miliseconds default.
protected myUser = false;
protected currentUserId: number;
protected pictureObs;
@ -91,9 +90,6 @@ export class CoreUserAvatarComponent implements OnInit, OnChanges, OnDestroy {
this.userId = this.userId || (this.user && (this.user.userid || this.user.id));
this.courseId = this.courseId || (this.user && this.user.courseid);
// If not available we cannot ensure the avatar is from the current user.
this.myUser = this.userId && this.userId == this.currentUserId;
}
/**
@ -102,7 +98,7 @@ export class CoreUserAvatarComponent implements OnInit, OnChanges, OnDestroy {
* @return boolean
*/
isOnline(): boolean {
if (this.myUser || this.utils.isFalseOrZero(this.user.isonline)) {
if (this.utils.isFalseOrZero(this.user.isonline)) {
return false;
}