diff --git a/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift b/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift index fc3c9eff4..224c703eb 100644 --- a/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift +++ b/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift @@ -14,6 +14,7 @@ // limitations under the License. // +import Combine import Foundation import UIKit @@ -90,6 +91,8 @@ struct HomeScreenViewState: BindableState { struct HomeScreenViewStateBindings { var searchQuery = "" + var isScrolling = false + var alertInfo: AlertInfo? } diff --git a/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift index 6b6a557b0..26b50ae62 100644 --- a/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift +++ b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift @@ -35,7 +35,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol var callback: ((HomeScreenViewModelAction) -> Void)? - // swiftlint:disable:next function_body_length + // swiftlint:disable:next function_body_length cyclomatic_complexity init(userSession: UserSessionProtocol, attributedStringBuilder: AttributedStringBuilderProtocol) { self.userSession = userSession self.attributedStringBuilder = attributedStringBuilder @@ -64,7 +64,17 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol .debounce(for: 0.1, scheduler: DispatchQueue.main) .removeDuplicates() .sink { [weak self] range in - self?.updateVisibleRange(range) + guard let self else { return } + + guard self.state.bindings.searchQuery.isEmpty else { + return + } + + if self.state.bindings.isScrolling { + self.updateVisibleRange(range, timelineLimit: SlidingSyncConstants.lastMessageTimelineLimit) + } else { + self.updateVisibleRange(range, timelineLimit: SlidingSyncConstants.timelinePrecachingTimelineLimit) + } } .store(in: &cancellables) @@ -234,7 +244,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol return room } - private func updateVisibleRange(_ range: Range) { + private func updateVisibleRange(_ range: Range, timelineLimit: UInt) { guard visibleRoomsSummaryProvider?.statePublisher.value == .live, !range.isEmpty else { return } @@ -246,6 +256,6 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol let lowerBound = max(0, range.lowerBound - Constants.slidingWindowBoundsPadding) let upperBound = min(Int(visibleRoomsSummaryProvider.countPublisher.value), range.upperBound + Constants.slidingWindowBoundsPadding) - visibleRoomsSummaryProvider.updateVisibleRange(lowerBound..() { - didSet { - if visibleItemIdentifiers != oldValue { - updateVisibleRange() - } - } - } + @State private var visibleItemIdentifiers = Set() + @State private var hasTriggeredInitialVisibleItemUpdate = false var body: some View { ScrollView { @@ -65,6 +61,10 @@ struct HomeScreen: View { .disableAutocorrection(true) } } + .introspectScrollView { scrollView in + guard scrollView != scrollViewAdapter.scrollView else { return } + scrollViewAdapter.scrollView = scrollView + } .scrollDismissesKeyboard(.immediately) .disabled(context.viewState.roomListMode == .skeletons) .animation(.elementDefault, value: context.viewState.showSessionVerificationBanner) @@ -76,6 +76,17 @@ struct HomeScreen: View { userMenuButton } } + .onChange(of: visibleItemIdentifiers) { _ in + if !hasTriggeredInitialVisibleItemUpdate { + updateVisibleRange() + hasTriggeredInitialVisibleItemUpdate = true + } + } + .onReceive(scrollViewAdapter.isScrolling) { isScrolling in + context.isScrolling = isScrolling + + updateVisibleRange() + } .background(Color.element.background) } diff --git a/ElementX/Sources/Services/Client/ClientProxy.swift b/ElementX/Sources/Services/Client/ClientProxy.swift index 1fbc9e471..7b3e996b1 100644 --- a/ElementX/Sources/Services/Client/ClientProxy.swift +++ b/ElementX/Sources/Services/Client/ClientProxy.swift @@ -259,7 +259,7 @@ class ClientProxy: ClientProxyProtocol { // Build the visibleRoomsSlidingSyncView here so that it can take advantage of the SS builder cold cache // We will still register the allRoomsSlidingSyncView later, and than will have no cache let visibleRoomsView = try SlidingSyncViewBuilder() - .timelineLimit(limit: 1) + .timelineLimit(limit: UInt32(SlidingSyncConstants.initialTimelineLimit)) // Starts off with zero to quickly load rooms, then goes to 1 while scrolling to quickly load last messages and 20 when the scrolling stops to load room history .requiredState(requiredState: slidingSyncRequiredState) .filters(filters: slidingSyncFilters) .name(name: "CurrentlyVisibleRooms") @@ -301,8 +301,8 @@ class ClientProxy: ClientProxyProtocol { // The allRoomsSlidingSyncView will be registered as soon as the visibleRoomsSlidingSyncView receives its first update visibleRoomsViewProxyStateObservationToken = visibleRoomsViewProxy.diffPublisher.sink { [weak self] _ in - MXLog.info("Visible rooms view received first update, registering all rooms view") - self?.registerAllRoomSlidingSyncView() + MXLog.info("Visible rooms view received first update, configuring views post initial sync") + self?.configureViewsPostInitialSync() self?.visibleRoomsViewProxyStateObservationToken = nil } } @@ -349,13 +349,21 @@ class ClientProxy: ClientProxyProtocol { tags: [], notTags: []) - private func registerAllRoomSlidingSyncView() { - guard let allRoomsSlidingSyncView else { - MXLog.error("All rooms sliding sync view unavailable") - return + private func configureViewsPostInitialSync() { + if let visibleRoomsSlidingSyncView { + MXLog.info("Setting visible rooms view timeline limit to \(SlidingSyncConstants.lastMessageTimelineLimit)") + visibleRoomsSlidingSyncView.setTimelineLimit(value: UInt32(SlidingSyncConstants.lastMessageTimelineLimit)) + } else { + MXLog.error("Visible rooms sliding sync view unavailable") + } + + if let allRoomsSlidingSyncView { + MXLog.info("Registering all rooms view") + _ = slidingSync?.addView(view: allRoomsSlidingSyncView) + } else { + MXLog.error("All rooms sliding sync view unavailable") } - _ = slidingSync?.addView(view: allRoomsSlidingSyncView) restartSync() } diff --git a/ElementX/Sources/Services/Client/ClientProxyProtocol.swift b/ElementX/Sources/Services/Client/ClientProxyProtocol.swift index d2d2aceb4..be34934f4 100644 --- a/ElementX/Sources/Services/Client/ClientProxyProtocol.swift +++ b/ElementX/Sources/Services/Client/ClientProxyProtocol.swift @@ -58,6 +58,12 @@ enum PushFormat { // } } +enum SlidingSyncConstants { + static let initialTimelineLimit: UInt = 0 + static let lastMessageTimelineLimit: UInt = 1 + static let timelinePrecachingTimelineLimit: UInt = 20 +} + protocol ClientProxyProtocol: AnyObject, MediaLoaderProtocol { var callbacks: PassthroughSubject { get } diff --git a/ElementX/Sources/Services/Client/SlidingSyncViewProxy.swift b/ElementX/Sources/Services/Client/SlidingSyncViewProxy.swift index cbf75dabd..c9d59d6fe 100644 --- a/ElementX/Sources/Services/Client/SlidingSyncViewProxy.swift +++ b/ElementX/Sources/Services/Client/SlidingSyncViewProxy.swift @@ -102,10 +102,11 @@ class SlidingSyncViewProxy { try slidingSync.getRoom(roomId: identifier) } - func updateVisibleRange(_ range: Range) { - MXLog.info("Setting sliding sync view range to \(range)") + func updateVisibleRange(_ range: Range, timelineLimit: UInt) { + MXLog.info("Setting sliding sync view range to \(range), timelineLimit: \(timelineLimit)") slidingSyncView.setRange(start: UInt32(range.lowerBound), end: UInt32(range.upperBound)) + slidingSyncView.setTimelineLimit(value: UInt32(timelineLimit)) visibleRangeUpdatePublisher.send(()) } diff --git a/ElementX/Sources/Services/Room/RoomSummary/MockRoomSummaryProvider.swift b/ElementX/Sources/Services/Room/RoomSummary/MockRoomSummaryProvider.swift index 60fba889f..d4dae9fb1 100644 --- a/ElementX/Sources/Services/Room/RoomSummary/MockRoomSummaryProvider.swift +++ b/ElementX/Sources/Services/Room/RoomSummary/MockRoomSummaryProvider.swift @@ -44,7 +44,7 @@ class MockRoomSummaryProvider: RoomSummaryProviderProtocol { } } - func updateVisibleRange(_ range: Range) { } + func updateVisibleRange(_ range: Range, timelineLimit: UInt) { } // MARK: - Private diff --git a/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProvider.swift b/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProvider.swift index 381bd5ec8..2d2fb3efa 100644 --- a/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProvider.swift +++ b/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProvider.swift @@ -61,8 +61,8 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol { .store(in: &cancellables) } - func updateVisibleRange(_ range: Range) { - slidingSyncViewProxy.updateVisibleRange(range) + func updateVisibleRange(_ range: Range, timelineLimit: UInt) { + slidingSyncViewProxy.updateVisibleRange(range, timelineLimit: timelineLimit) } // MARK: - Private diff --git a/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProviderProtocol.swift b/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProviderProtocol.swift index 9ecb5d7d3..ae62a79c0 100644 --- a/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProviderProtocol.swift +++ b/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProviderProtocol.swift @@ -50,5 +50,5 @@ protocol RoomSummaryProviderProtocol { /// Publishes the total number of rooms var countPublisher: CurrentValueSubject { get } - func updateVisibleRange(_ range: Range) + func updateVisibleRange(_ range: Range, timelineLimit: UInt) }