Display members of a space (#4629)

* Present members of a space

* present the members modally from the space

* Implemented a room members flow coordinator to make such flow more modular and reusable

this is required since we will need to reuse this module also in the space settings, and later we could also replace it in the RoomFlowCoordinator.

* the implementation to support at least the SpaceFlowCoordinator is done a follow UP should do the refactor.

* remove modal usage from the flow, we want to always be a navigation flow

* Improved and implemented the room navigation in the members flow coordinator

* pr suggestions and refactored the start chat flow and the invite screen

* updated copies for managing room members

* Update ElementX/Sources/Screens/Spaces/SpaceScreen/SpaceScreenViewModel.swift

Co-authored-by: Doug <6060466+pixlwave@users.noreply.github.com>

---------

Co-authored-by: Doug <6060466+pixlwave@users.noreply.github.com>
This commit is contained in:
Mauro
2025-10-27 13:17:50 +01:00
committed by GitHub
parent ab290e6a23
commit 0bc832f8a6
31 changed files with 561 additions and 217 deletions
@@ -18,41 +18,23 @@ class CreateRoomScreenViewModelTests: XCTestCase {
var userSession: UserSessionMock!
private let usersSubject = CurrentValueSubject<[UserProfileProxy], Never>([])
private var cancellables = Set<AnyCancellable>()
var context: CreateRoomViewModel.Context {
viewModel.context
}
override func setUpWithError() throws {
cancellables.removeAll()
clientProxy = ClientProxyMock(.init(userIDServerName: "matrix.org", userID: "@a:b.com"))
userSession = UserSessionMock(.init(clientProxy: clientProxy))
let parameters = CreateRoomFlowParameters()
usersSubject.send([.mockAlice, .mockBob, .mockCharlie])
ServiceLocator.shared.settings.knockingEnabled = true
let viewModel = CreateRoomViewModel(userSession: userSession,
createRoomParameters: .init(parameters),
selectedUsers: usersSubject.asCurrentValuePublisher(),
selectedUsers: [.mockAlice, .mockBob, .mockCharlie],
analytics: ServiceLocator.shared.analytics,
userIndicatorController: UserIndicatorControllerMock(),
appSettings: ServiceLocator.shared.settings)
self.viewModel = viewModel
viewModel.actions.sink { [weak self] action in
guard let self else { return }
switch action {
case .deselectUser(let user):
var selectedUsers = usersSubject.value
if let index = selectedUsers.firstIndex(where: { $0.userID == user.userID }) {
selectedUsers.remove(at: index)
}
usersSubject.send(selectedUsers)
default:
break
}
}
.store(in: &cancellables)
}
func testDeselectUser() {
@@ -15,17 +15,11 @@ import XCTest
class InviteUsersScreenViewModelTests: XCTestCase {
var viewModel: InviteUsersScreenViewModelProtocol!
var userDiscoveryService: UserDiscoveryServiceMock!
private var cancellables = Set<AnyCancellable>()
var context: InviteUsersScreenViewModel.Context {
viewModel.context
}
override func setUp() {
cancellables.removeAll()
}
func testSelectUser() {
setupWithRoomType(roomType: .draft)
XCTAssertTrue(context.viewState.selectedUsers.isEmpty)
@@ -56,7 +50,9 @@ class InviteUsersScreenViewModelTests: XCTestCase {
func testInviteButton() async throws {
let mockedMembers: [RoomMemberProxyMock] = [.mockAlice, .mockBob]
setupWithRoomType(roomType: .room(roomProxy: JoinedRoomProxyMock(.init(name: "test", members: mockedMembers))))
let roomProxy = JoinedRoomProxyMock(.init(name: "test", members: mockedMembers))
roomProxy.inviteUserIDReturnValue = .success(())
setupWithRoomType(roomType: .room(roomProxy: roomProxy))
let deferredState = deferFulfillment(viewModel.context.$viewState) { state in
state.isUserSelected(.mockAlice)
@@ -68,7 +64,7 @@ class InviteUsersScreenViewModelTests: XCTestCase {
let deferredAction = deferFulfillment(viewModel.actions) { action in
switch action {
case .invite:
case .dismiss:
return true
default:
return false
@@ -77,41 +73,20 @@ class InviteUsersScreenViewModelTests: XCTestCase {
context.send(viewAction: .proceed)
guard case let .invite(members) = try await deferredAction.fulfill() else {
XCTFail("Sent action should be 'invite'")
return
}
XCTAssertEqual(members, [RoomMemberProxyMock.mockAlice.userID])
try await deferredAction.fulfill()
XCTAssertEqual(roomProxy.inviteUserIDReceivedInvocations, [RoomMemberProxyMock.mockAlice.userID])
}
private func setupWithRoomType(roomType: InviteUsersScreenRoomType) {
let usersSubject = CurrentValueSubject<[UserProfileProxy], Never>([])
userDiscoveryService = UserDiscoveryServiceMock()
userDiscoveryService.searchProfilesWithReturnValue = .success([])
usersSubject.send([])
let viewModel = InviteUsersScreenViewModel(userSession: UserSessionMock(.init()),
selectedUsers: usersSubject.asCurrentValuePublisher(),
selectedUsers: nil,
roomType: roomType,
userDiscoveryService: userDiscoveryService,
userIndicatorController: UserIndicatorControllerMock())
userIndicatorController: UserIndicatorControllerMock(),
appSettings: ServiceLocator.shared.settings)
viewModel.state.usersSection = .init(type: .suggestions, users: [.mockAlice, .mockBob, .mockCharlie])
self.viewModel = viewModel
viewModel.actions.sink { action in
switch action {
case .toggleUser(let user):
var selectedUsers = usersSubject.value
if let index = selectedUsers.firstIndex(where: { $0.userID == user.userID }) {
selectedUsers.remove(at: index)
} else {
selectedUsers.append(user)
}
usersSubject.send(selectedUsers)
default:
break
}
}
.store(in: &cancellables)
}
}