mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-04 19:44:05 +00:00
Refactor the dynamic timeline modifiers and make sure they fire in the right order
This commit is contained in:
committed by
Stefan Ceriu
parent
49c41d8581
commit
cbfd2d1cca
@@ -39,7 +39,7 @@ enum HomeScreenViewAction {
|
|||||||
case userMenu(action: HomeScreenViewUserMenuAction)
|
case userMenu(action: HomeScreenViewUserMenuAction)
|
||||||
case verifySession
|
case verifySession
|
||||||
case skipSessionVerification
|
case skipSessionVerification
|
||||||
case updatedVisibleItemRange(Range<Int>)
|
case updateVisibleItemRange(range: Range<Int>, isScrolling: Bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
enum HomeScreenRoomListMode: CustomStringConvertible {
|
enum HomeScreenRoomListMode: CustomStringConvertible {
|
||||||
@@ -91,8 +91,6 @@ struct HomeScreenViewState: BindableState {
|
|||||||
struct HomeScreenViewStateBindings {
|
struct HomeScreenViewStateBindings {
|
||||||
var searchQuery = ""
|
var searchQuery = ""
|
||||||
|
|
||||||
var isScrolling = false
|
|
||||||
|
|
||||||
var alertInfo: AlertInfo<UUID>?
|
var alertInfo: AlertInfo<UUID>?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,8 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
|||||||
private let allRoomsSummaryProvider: RoomSummaryProviderProtocol?
|
private let allRoomsSummaryProvider: RoomSummaryProviderProtocol?
|
||||||
private let attributedStringBuilder: AttributedStringBuilderProtocol
|
private let attributedStringBuilder: AttributedStringBuilderProtocol
|
||||||
|
|
||||||
private let visibleItemRangePublisher = CurrentValueSubject<Range<Int>, Never>(0..<0)
|
private var visibleItemRangeObservationToken: AnyCancellable?
|
||||||
|
private let visibleItemRangePublisher = CurrentValueSubject<(range: Range<Int>, isScrolling: Bool), Never>((0..<0, false))
|
||||||
|
|
||||||
var callback: ((HomeScreenViewModelAction) -> Void)?
|
var callback: ((HomeScreenViewModelAction) -> Void)?
|
||||||
|
|
||||||
@@ -58,24 +59,6 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
|||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
visibleItemRangePublisher
|
|
||||||
.debounce(for: 0.1, scheduler: DispatchQueue.main)
|
|
||||||
.removeDuplicates()
|
|
||||||
.sink { [weak self] range in
|
|
||||||
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)
|
|
||||||
|
|
||||||
guard let visibleRoomsSummaryProvider, let allRoomsSummaryProvider else {
|
guard let visibleRoomsSummaryProvider, let allRoomsSummaryProvider else {
|
||||||
MXLog.error("Room summary provider unavailable")
|
MXLog.error("Room summary provider unavailable")
|
||||||
return
|
return
|
||||||
@@ -136,9 +119,14 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
|||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
allRoomsSummaryProvider.roomListPublisher
|
allRoomsSummaryProvider.roomListPublisher
|
||||||
|
.dropFirst(1) // We don't care about its initial value
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.sink { [weak self] _ in
|
.sink { [weak self] _ in
|
||||||
self?.updateRooms()
|
self?.updateRooms()
|
||||||
|
|
||||||
|
// Wait for the all rooms view to receive its first update before installing
|
||||||
|
// dynamic timeline modifiers
|
||||||
|
self?.installDynamicTimelineLimitModifiers()
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
@@ -166,8 +154,8 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
|||||||
callback?(.presentSessionVerificationScreen)
|
callback?(.presentSessionVerificationScreen)
|
||||||
case .skipSessionVerification:
|
case .skipSessionVerification:
|
||||||
state.showSessionVerificationBanner = false
|
state.showSessionVerificationBanner = false
|
||||||
case .updatedVisibleItemRange(let range):
|
case .updateVisibleItemRange(let range, let isScrolling):
|
||||||
visibleItemRangePublisher.send(range)
|
visibleItemRangePublisher.send((range, isScrolling))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,6 +170,29 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
|||||||
|
|
||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
|
|
||||||
|
/// We want the timeline limit to be set to 1 while scrolling the list so that last messages load up fast. We also want to set that back to 20 when the scrolling
|
||||||
|
/// stops to load room history. Also we don't want this to be setup before the initial sync is over so we only call it when the allRoomsSummaryProvider
|
||||||
|
/// first receives some changes
|
||||||
|
private func installDynamicTimelineLimitModifiers() {
|
||||||
|
guard visibleItemRangeObservationToken == nil else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
visibleItemRangeObservationToken = visibleItemRangePublisher
|
||||||
|
.removeDuplicates(by: { $0.isScrolling == $1.isScrolling && $0.range == $1.range })
|
||||||
|
.throttle(for: 0.5, scheduler: DispatchQueue.main, latest: true)
|
||||||
|
.sink { [weak self] value in
|
||||||
|
guard let self else { return }
|
||||||
|
|
||||||
|
// Ignore scrolling while filtering rooms
|
||||||
|
guard self.state.bindings.searchQuery.isEmpty else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.updateVisibleRange(value.range, timelineLimit: value.isScrolling ? SlidingSyncConstants.lastMessageTimelineLimit : SlidingSyncConstants.timelinePrecachingTimelineLimit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// This method will update all view state rooms by merging the data from both summary providers
|
/// This method will update all view state rooms by merging the data from both summary providers
|
||||||
/// If a room is empty in the visible room summary provider it will try to get it from the allRooms one
|
/// If a room is empty in the visible room summary provider it will try to get it from the allRooms one
|
||||||
/// This ensures that we show as many room details as possible without loading up timelines
|
/// This ensures that we show as many room details as possible without loading up timelines
|
||||||
|
|||||||
@@ -21,8 +21,11 @@ struct HomeScreen: View {
|
|||||||
|
|
||||||
@State private var scrollViewAdapter = ScrollViewAdapter()
|
@State private var scrollViewAdapter = ScrollViewAdapter()
|
||||||
@State private var showingLogoutConfirmation = false
|
@State private var showingLogoutConfirmation = false
|
||||||
@State private var visibleItemIdentifiers = Set<String>()
|
@State private var visibleItemIdentifiers = Set<String>() {
|
||||||
@State private var hasTriggeredInitialVisibleItemUpdate = false
|
didSet {
|
||||||
|
updateVisibleRange()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ScrollView {
|
ScrollView {
|
||||||
@@ -65,6 +68,11 @@ struct HomeScreen: View {
|
|||||||
guard scrollView != scrollViewAdapter.scrollView else { return }
|
guard scrollView != scrollViewAdapter.scrollView else { return }
|
||||||
scrollViewAdapter.scrollView = scrollView
|
scrollViewAdapter.scrollView = scrollView
|
||||||
}
|
}
|
||||||
|
.onReceive(scrollViewAdapter.isScrolling) { isScrolling in
|
||||||
|
if !isScrolling {
|
||||||
|
updateVisibleRange()
|
||||||
|
}
|
||||||
|
}
|
||||||
.scrollDismissesKeyboard(.immediately)
|
.scrollDismissesKeyboard(.immediately)
|
||||||
.disabled(context.viewState.roomListMode == .skeletons)
|
.disabled(context.viewState.roomListMode == .skeletons)
|
||||||
.animation(.elementDefault, value: context.viewState.showSessionVerificationBanner)
|
.animation(.elementDefault, value: context.viewState.showSessionVerificationBanner)
|
||||||
@@ -76,17 +84,6 @@ struct HomeScreen: View {
|
|||||||
userMenuButton
|
userMenuButton
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onChange(of: visibleItemIdentifiers) { _ in
|
|
||||||
if !hasTriggeredInitialVisibleItemUpdate {
|
|
||||||
updateVisibleRange()
|
|
||||||
hasTriggeredInitialVisibleItemUpdate = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.onReceive(scrollViewAdapter.isScrolling) { isScrolling in
|
|
||||||
context.isScrolling = isScrolling
|
|
||||||
|
|
||||||
updateVisibleRange()
|
|
||||||
}
|
|
||||||
.background(Color.element.background)
|
.background(Color.element.background)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,7 +193,7 @@ struct HomeScreen: View {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
context.send(viewAction: .updatedVisibleItemRange(firstIndex..<lastIndex))
|
context.send(viewAction: .updateVisibleItemRange(range: firstIndex..<lastIndex, isScrolling: scrollViewAdapter.isScrolling.value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ class SlidingSyncViewProxy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func updateVisibleRange(_ range: Range<Int>, timelineLimit: UInt) {
|
func updateVisibleRange(_ range: Range<Int>, timelineLimit: UInt) {
|
||||||
MXLog.info("Setting sliding sync view range to \(range), timelineLimit: \(timelineLimit)")
|
MXLog.info("Setting sliding sync view range to \(range), timeline limit: \(timelineLimit)")
|
||||||
|
|
||||||
slidingSyncView.setRange(start: UInt32(range.lowerBound), end: UInt32(range.upperBound))
|
slidingSyncView.setRange(start: UInt32(range.lowerBound), end: UInt32(range.upperBound))
|
||||||
slidingSyncView.setTimelineLimit(value: UInt32(timelineLimit))
|
slidingSyncView.setTimelineLimit(value: UInt32(timelineLimit))
|
||||||
|
|||||||
Reference in New Issue
Block a user