diff --git a/scripts/langindex.json b/scripts/langindex.json index c87666a88..7f4fb4d09 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -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", diff --git a/src/addon/messages/lang/en.json b/src/addon/messages/lang/en.json index 1b542a0c7..b5aa51b7e 100644 --- a/src/addon/messages/lang/en.json +++ b/src/addon/messages/lang/en.json @@ -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", diff --git a/src/addon/messages/pages/discussion/discussion.html b/src/addon/messages/pages/discussion/discussion.html index 7fb8da636..f93a8b6af 100644 --- a/src/addon/messages/pages/discussion/discussion.html +++ b/src/addon/messages/pages/discussion/discussion.html @@ -10,15 +10,15 @@ - - + + - - + + - - + + @@ -26,6 +26,12 @@ + + +

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

+

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

+
+
diff --git a/src/addon/messages/pages/discussion/discussion.ts b/src/addon/messages/pages/discussion/discussion.ts index 25cdcab13..fb0f2299a 100644 --- a/src/addon/messages/pages/discussion/discussion.ts +++ b/src/addon/messages/pages/discussion/discussion.ts @@ -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} Resolved when done. */ loadPrevious(infiniteComplete?: any): Promise { + 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(() => { diff --git a/src/addon/messages/pages/group-conversations/group-conversations.html b/src/addon/messages/pages/group-conversations/group-conversations.html index 10d5c3052..bcb05e861 100644 --- a/src/addon/messages/pages/group-conversations/group-conversations.html +++ b/src/addon/messages/pages/group-conversations/group-conversations.html @@ -91,12 +91,12 @@ - + - +

@@ -109,7 +109,7 @@

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

diff --git a/src/addon/messages/pages/group-conversations/group-conversations.ts b/src/addon/messages/pages/group-conversations/group-conversations.ts index d2fe94280..a97006ea1 100644 --- a/src/addon/messages/pages/group-conversations/group-conversations.ts +++ b/src/addon/messages/pages/group-conversations/group-conversations.ts @@ -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; }); } diff --git a/src/addon/messages/providers/mainmenu-handler.ts b/src/addon/messages/providers/mainmenu-handler.ts index 77d453ce1..0875f19b6 100644 --- a/src/addon/messages/providers/mainmenu-handler.ts +++ b/src/addon/messages/providers/mainmenu-handler.ts @@ -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); }); diff --git a/src/addon/messages/providers/messages.ts b/src/addon/messages/providers/messages.ts index bfa615909..47c238b8e 100644 --- a/src/addon/messages/providers/messages.ts +++ b/src/addon/messages/providers/messages.ts @@ -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} Promise resolved with favourite, individual and - * group conversation counts. + * @return {Promise} 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} Promise resolved with the response. + * @since 3.7 + */ + getSelfConversation(messageOffset: number = 0, messageLimit: number = 1, newestFirst: boolean = true, siteId?: string, + userId?: number): Promise { + + 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} 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} Resolved when done. + */ + invalidateSelfConversation(siteId?: string, userId?: number): Promise { + 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} Promise resolved with boolean: whether related WS is available on a certain site. + * @since 3.7 + */ + isSelfConversationEnabledInSite(siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + return this.isSelfConversationEnabled(site); + }).catch(() => { + return false; + }); + } + /** * Mark message as read. * diff --git a/src/addon/messages/providers/user-send-message-handler.ts b/src/addon/messages/providers/user-send-message-handler.ts index e42a2ca67..a80b90ade 100644 --- a/src/addon/messages/providers/user-send-message-handler.ts +++ b/src/addon/messages/providers/user-send-message-handler.ts @@ -49,7 +49,14 @@ export class AddonMessagesSendMessageUserHandler implements CoreUserProfileHandl * @return {boolean|Promise} Promise resolved with true if enabled, resolved with false otherwise. */ isEnabledForUser(user: any, courseId: number, navOptions?: any, admOptions?: any): boolean | Promise { - 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'); } /** diff --git a/src/assets/lang/en.json b/src/assets/lang/en.json index 8e7ff5128..a6b658a3d 100644 --- a/src/assets/lang/en.json +++ b/src/assets/lang/en.json @@ -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", diff --git a/src/components/infinite-loading/core-infinite-loading.html b/src/components/infinite-loading/core-infinite-loading.html index 35a8285a8..8775a5b60 100644 --- a/src/components/infinite-loading/core-infinite-loading.html +++ b/src/components/infinite-loading/core-infinite-loading.html @@ -1,5 +1,5 @@ -
+
@@ -9,12 +9,12 @@
- + -
+
@@ -24,6 +24,6 @@
-
+
\ No newline at end of file diff --git a/src/components/infinite-loading/infinite-loading.ts b/src/components/infinite-loading/infinite-loading.ts index 101e303d4..977eba6b7 100644 --- a/src/components/infinite-loading/infinite-loading.ts +++ b/src/components/infinite-loading/infinite-loading.ts @@ -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; + } + } diff --git a/src/components/user-avatar/user-avatar.ts b/src/components/user-avatar/user-avatar.ts index cb1243e1d..a8e9d6bc7 100644 --- a/src/components/user-avatar/user-avatar.ts +++ b/src/components/user-avatar/user-avatar.ts @@ -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; }