Files
letro-ios/ElementX/Sources/Screens/GlobalSearchScreen/GlobalSearchScreenViewModel.swift
Stefan Ceriu 40bf8a460d Automatically try reloading failed images on network changes (#3170)
* Add the networkMonitor to the NSE and delete the old MockMediaLoader

* Generate a MediaLoaderMock through AutoMockable and use it in the MediaProviderTests

* Implement an ImageProvider mechanism that automatically tries reloading images when connectivity is established again and use it for LoadableImages

* Merge the ImageProvider protocol back into the MediaProvider

* Address PR review comments

* Provide a default sdk client request configuration

* Address tasks not automatically cancelling themselves when views get deallocated
2024-08-15 18:20:19 +03:00

102 lines
3.8 KiB
Swift

//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Combine
import SwiftUI
typealias GlobalSearchScreenViewModelType = StateStoreViewModel<GlobalSearchScreenViewState, GlobalSearchScreenViewAction>
class GlobalSearchScreenViewModel: GlobalSearchScreenViewModelType, GlobalSearchScreenViewModelProtocol {
private let roomSummaryProvider: RoomSummaryProviderProtocol
private var actionsSubject: PassthroughSubject<GlobalSearchScreenViewModelAction, Never> = .init()
var actions: AnyPublisher<GlobalSearchScreenViewModelAction, Never> {
actionsSubject.eraseToAnyPublisher()
}
init(roomSummaryProvider: RoomSummaryProviderProtocol,
mediaProvider: MediaProviderProtocol) {
self.roomSummaryProvider = roomSummaryProvider
super.init(initialViewState: GlobalSearchScreenViewState(bindings: .init(searchQuery: "")),
mediaProvider: mediaProvider)
roomSummaryProvider.roomListPublisher
.receive(on: DispatchQueue.main)
.sink { [weak self] summaries in
self?.updateRooms(with: summaries)
}
.store(in: &cancellables)
context.$viewState
.map(\.bindings.searchQuery)
.removeDuplicates()
.sink { [weak self] searchQuery in
self?.roomSummaryProvider.setFilter(.search(query: searchQuery))
}
.store(in: &cancellables)
updateRooms(with: roomSummaryProvider.roomListPublisher.value)
}
// MARK: - Public
override func process(viewAction: GlobalSearchScreenViewAction) {
MXLog.info("View model: received view action: \(viewAction)")
switch viewAction {
case .dismiss:
actionsSubject.send(.dismiss)
roomSummaryProvider.setFilter(.all(filters: [])) // This is a shared provider
case .select(let roomID):
actionsSubject.send(.select(roomID: roomID))
case .reachedTop:
updateVisibleRange(edge: .top)
case .reachedBottom:
updateVisibleRange(edge: .bottom)
}
}
// MARK: - Private
private func updateRooms(with summaries: [RoomSummary]) {
state.rooms = summaries.compactMap { summary in
GlobalSearchRoom(id: summary.id,
name: summary.name,
alias: summary.canonicalAlias,
avatar: summary.avatar)
}
}
/// The actual range values don't matter as long as they contain the lower
/// or upper bounds. updateVisibleRange is a hybrid API that powers both
/// sliding sync visible range update and list paginations
/// For lists other than the home screen one we don't care about visible ranges,
/// we just need the respective bounds to be there to trigger a next page load or
/// a reset to just one page
private func updateVisibleRange(edge: UIRectEdge) {
switch edge {
case .top:
roomSummaryProvider.updateVisibleRange(0..<0)
case .bottom:
let roomCount = roomSummaryProvider.roomListPublisher.value.count
roomSummaryProvider.updateVisibleRange(roomCount..<roomCount)
default:
break
}
}
}