mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-02 21:33:26 +00:00
Implement the SpaceAddRoomsScreen. (#4951)
* Update proxies ready for adding rooms to a space. - ClientProxy.recentlyVisitedRooms to take a filter and to return room proxies. - Expose SpaceServiceProxy.addChildToSpace. * Implement the SpaceAddRoomsScreen. * Fix the designs of the selected carousels. Add more snapshot tests for inviting users. * Fix for screen using wrong avatar size.
This commit is contained in:
+142
@@ -0,0 +1,142 @@
|
||||
//
|
||||
// Copyright 2026 Element Creations 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 SwiftUI
|
||||
|
||||
typealias SpaceAddRoomsScreenViewModelType = StateStoreViewModelV2<SpaceAddRoomsScreenViewState, SpaceAddRoomsScreenViewAction>
|
||||
|
||||
class SpaceAddRoomsScreenViewModel: SpaceAddRoomsScreenViewModelType, SpaceAddRoomsScreenViewModelProtocol {
|
||||
private let spaceRoomListProxy: SpaceRoomListProxyProtocol
|
||||
private let spaceServiceProxy: SpaceServiceProxyProtocol
|
||||
private let roomSummaryProvider: RoomSummaryProviderProtocol
|
||||
private let userIndicatorController: UserIndicatorControllerProtocol
|
||||
|
||||
private var suggestedRooms: [SpaceAddRoomsScreenRoom] = []
|
||||
|
||||
private var actionsSubject: PassthroughSubject<SpaceAddRoomsScreenViewModelAction, Never> = .init()
|
||||
var actions: AnyPublisher<SpaceAddRoomsScreenViewModelAction, Never> {
|
||||
actionsSubject.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
init(spaceRoomListProxy: SpaceRoomListProxyProtocol,
|
||||
userSession: UserSessionProtocol,
|
||||
roomSummaryProvider: RoomSummaryProviderProtocol,
|
||||
userIndicatorController: UserIndicatorControllerProtocol) {
|
||||
self.spaceRoomListProxy = spaceRoomListProxy
|
||||
spaceServiceProxy = userSession.clientProxy.spaceService
|
||||
self.roomSummaryProvider = roomSummaryProvider
|
||||
self.userIndicatorController = userIndicatorController
|
||||
|
||||
super.init(initialViewState: SpaceAddRoomsScreenViewState(roomsSection: .init(type: .suggestions, rooms: [])),
|
||||
mediaProvider: userSession.mediaProvider)
|
||||
|
||||
roomSummaryProvider.roomListPublisher
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] _ in
|
||||
self?.updateRooms()
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
Task {
|
||||
let existingRooms = spaceRoomListProxy.spaceRoomsPublisher.value
|
||||
suggestedRooms = await userSession.clientProxy
|
||||
.recentlyVisitedRooms { roomProxy in
|
||||
!roomProxy.infoPublisher.value.isDirect && !existingRooms.contains { $0.id == roomProxy.id }
|
||||
}
|
||||
.map { .init(roomProxy: $0) }
|
||||
|
||||
if state.roomsSection.type == .suggestions {
|
||||
state.roomsSection = .init(type: .suggestions, rooms: suggestedRooms)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func process(viewAction: SpaceAddRoomsScreenViewAction) {
|
||||
switch viewAction {
|
||||
case .cancel:
|
||||
actionsSubject.send(.dismiss)
|
||||
case .reachedTop:
|
||||
updateVisibleRange(edge: .top)
|
||||
case .reachedBottom:
|
||||
updateVisibleRange(edge: .bottom)
|
||||
case .searchQueryChanged where state.bindings.searchQuery.isEmpty:
|
||||
roomSummaryProvider.setFilter(.all(filters: []))
|
||||
case .searchQueryChanged:
|
||||
roomSummaryProvider.setFilter(.search(query: state.bindings.searchQuery))
|
||||
case .toggleRoom(let room):
|
||||
toggleRoom(room)
|
||||
case .save:
|
||||
Task { await save() }
|
||||
}
|
||||
}
|
||||
|
||||
func stop() {
|
||||
// This is a shared provider so we should reset the filtering when we are done with the view
|
||||
roomSummaryProvider.setFilter(.all(filters: []))
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func updateRooms() {
|
||||
guard !state.bindings.searchQuery.isEmpty else {
|
||||
state.roomsSection = .init(type: .suggestions, rooms: suggestedRooms)
|
||||
return
|
||||
}
|
||||
|
||||
let existingRooms = spaceRoomListProxy.spaceRoomsPublisher.value
|
||||
var rooms = [SpaceAddRoomsScreenRoom]()
|
||||
|
||||
for summary in roomSummaryProvider.roomListPublisher.value where !summary.isDirect && !existingRooms.contains(where: { $0.id == summary.id }) {
|
||||
rooms.append(.init(summary: summary))
|
||||
}
|
||||
|
||||
state.roomsSection = .init(type: .searchResults, rooms: rooms)
|
||||
|
||||
// rooms crap
|
||||
}
|
||||
|
||||
/// 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
|
||||
}
|
||||
}
|
||||
|
||||
private func toggleRoom(_ room: SpaceAddRoomsScreenRoom) {
|
||||
if state.selectedRooms.contains(room) {
|
||||
state.selectedRooms.removeAll { $0.id == room.id }
|
||||
} else {
|
||||
state.selectedRooms.append(room)
|
||||
withElementAnimation(.easeInOut) { state.bindings.selectedRoomsPosition = room.id }
|
||||
}
|
||||
}
|
||||
|
||||
private func save() async {
|
||||
for room in state.selectedRooms {
|
||||
if case .failure(let error) = await spaceServiceProxy.addChild(room.id, to: spaceRoomListProxy.id) {
|
||||
MXLog.error("Failed adding room to space: \(error)")
|
||||
userIndicatorController.submitIndicator(UserIndicator(title: L10n.errorUnknown))
|
||||
updateRooms() // Hide any rooms that were already added.
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
actionsSubject.send(.dismiss)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user