mirror of
https://github.com/element-hq/element-x-ios.git
synced 2025-03-11 13:59:13 +00:00
Update subscriptions if visible range rooms change without the range itself changing (#3220)
* Remove room list range deduplication and allow subscribing to rooms when then update to come into the visible range. * Fixes #3088 - Update subscriptions if visible range rooms change without the range itself changing - we switched from range based subscribing with duplicate suppression to id based, but ids can change without ranges changing - the all rooms list fetches at least 1 event per rooms and can cause rooms to come in and out of the visible range without user interaction
This commit is contained in:
parent
062811cac4
commit
288b1e7896
@ -40,7 +40,7 @@ enum HomeScreenViewAction {
|
|||||||
case startChat
|
case startChat
|
||||||
case confirmRecoveryKey
|
case confirmRecoveryKey
|
||||||
case skipRecoveryKeyConfirmation
|
case skipRecoveryKeyConfirmation
|
||||||
case updateVisibleItemRange(range: Range<Int>, isScrolling: Bool)
|
case updateVisibleItemRange(Range<Int>)
|
||||||
case globalSearch
|
case globalSearch
|
||||||
case markRoomAsUnread(roomIdentifier: String)
|
case markRoomAsUnread(roomIdentifier: String)
|
||||||
case markRoomAsRead(roomIdentifier: String)
|
case markRoomAsRead(roomIdentifier: String)
|
||||||
|
@ -30,9 +30,6 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
|||||||
|
|
||||||
private var migrationCancellable: AnyCancellable?
|
private var migrationCancellable: AnyCancellable?
|
||||||
|
|
||||||
private var visibleItemRangeObservationToken: AnyCancellable?
|
|
||||||
private let visibleItemRangePublisher = CurrentValueSubject<(range: Range<Int>, isScrolling: Bool), Never>((0..<0, false))
|
|
||||||
|
|
||||||
private var actionsSubject: PassthroughSubject<HomeScreenViewModelAction, Never> = .init()
|
private var actionsSubject: PassthroughSubject<HomeScreenViewModelAction, Never> = .init()
|
||||||
var actions: AnyPublisher<HomeScreenViewModelAction, Never> {
|
var actions: AnyPublisher<HomeScreenViewModelAction, Never> {
|
||||||
actionsSubject.eraseToAnyPublisher()
|
actionsSubject.eraseToAnyPublisher()
|
||||||
@ -151,8 +148,8 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
|||||||
actionsSubject.send(.presentSecureBackupSettings)
|
actionsSubject.send(.presentSecureBackupSettings)
|
||||||
case .skipRecoveryKeyConfirmation:
|
case .skipRecoveryKeyConfirmation:
|
||||||
state.securityBannerMode = .dismissed
|
state.securityBannerMode = .dismissed
|
||||||
case .updateVisibleItemRange(let range, let isScrolling):
|
case .updateVisibleItemRange(let range):
|
||||||
visibleItemRangePublisher.send((range, isScrolling))
|
roomSummaryProvider?.updateVisibleRange(range)
|
||||||
case .startChat:
|
case .startChat:
|
||||||
actionsSubject.send(.presentStartChatScreen)
|
actionsSubject.send(.presentStartChatScreen)
|
||||||
case .globalSearch:
|
case .globalSearch:
|
||||||
@ -266,14 +263,9 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
|||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
roomSummaryProvider.roomListPublisher
|
roomSummaryProvider.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?.installListRangeModifiers()
|
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
}
|
}
|
||||||
@ -315,27 +307,6 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func installListRangeModifiers() {
|
|
||||||
guard visibleItemRangeObservationToken == nil else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
visibleItemRangeObservationToken = visibleItemRangePublisher
|
|
||||||
.filter { $0.isScrolling == false }
|
|
||||||
.throttle(for: 0.5, scheduler: DispatchQueue.main, latest: true)
|
|
||||||
.removeDuplicates(by: { $0.isScrolling == $1.isScrolling && $0.range == $1.range })
|
|
||||||
.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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func updateRooms() {
|
private func updateRooms() {
|
||||||
guard let roomSummaryProvider else {
|
guard let roomSummaryProvider else {
|
||||||
@ -352,20 +323,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
|||||||
|
|
||||||
state.rooms = rooms
|
state.rooms = rooms
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateVisibleRange(_ range: Range<Int>) {
|
|
||||||
guard !range.isEmpty else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let roomSummaryProvider else {
|
|
||||||
MXLog.error("Visible rooms summary provider unavailable")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
roomSummaryProvider.updateVisibleRange(range)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func markRoomAsFavourite(_ roomID: String, isFavourite: Bool) async {
|
private func markRoomAsFavourite(_ roomID: String, isFavourite: Bool) async {
|
||||||
guard case let .joined(roomProxy) = await userSession.clientProxy.roomForIdentifier(roomID) else {
|
guard case let .joined(roomProxy) = await userSession.clientProxy.roomForIdentifier(roomID) else {
|
||||||
MXLog.error("Failed retrieving room for identifier: \(roomID)")
|
MXLog.error("Failed retrieving room for identifier: \(roomID)")
|
||||||
|
@ -200,6 +200,8 @@ struct HomeScreenContent: View {
|
|||||||
|
|
||||||
private func delayedUpdateVisibleRange() {
|
private func delayedUpdateVisibleRange() {
|
||||||
guard let scrollView = scrollViewAdapter.scrollView,
|
guard let scrollView = scrollViewAdapter.scrollView,
|
||||||
|
scrollViewAdapter.isScrolling.value == false, // Ignore while scrolling
|
||||||
|
context.searchQuery.isEmpty == true, // Ignore while filtering
|
||||||
context.viewState.visibleRooms.count > 0 else {
|
context.viewState.visibleRooms.count > 0 else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -215,6 +217,6 @@ struct HomeScreenContent: View {
|
|||||||
let lastIndex = Int(max(0.0, scrollView.contentOffset.y + scrollView.bounds.height) / cellHeight)
|
let lastIndex = Int(max(0.0, scrollView.contentOffset.y + scrollView.bounds.height) / cellHeight)
|
||||||
|
|
||||||
// This will be deduped and throttled on the view model layer
|
// This will be deduped and throttled on the view model layer
|
||||||
context.send(viewAction: .updateVisibleItemRange(range: firstIndex..<lastIndex, isScrolling: scrollViewAdapter.isScrolling.value))
|
context.send(viewAction: .updateVisibleItemRange(firstIndex..<lastIndex))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,8 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
|
|||||||
|
|
||||||
private let serialDispatchQueue: DispatchQueue
|
private let serialDispatchQueue: DispatchQueue
|
||||||
|
|
||||||
|
private let visibleItemRangePublisher = CurrentValueSubject<Range<Int>, Never>(0..<0)
|
||||||
|
|
||||||
// periphery:ignore - retaining purpose
|
// periphery:ignore - retaining purpose
|
||||||
private var roomList: RoomListProtocol?
|
private var roomList: RoomListProtocol?
|
||||||
|
|
||||||
@ -80,6 +82,8 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
|
|||||||
.sink { [weak self] in self?.updateRoomsWithDiffs($0) }
|
.sink { [weak self] in self?.updateRoomsWithDiffs($0) }
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
|
setupVisibleRangeObservers()
|
||||||
|
|
||||||
setupNotificationSettingsSubscription()
|
setupNotificationSettingsSubscription()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,38 +118,7 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func updateVisibleRange(_ range: Range<Int>) {
|
func updateVisibleRange(_ range: Range<Int>) {
|
||||||
if range.upperBound >= rooms.count {
|
visibleItemRangePublisher.send(range)
|
||||||
listUpdatesSubscriptionResult?.controller().addOnePage()
|
|
||||||
} else if range.lowerBound == 0 {
|
|
||||||
listUpdatesSubscriptionResult?.controller().resetToOnePage()
|
|
||||||
}
|
|
||||||
|
|
||||||
guard shouldUpdateVisibleRange else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// The scroll view content size based visible range calculations might create large ranges
|
|
||||||
// This is just a safety check to not overload the backend
|
|
||||||
var range = range
|
|
||||||
if range.upperBound - range.lowerBound > SlidingSyncConstants.maximumVisibleRangeSize {
|
|
||||||
let upperBound = range.lowerBound + SlidingSyncConstants.maximumVisibleRangeSize
|
|
||||||
range = range.lowerBound..<upperBound
|
|
||||||
}
|
|
||||||
|
|
||||||
MXLog.info("\(name): Requesting subscriptions for visible range: \(range)")
|
|
||||||
|
|
||||||
let roomIDs = range
|
|
||||||
.filter { $0 < rooms.count }
|
|
||||||
.map { rooms[$0].id }
|
|
||||||
|
|
||||||
do {
|
|
||||||
try roomListService.subscribeToRooms(roomIds: roomIDs,
|
|
||||||
settings: .init(requiredState: SlidingSyncConstants.defaultRequiredState,
|
|
||||||
timelineLimit: SlidingSyncConstants.defaultTimelineLimit,
|
|
||||||
includeHeroes: false))
|
|
||||||
} catch {
|
|
||||||
MXLog.error("Failed subscribing to rooms with error: \(error)")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func setFilter(_ filter: RoomSummaryProviderFilter) {
|
func setFilter(_ filter: RoomSummaryProviderFilter) {
|
||||||
@ -167,7 +140,63 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
|
|
||||||
|
private func setupVisibleRangeObservers() {
|
||||||
|
visibleItemRangePublisher
|
||||||
|
.throttle(for: 0.5, scheduler: DispatchQueue.main, latest: true)
|
||||||
|
.removeDuplicates()
|
||||||
|
.sink { [weak self] range in
|
||||||
|
guard let self else { return }
|
||||||
|
|
||||||
|
MXLog.info("\(self.name): Updating visible range: \(range)")
|
||||||
|
|
||||||
|
if range.upperBound >= rooms.count {
|
||||||
|
listUpdatesSubscriptionResult?.controller().addOnePage()
|
||||||
|
} else if range.lowerBound == 0 {
|
||||||
|
listUpdatesSubscriptionResult?.controller().resetToOnePage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
|
||||||
|
visibleItemRangePublisher
|
||||||
|
.throttle(for: 0.5, scheduler: DispatchQueue.main, latest: true)
|
||||||
|
.filter { [weak self] range in
|
||||||
|
guard let self else { return false }
|
||||||
|
return !range.isEmpty && shouldUpdateVisibleRange
|
||||||
|
}
|
||||||
|
.compactMap { [weak self] (range: Range) -> [String]? in
|
||||||
|
guard let self else { return nil }
|
||||||
|
|
||||||
|
// The scroll view content size based visible range calculations might create large ranges
|
||||||
|
// This is just a safety check to not overload the backend
|
||||||
|
var range = range
|
||||||
|
if range.upperBound - range.lowerBound > SlidingSyncConstants.maximumVisibleRangeSize {
|
||||||
|
let upperBound = range.lowerBound + SlidingSyncConstants.maximumVisibleRangeSize
|
||||||
|
range = range.lowerBound..<upperBound
|
||||||
|
}
|
||||||
|
|
||||||
|
return range
|
||||||
|
.filter { $0 < self.rooms.count }
|
||||||
|
.map { self.rooms[$0].id }
|
||||||
|
}
|
||||||
|
.removeDuplicates()
|
||||||
|
.sink { [weak self] roomIDs in
|
||||||
|
guard let self else { return }
|
||||||
|
|
||||||
|
MXLog.debug("\(self.name): Updating subscriptions for visible rooms: \(roomIDs)")
|
||||||
|
|
||||||
|
do {
|
||||||
|
try roomListService.subscribeToRooms(roomIds: roomIDs,
|
||||||
|
settings: .init(requiredState: SlidingSyncConstants.defaultRequiredState,
|
||||||
|
timelineLimit: SlidingSyncConstants.defaultTimelineLimit,
|
||||||
|
includeHeroes: false))
|
||||||
|
} catch {
|
||||||
|
MXLog.error("Failed subscribing to rooms with error: \(error)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
}
|
||||||
|
|
||||||
fileprivate func updateRoomsWithDiffs(_ diffs: [RoomListEntriesUpdate]) {
|
fileprivate func updateRoomsWithDiffs(_ diffs: [RoomListEntriesUpdate]) {
|
||||||
let span = MXLog.createSpan("\(name).process_room_list_diffs")
|
let span = MXLog.createSpan("\(name).process_room_list_diffs")
|
||||||
span.enter()
|
span.enter()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user