// // Copyright 2025 Element Creations Ltd. // Copyright 2022-2025 New Vector Ltd. // // SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. // Please see LICENSE files in the repository root for full details. // import Combine import UIKit class ScrollViewAdapter: NSObject, UIScrollViewDelegate { var scrollView: UIScrollView? { didSet { oldValue?.delegate = nil scrollView?.delegate = self } } var shouldScrollToTopClosure: ((UIScrollView) -> Bool)? private let didScrollSubject = PassthroughSubject() var didScroll: AnyPublisher { didScrollSubject.eraseToAnyPublisher() } private let isScrollingSubject = CurrentValueSubject(false) var isScrolling: CurrentValuePublisher { .init(isScrollingSubject) } private let isAtTopEdgeSubject: CurrentValueSubject = .init(false) var isAtTopEdge: CurrentValuePublisher { isAtTopEdgeSubject .asCurrentValuePublisher() } // MARK: - UIScrollViewDelegate func scrollViewDidScroll(_ scrollView: UIScrollView) { didScrollSubject.send(()) let insetContentOffset = scrollView.contentOffset.y + scrollView.contentInset.top isAtTopEdgeSubject.send(insetContentOffset >= 3) } func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { updateDidScroll(scrollView) } func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { updateDidScroll(scrollView) } func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) { updateDidScroll(scrollView) } func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { updateDidScroll(scrollView) } func scrollViewDidScrollToTop(_ scrollView: UIScrollView) { updateDidScroll(scrollView) } func scrollViewShouldScrollToTop(_ scrollView: UIScrollView) -> Bool { guard let shouldScrollToTopClosure else { // Default behaviour return true } return shouldScrollToTopClosure(scrollView) } // MARK: - Private private func updateDidScroll(_ scrollView: UIScrollView) { isScrollingSubject.send(scrollView.isDragging || scrollView.isDecelerating) } }