diff --git a/src/addons/messages/pages/discussion/discussion.page.ts b/src/addons/messages/pages/discussion/discussion.page.ts index 0d4896837..a7be06c6e 100644 --- a/src/addons/messages/pages/discussion/discussion.page.ts +++ b/src/addons/messages/pages/discussion/discussion.page.ts @@ -445,13 +445,9 @@ export class AddonMessagesDiscussionPage implements OnInit, OnDestroy, AfterView return; } - // Don't use domUtils.getScrollHeight because it gives an outdated value after receiving a new message. - const scrollHeight = this.scrollElement ? this.scrollElement.scrollHeight : 0; - // Check if we are at the bottom to scroll it after render. // Use a 5px error margin because in iOS there is 1px difference for some reason. - this.scrollBottom = Math.abs(scrollHeight - (this.scrollElement?.scrollTop || 0) - - (this.scrollElement?.clientHeight || 0)) < 5; + this.scrollBottom = CoreDom.scrollIsBottom(this.scrollElement, 5); if (this.messagesBeingSent > 0) { // Ignore polling due to a race condition. @@ -510,39 +506,39 @@ export class AddonMessagesDiscussionPage implements OnInit, OnDestroy, AfterView * The scroll was moved. Update new messages count. */ scrollFunction(): void { - if (this.newMessages > 0) { - const scrollBottom = (this.scrollElement?.scrollTop || 0) + (this.scrollElement?.clientHeight || 0); - const scrollHeight = (this.scrollElement?.scrollHeight || 0); - if (scrollBottom > scrollHeight - 40) { - // At the bottom, reset. - this.setNewMessagesBadge(0); + if (this.newMessages == 0) { + return; + } - return; + if (CoreDom.scrollIsBottom(this.scrollElement, 40)) { + // At the bottom, reset. + this.setNewMessagesBadge(0); + + return; + } + + const scrollElRect = this.scrollElement?.getBoundingClientRect(); + const scrollBottomPos = (scrollElRect && scrollElRect.bottom) || 0; + + if (scrollBottomPos == 0) { + return; + } + + const messages = Array.from(this.hostElement.querySelectorAll('.addon-message-not-mine')) + .slice(-this.newMessages) + .reverse(); + + const newMessagesUnread = messages.findIndex((message) => { + const elementRect = message.getBoundingClientRect(); + if (!elementRect) { + return false; } - const scrollElRect = this.scrollElement?.getBoundingClientRect(); - const scrollBottomPos = (scrollElRect && scrollElRect.bottom) || 0; + return elementRect.bottom <= scrollBottomPos; + }); - if (scrollBottomPos == 0) { - return; - } - - const messages = Array.from(this.hostElement.querySelectorAll('.addon-message-not-mine')) - .slice(-this.newMessages) - .reverse(); - - const newMessagesUnread = messages.findIndex((message) => { - const elementRect = message.getBoundingClientRect(); - if (!elementRect) { - return false; - } - - return elementRect.bottom <= scrollBottomPos; - }); - - if (newMessagesUnread > 0 && newMessagesUnread < this.newMessages) { - this.setNewMessagesBadge(newMessagesUnread); - } + if (newMessagesUnread > 0 && newMessagesUnread < this.newMessages) { + this.setNewMessagesBadge(newMessagesUnread); } } diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 7c361804a..275daf547 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -31,6 +31,7 @@ import { CoreUrlUtils } from '@services/utils/url'; import { CoreConstants } from '@/core/constants'; import { CoreSitePlugins } from '@features/siteplugins/services/siteplugins'; import { CoreDomUtils } from '@services/utils/dom'; +import { CoreDom } from '@singletons/dom'; const MOODLE_VERSION_PREFIX = 'version-'; const MOODLEAPP_VERSION_PREFIX = 'moodleapp-'; @@ -90,9 +91,21 @@ export class AppComponent implements OnInit, AfterViewInit { }); // Listen to scroll to add style when scroll is not 0. - win.addEventListener('ionScroll', ({ detail, target }: CustomEvent) => { - const header = (target as HTMLElement).closest('.ion-page')?.querySelector('ion-header'); - header?.classList.toggle('core-header-shadow', detail.scrollTop > 0); + win.addEventListener('ionScroll', async ({ detail, target }: CustomEvent) => { + if ((target as HTMLElement).tagName != 'ION-CONTENT') { + return; + } + const content = (target as HTMLIonContentElement); + + const page = content.closest('.ion-page'); + if (!page) { + return; + } + + page.querySelector('ion-header')?.classList.toggle('core-header-shadow', detail.scrollTop > 0); + + const scrollElement = await content.getScrollElement(); + content.classList.toggle('core-footer-shadow', !CoreDom.scrollIsBottom(scrollElement)); }); // Listen for session expired events. diff --git a/src/core/directives/collapsible-footer.ts b/src/core/directives/collapsible-footer.ts index f345a9b70..7ead926a9 100644 --- a/src/core/directives/collapsible-footer.ts +++ b/src/core/directives/collapsible-footer.ts @@ -126,6 +126,9 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy { const scroll = await this.content.getScrollElement(); this.content.scrollEvents = true; + // Init shadow. + this.content.classList.toggle('core-footer-shadow', !CoreDom.scrollIsBottom(scroll)); + this.content.addEventListener('ionScroll', this.contentScrollListener = (e: CustomEvent): void => { if (!this.content) { return; diff --git a/src/core/singletons/dom.ts b/src/core/singletons/dom.ts index d26b1834e..35306cc12 100644 --- a/src/core/singletons/dom.ts +++ b/src/core/singletons/dom.ts @@ -232,6 +232,21 @@ export class CoreDom { return CoreDom.scrollToElement(container, '.core-input-error'); } + /** + * Has the scroll reached bottom? + * + * @param scrollElement Scroll Element. + * @param marginError Error margin when calculating. + * @return Wether the scroll reached the bottom. + */ + static scrollIsBottom(scrollElement?: HTMLElement, marginError = 0): boolean { + if (!scrollElement) { + return true; + } + + return scrollElement.scrollTop + scrollElement.clientHeight >= scrollElement.scrollHeight - marginError; + } + /** * Move element to content so it can be slotted. * diff --git a/src/theme/theme.base.scss b/src/theme/theme.base.scss index 4448600ce..7bc9f0437 100644 --- a/src/theme/theme.base.scss +++ b/src/theme/theme.base.scss @@ -1434,8 +1434,11 @@ ion-grid.core-no-grid > ion-row { right: 0; } -[collapsible-footer] { +.core-footer-shadow [collapsible-footer] { box-shadow: var(--drop-shadow-top, none); +} +[collapsible-footer] { + transition: box-shadow 0.5s; width: 100%; bottom: 0; z-index: 3;