diff --git a/ElementX/Sources/Screens/CreateRoom/CreateRoomCoordinator.swift b/ElementX/Sources/Screens/CreateRoom/CreateRoomCoordinator.swift index 409161973..7f010f1db 100644 --- a/ElementX/Sources/Screens/CreateRoom/CreateRoomCoordinator.swift +++ b/ElementX/Sources/Screens/CreateRoom/CreateRoomCoordinator.swift @@ -19,12 +19,14 @@ import SwiftUI struct CreateRoomCoordinatorParameters { let userSession: UserSessionProtocol - let createRoomParameters: CreateRoomVolatileParameters + let createRoomParameters: CurrentValuePublisher + let selectedUsers: CurrentValuePublisher<[UserProfile], Never> } enum CreateRoomCoordinatorAction { case createRoom case deselectUser(UserProfile) + case updateDetails(CreateRoomFlowParameters) } final class CreateRoomCoordinator: CoordinatorProtocol { @@ -39,7 +41,9 @@ final class CreateRoomCoordinator: CoordinatorProtocol { init(parameters: CreateRoomCoordinatorParameters) { self.parameters = parameters - viewModel = CreateRoomViewModel(userSession: parameters.userSession, createRoomParameters: parameters.createRoomParameters) + viewModel = CreateRoomViewModel(userSession: parameters.userSession, + createRoomParameters: parameters.createRoomParameters, + selectedUsers: parameters.selectedUsers) } func start() { @@ -50,6 +54,8 @@ final class CreateRoomCoordinator: CoordinatorProtocol { self.actionsSubject.send(.deselectUser(user)) case .createRoom: self.actionsSubject.send(.createRoom) + case .updateDetails(let details): + self.actionsSubject.send(.updateDetails(details)) } } .store(in: &cancellables) diff --git a/ElementX/Sources/Screens/CreateRoom/CreateRoomModels.swift b/ElementX/Sources/Screens/CreateRoom/CreateRoomModels.swift index e821a62f1..950d1b6fc 100644 --- a/ElementX/Sources/Screens/CreateRoom/CreateRoomModels.swift +++ b/ElementX/Sources/Screens/CreateRoom/CreateRoomModels.swift @@ -19,6 +19,7 @@ import Foundation enum CreateRoomViewModelAction { case createRoom case deselectUser(UserProfile) + case updateDetails(CreateRoomFlowParameters) } struct CreateRoomViewState: BindableState { diff --git a/ElementX/Sources/Screens/CreateRoom/CreateRoomViewModel.swift b/ElementX/Sources/Screens/CreateRoom/CreateRoomViewModel.swift index 5b8bd5221..2e34b1d3c 100644 --- a/ElementX/Sources/Screens/CreateRoom/CreateRoomViewModel.swift +++ b/ElementX/Sources/Screens/CreateRoom/CreateRoomViewModel.swift @@ -21,15 +21,25 @@ typealias CreateRoomViewModelType = StateStoreViewModel = .init() - private let createRoomParameters: CreateRoomVolatileParameters + private var createRoomParameters: CreateRoomFlowParameters var actions: AnyPublisher { actionsSubject.eraseToAnyPublisher() } + + init(userSession: UserSessionProtocol, + createRoomParameters: CurrentValuePublisher, + selectedUsers: CurrentValuePublisher<[UserProfile], Never>) { + let parameters = createRoomParameters.value + self.createRoomParameters = parameters + let bindings = CreateRoomViewStateBindings(roomName: parameters.name, roomTopic: parameters.topic, isRoomPrivate: parameters.isRoomPrivate) - init(userSession: UserSessionProtocol, createRoomParameters: CreateRoomVolatileParameters) { - let bindings = CreateRoomViewStateBindings(roomName: createRoomParameters.name, roomTopic: createRoomParameters.topic, isRoomPrivate: createRoomParameters.isRoomPrivate) - self.createRoomParameters = createRoomParameters - super.init(initialViewState: CreateRoomViewState(selectedUsers: createRoomParameters.selectedUsers, bindings: bindings), imageProvider: userSession.mediaProvider) + super.init(initialViewState: CreateRoomViewState(selectedUsers: selectedUsers.value, bindings: bindings), imageProvider: userSession.mediaProvider) + + selectedUsers + .sink { [weak self] users in + self?.state.selectedUsers = users + } + .store(in: &cancellables) setupBindings() } @@ -41,7 +51,6 @@ class CreateRoomViewModel: CreateRoomViewModelType, CreateRoomViewModelProtocol case .createRoom: actionsSubject.send(.createRoom) case .deselectUser(let user): - state.selectedUsers.removeAll(where: { $0.userID == user.userID }) actionsSubject.send(.deselectUser(user)) } } @@ -51,10 +60,13 @@ class CreateRoomViewModel: CreateRoomViewModelType, CreateRoomViewModelProtocol private func setupBindings() { context.$viewState .map(\.bindings) + .throttle(for: 0.5, scheduler: DispatchQueue.main, latest: true) .sink { [weak self] bindings in - self?.createRoomParameters.name = bindings.roomName - self?.createRoomParameters.topic = bindings.roomTopic - self?.createRoomParameters.isRoomPrivate = bindings.isRoomPrivate + guard let self else { return } + createRoomParameters.name = bindings.roomName + createRoomParameters.topic = bindings.roomTopic + createRoomParameters.isRoomPrivate = bindings.isRoomPrivate + actionsSubject.send(.updateDetails(createRoomParameters)) } .store(in: &cancellables) } diff --git a/ElementX/Sources/Screens/CreateRoom/View/CreateRoomScreen.swift b/ElementX/Sources/Screens/CreateRoom/View/CreateRoomScreen.swift index ccecec057..0ac8e6d85 100644 --- a/ElementX/Sources/Screens/CreateRoom/View/CreateRoomScreen.swift +++ b/ElementX/Sources/Screens/CreateRoom/View/CreateRoomScreen.swift @@ -160,16 +160,17 @@ struct CreateRoom_Previews: PreviewProvider { static let viewModel = { let userSession = MockUserSession(clientProxy: MockClientProxy(userID: "@userid:example.com"), mediaProvider: MockMediaProvider()) - let parameters = CreateRoomVolatileParameters() - parameters.selectedUsers = [.mockAlice, .mockBob, .mockCharlie] - return CreateRoomViewModel(userSession: userSession, createRoomParameters: parameters) + let parameters = CreateRoomFlowParameters() + let selectedUsers: [UserProfile] = [.mockAlice, .mockBob, .mockCharlie] + + return CreateRoomViewModel(userSession: userSession, createRoomParameters: .init(parameters), selectedUsers: .init(selectedUsers)) }() static let emtpyViewModel = { let userSession = MockUserSession(clientProxy: MockClientProxy(userID: "@userid:example.com"), mediaProvider: MockMediaProvider()) - let parameters = CreateRoomVolatileParameters() - return CreateRoomViewModel(userSession: userSession, createRoomParameters: parameters) + let parameters = CreateRoomFlowParameters() + return CreateRoomViewModel(userSession: userSession, createRoomParameters: .init(parameters), selectedUsers: .init([])) }() static var previews: some View { diff --git a/ElementX/Sources/Screens/InviteUsersScreen/InviteUsersScreenCoordinator.swift b/ElementX/Sources/Screens/InviteUsersScreen/InviteUsersScreenCoordinator.swift index fd42481e7..ed994b5b9 100644 --- a/ElementX/Sources/Screens/InviteUsersScreen/InviteUsersScreenCoordinator.swift +++ b/ElementX/Sources/Screens/InviteUsersScreen/InviteUsersScreenCoordinator.swift @@ -18,14 +18,15 @@ import Combine import SwiftUI struct InviteUsersScreenCoordinatorParameters { - let navigationStackCoordinator: NavigationStackCoordinator? let userSession: UserSessionProtocol let userDiscoveryService: UserDiscoveryServiceProtocol - let createRoomParameters: CreateRoomVolatileParameters? + let selectedUsers: CurrentValuePublisher<[UserProfile], Never> } enum InviteUsersScreenCoordinatorAction { case close + case proceed + case toggleUser(UserProfile) } final class InviteUsersScreenCoordinator: CoordinatorProtocol { @@ -41,7 +42,7 @@ final class InviteUsersScreenCoordinator: CoordinatorProtocol { init(parameters: InviteUsersScreenCoordinatorParameters) { self.parameters = parameters - viewModel = InviteUsersScreenViewModel(userSession: parameters.userSession, userDiscoveryService: parameters.userDiscoveryService) + viewModel = InviteUsersScreenViewModel(selectedUsers: parameters.selectedUsers, userSession: parameters.userSession, userDiscoveryService: parameters.userDiscoveryService) } func start() { @@ -49,33 +50,17 @@ final class InviteUsersScreenCoordinator: CoordinatorProtocol { guard let self else { return } switch action { case .close: - self.actionsSubject.send(.close) - case .proceed(let users): - self.openCreateRoomScreenWith(users) + case .proceed: + self.actionsSubject.send(.proceed) + case .toggleUser(let user): + self.actionsSubject.send(.toggleUser(user)) } } .store(in: &cancellables) } - + func toPresentable() -> AnyView { AnyView(InviteUsersScreen(context: viewModel.context)) } - - private func openCreateRoomScreenWith(_ users: [UserProfile]) { - guard let createRoomParameters = parameters.createRoomParameters else { return } - createRoomParameters.selectedUsers = users - let paramenters = CreateRoomCoordinatorParameters(userSession: parameters.userSession, createRoomParameters: createRoomParameters) - let coordinator = CreateRoomCoordinator(parameters: paramenters) - coordinator.actions.sink { [weak self] result in - switch result { - case .deselectUser(let user): - self?.viewModel.context.send(viewAction: .deselectUser(user)) - default: - break - } - } - .store(in: &cancellables) - parameters.navigationStackCoordinator?.push(coordinator) - } } diff --git a/ElementX/Sources/Screens/InviteUsersScreen/InviteUsersScreenModels.swift b/ElementX/Sources/Screens/InviteUsersScreen/InviteUsersScreenModels.swift index 5b44f1c1a..778ef2b91 100644 --- a/ElementX/Sources/Screens/InviteUsersScreen/InviteUsersScreenModels.swift +++ b/ElementX/Sources/Screens/InviteUsersScreen/InviteUsersScreenModels.swift @@ -22,13 +22,15 @@ enum InviteUsersScreenErrorType: Error { enum InviteUsersScreenViewModelAction { case close - case proceed(users: [UserProfile]) + case proceed + case toggleUser(UserProfile) } struct InviteUsersScreenViewState: BindableState { var bindings = InviteUsersScreenViewStateBindings() var usersSection: UserDiscoverySection = .init(type: .suggestions, users: []) + var selectedUsers: [UserProfile] = [] var isSearching: Bool { @@ -56,6 +58,5 @@ struct InviteUsersScreenViewStateBindings { enum InviteUsersScreenViewAction { case close case proceed - case tapUser(UserProfile) - case deselectUser(UserProfile) + case toggleUser(UserProfile) } diff --git a/ElementX/Sources/Screens/InviteUsersScreen/InviteUsersScreenViewModel.swift b/ElementX/Sources/Screens/InviteUsersScreen/InviteUsersScreenViewModel.swift index 36ccd801a..7650210f1 100644 --- a/ElementX/Sources/Screens/InviteUsersScreen/InviteUsersScreenViewModel.swift +++ b/ElementX/Sources/Screens/InviteUsersScreen/InviteUsersScreenViewModel.swift @@ -28,10 +28,16 @@ class InviteUsersScreenViewModel: InviteUsersScreenViewModelType, InviteUsersScr actionsSubject.eraseToAnyPublisher() } - init(userSession: UserSessionProtocol, userDiscoveryService: UserDiscoveryServiceProtocol) { + init(selectedUsers: CurrentValuePublisher<[UserProfile], Never>, userSession: UserSessionProtocol, userDiscoveryService: UserDiscoveryServiceProtocol) { self.userSession = userSession self.userDiscoveryService = userDiscoveryService - super.init(initialViewState: InviteUsersScreenViewState(), imageProvider: userSession.mediaProvider) + super.init(initialViewState: InviteUsersScreenViewState(selectedUsers: selectedUsers.value), imageProvider: userSession.mediaProvider) + + selectedUsers + .sink { [weak self] users in + self?.state.selectedUsers = users + } + .store(in: &cancellables) setupSubscriptions() } @@ -43,27 +49,11 @@ class InviteUsersScreenViewModel: InviteUsersScreenViewModelType, InviteUsersScr case .close: actionsSubject.send(.close) case .proceed: - actionsSubject.send(.proceed(users: state.selectedUsers)) - case .tapUser(let user): - if state.isUserSelected(user) { - deselect(user) - } else { - select(user) - } - case .deselectUser(let user): - deselect(user) + actionsSubject.send(.proceed) + case .toggleUser(let user): + actionsSubject.send(.toggleUser(user)) } } - - private func select(_ user: UserProfile) { - state.selectedUsers.append(user) - state.scrollToLastID = user.userID - } - - private func deselect(_ user: UserProfile) { - state.selectedUsers.removeAll(where: { $0.userID == user.userID }) - state.scrollToLastID = nil - } // MARK: - Private diff --git a/ElementX/Sources/Screens/InviteUsersScreen/View/InviteUsersScreen.swift b/ElementX/Sources/Screens/InviteUsersScreen/View/InviteUsersScreen.swift index 20c2e1f4f..545bc11da 100644 --- a/ElementX/Sources/Screens/InviteUsersScreen/View/InviteUsersScreen.swift +++ b/ElementX/Sources/Screens/InviteUsersScreen/View/InviteUsersScreen.swift @@ -67,7 +67,7 @@ struct InviteUsersScreen: View { private var usersSection: some View { Section { ForEach(context.viewState.usersSection.users, id: \.userID) { user in - Button { context.send(viewAction: .tapUser(user)) } label: { + Button { context.send(viewAction: .toggleUser(user)) } label: { UserProfileCell(user: user, imageProvider: context.imageProvider) } @@ -112,7 +112,7 @@ struct InviteUsersScreen: View { } private func deselect(_ user: UserProfile) { - context.send(viewAction: .deselectUser(user)) + context.send(viewAction: .toggleUser(user)) } } @@ -125,7 +125,7 @@ struct InviteUsersScreen_Previews: PreviewProvider { let userDiscoveryService = UserDiscoveryServiceMock() userDiscoveryService.fetchSuggestionsReturnValue = .success([.mockAlice]) userDiscoveryService.searchProfilesWithReturnValue = .success([.mockAlice]) - return InviteUsersScreenViewModel(userSession: userSession, userDiscoveryService: userDiscoveryService) + return InviteUsersScreenViewModel(selectedUsers: .init([]), userSession: userSession, userDiscoveryService: userDiscoveryService) }() static var previews: some View { diff --git a/ElementX/Sources/Screens/StartChatScreen/StartChatScreenCoordinator.swift b/ElementX/Sources/Screens/StartChatScreen/StartChatScreenCoordinator.swift index a3b2614b9..d1082bc08 100644 --- a/ElementX/Sources/Screens/StartChatScreen/StartChatScreenCoordinator.swift +++ b/ElementX/Sources/Screens/StartChatScreen/StartChatScreenCoordinator.swift @@ -35,8 +35,15 @@ final class StartChatScreenCoordinator: CoordinatorProtocol { private let actionsSubject: PassthroughSubject = .init() private var cancellables: Set = .init() - // this is needed to persist some data in this flow and then destroy them when the flow is eneded - private var createRoomParameters = CreateRoomVolatileParameters() + private var createRoomParameters = CurrentValueSubject(.init()) + private var createRoomParametersPublisher: CurrentValuePublisher { + createRoomParameters.asCurrentValuePublisher() + } + + private let selectedUsers = CurrentValueSubject<[UserProfile], Never>([]) + private var selectedUsersPublisher: CurrentValuePublisher<[UserProfile], Never> { + selectedUsers.asCurrentValuePublisher() + } var actions: AnyPublisher { actionsSubject.eraseToAnyPublisher() @@ -73,19 +80,59 @@ final class StartChatScreenCoordinator: CoordinatorProtocol { // MARK: - Private private func presentInviteUsersScreen() { - createRoomParameters = .init() - let inviteParameters = InviteUsersScreenCoordinatorParameters(navigationStackCoordinator: parameters.navigationStackCoordinator, - userSession: parameters.userSession, + let inviteParameters = InviteUsersScreenCoordinatorParameters(userSession: parameters.userSession, userDiscoveryService: parameters.userDiscoveryService, - createRoomParameters: createRoomParameters) + selectedUsers: selectedUsersPublisher) let coordinator = InviteUsersScreenCoordinator(parameters: inviteParameters) coordinator.actions.sink { [weak self] result in + guard let self else { return } + switch result { case .close: - self?.parameters.navigationStackCoordinator?.pop() + parameters.navigationStackCoordinator?.pop() + case .proceed: + openCreateRoomScreen() + case .toggleUser(let user): + toggleUser(user) } } .store(in: &cancellables) + + parameters.navigationStackCoordinator?.push(coordinator) { [weak self] in + self?.createRoomParameters.send(.init()) + self?.selectedUsers.send([]) + } + } + + private func openCreateRoomScreen() { + let paramenters = CreateRoomCoordinatorParameters(userSession: parameters.userSession, + createRoomParameters: createRoomParametersPublisher, + selectedUsers: selectedUsersPublisher) + let coordinator = CreateRoomCoordinator(parameters: paramenters) + coordinator.actions.sink { [weak self] result in + switch result { + case .deselectUser(let user): + self?.toggleUser(user) + case .updateDetails(let details): + self?.createRoomParameters.send(details) + case .createRoom: + break + } + } + .store(in: &cancellables) + parameters.navigationStackCoordinator?.push(coordinator) } + + // MARK: - Private + + private func toggleUser(_ user: UserProfile) { + var selectedUsers = selectedUsers.value + if let index = selectedUsers.firstIndex(where: { $0.userID == user.userID }) { + selectedUsers.remove(at: index) + } else { + selectedUsers.append(user) + } + self.selectedUsers.send(selectedUsers) + } } diff --git a/ElementX/Sources/Services/CreateRoom/CreateRoomParameters.swift b/ElementX/Sources/Services/CreateRoom/CreateRoomFlowParameters.swift similarity index 82% rename from ElementX/Sources/Services/CreateRoom/CreateRoomParameters.swift rename to ElementX/Sources/Services/CreateRoom/CreateRoomFlowParameters.swift index ffdb628bf..88eed2f61 100644 --- a/ElementX/Sources/Services/CreateRoom/CreateRoomParameters.swift +++ b/ElementX/Sources/Services/CreateRoom/CreateRoomFlowParameters.swift @@ -16,10 +16,9 @@ import Foundation -/// This parameters are only used in the create room flow for having a a volatile persisted object that will be disposed once the flow is ended -class CreateRoomVolatileParameters { +/// This parameters are only used in the create room flow for having persisted informations between screens +struct CreateRoomFlowParameters { var name = "" var topic = "" - var selectedUsers: [UserProfile] = [] var isRoomPrivate = true } diff --git a/ElementX/Sources/UITests/UITestsAppCoordinator.swift b/ElementX/Sources/UITests/UITestsAppCoordinator.swift index e28df8871..6c45f3c36 100644 --- a/ElementX/Sources/UITests/UITestsAppCoordinator.swift +++ b/ElementX/Sources/UITests/UITestsAppCoordinator.swift @@ -14,6 +14,7 @@ // limitations under the License. // +import Combine import SwiftUI import UIKit @@ -53,6 +54,7 @@ class MockScreen: Identifiable { let id: UITestsScreenIdentifier private var retainedState = [Any]() + private var cancellables: Set = [] init(id: UITestsScreenIdentifier) { self.id = id @@ -409,16 +411,32 @@ class MockScreen: Identifiable { userDiscoveryMock.fetchSuggestionsReturnValue = .success([.mockAlice, .mockBob, .mockCharlie]) userDiscoveryMock.searchProfilesWithReturnValue = .success([]) let userSession = MockUserSession(clientProxy: MockClientProxy(userID: "@mock:client.com"), mediaProvider: MockMediaProvider()) - let coordinator = InviteUsersScreenCoordinator(parameters: .init(navigationStackCoordinator: navigationStackCoordinator, userSession: userSession, userDiscoveryService: userDiscoveryMock, createRoomParameters: nil)) + let usersSubject = CurrentValueSubject<[UserProfile], Never>([]) + let coordinator = InviteUsersScreenCoordinator(parameters: .init(userSession: userSession, userDiscoveryService: userDiscoveryMock, selectedUsers: usersSubject.asCurrentValuePublisher())) + coordinator.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) navigationStackCoordinator.setRootCoordinator(coordinator) return navigationStackCoordinator case .createRoom: let navigationStackCoordinator = NavigationStackCoordinator() let clientProxy = MockClientProxy(userID: "@mock:client.com") let mockUserSession = MockUserSession(clientProxy: clientProxy, mediaProvider: MockMediaProvider()) - let createRoomParameters = CreateRoomVolatileParameters() - createRoomParameters.selectedUsers = [.mockAlice, .mockBob, .mockCharlie] - let parameters = CreateRoomCoordinatorParameters(userSession: mockUserSession, createRoomParameters: createRoomParameters) + let createRoomParameters = CreateRoomFlowParameters() + let selectedUsers: [UserProfile] = [.mockAlice, .mockBob, .mockCharlie] + let parameters = CreateRoomCoordinatorParameters(userSession: mockUserSession, createRoomParameters: .init(createRoomParameters), selectedUsers: .init(selectedUsers)) let coordinator = CreateRoomCoordinator(parameters: parameters) navigationStackCoordinator.setRootCoordinator(coordinator) return navigationStackCoordinator @@ -426,8 +444,8 @@ class MockScreen: Identifiable { let navigationStackCoordinator = NavigationStackCoordinator() let clientProxy = MockClientProxy(userID: "@mock:client.com") let mockUserSession = MockUserSession(clientProxy: clientProxy, mediaProvider: MockMediaProvider()) - let createRoomParameters = CreateRoomVolatileParameters() - let parameters = CreateRoomCoordinatorParameters(userSession: mockUserSession, createRoomParameters: createRoomParameters) + let createRoomParameters = CreateRoomFlowParameters() + let parameters = CreateRoomCoordinatorParameters(userSession: mockUserSession, createRoomParameters: .init(createRoomParameters), selectedUsers: .init([])) let coordinator = CreateRoomCoordinator(parameters: parameters) navigationStackCoordinator.setRootCoordinator(coordinator) return navigationStackCoordinator diff --git a/UITests/Sources/CreateRoomScreenUITests.swift b/UITests/Sources/CreateRoomScreenUITests.swift index dfb9fe97c..3b5cd5933 100644 --- a/UITests/Sources/CreateRoomScreenUITests.swift +++ b/UITests/Sources/CreateRoomScreenUITests.swift @@ -30,13 +30,8 @@ class CreateRoomScreenUITests: XCTestCase { func testLongInputNameText() { let app = Application.launch(.createRoom) - let roomName: String - if UIDevice.current.userInterfaceIdiom == .pad { - roomName = "Room name very very very very very very very very very very very very very very very very long" - } else { - roomName = "Room name very very very very long" - } - app.textFields[A11yIdentifiers.createRoomScreen.roomName].clearAndTypeText(roomName) + app.textFields[A11yIdentifiers.createRoomScreen.roomName].tap() + app.textFields[A11yIdentifiers.createRoomScreen.roomName].typeText("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx") app.assertScreenshot(.createRoom, step: 2) } diff --git a/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.createRoom-0.png b/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.createRoom-0.png index f2beab479..7910e4f5b 100644 --- a/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.createRoom-0.png +++ b/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.createRoom-0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2ce58d664380c881e9f793d01897229f4f9afce1b6d6e2614274c780e4105606 -size 140709 +oid sha256:e6df5a4b0835895c39f5a036ad83a8ce07953943bee6e23ee337c820f2655103 +size 141995 diff --git a/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.createRoom-1.png b/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.createRoom-1.png index de1c70293..078e0fc9c 100644 --- a/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.createRoom-1.png +++ b/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.createRoom-1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dd40abfd78340148ab9640d9f57329381e177782364085eefe063782b76451a3 -size 117434 +oid sha256:5557c20994afe27635dcb27896d882e06516f25bd09169daa238d1fbdae3398a +size 119317 diff --git a/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.createRoom-2.png b/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.createRoom-2.png index 10ce344ec..f78870cf0 100644 --- a/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.createRoom-2.png +++ b/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.createRoom-2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7843b746bdb39e5d3f11588f065f7cfd37d03b33029d5a2dcee061408d4e068b -size 212635 +oid sha256:6adf918bbcd82a2a3db96d12b371d95dd9fe6c275e6b79691c286d4e91b43d0f +size 208763 diff --git a/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.createRoom-3.png b/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.createRoom-3.png index 1098cd60d..eb176c3cf 100644 --- a/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.createRoom-3.png +++ b/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.createRoom-3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0197617ed76b9a17cde9d22323bbe374d78fbe9596e2c31928e2991482a7128a -size 212483 +oid sha256:8ae40b042699968252d3ccec09c2f186422c3ac0ce6b9a23335f560499d5fe26 +size 213547 diff --git a/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.createRoom-0.png b/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.createRoom-0.png index 0282faf37..df21141a7 100644 --- a/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.createRoom-0.png +++ b/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.createRoom-0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3cdecb8eda71e1142725931aafed538e61b01ee571c2bac0fc64500cc5b64af4 -size 188211 +oid sha256:5fa84fc2b9e3bf1de16dcac03cae8ddc6ab367c6a8d700dc17f3b911db5f7139 +size 188055 diff --git a/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.createRoom-1.png b/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.createRoom-1.png index ebf131c15..9bed385b6 100644 --- a/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.createRoom-1.png +++ b/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.createRoom-1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6ced89105404a17c6cbd117ff64639d2a0ffe688a99d289e142d76c3ad031187 -size 157443 +oid sha256:5bb5d31ab43bd6766306464bfbd7915accaf560b0069d19ee450d2721addc4a3 +size 155277 diff --git a/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.createRoom-2.png b/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.createRoom-2.png index 744e70f55..3f61139de 100644 --- a/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.createRoom-2.png +++ b/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.createRoom-2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:28f3f129db6a100cc7c24c1419ecdd319820d1b5b23dfcd942648fc51469152d -size 208353 +oid sha256:46ae80a1bc4f1f2e2eb1536302d589c7b8561c3204035c3b4f8f9a05336e9ded +size 206718 diff --git a/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.createRoom-3.png b/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.createRoom-3.png index af32eaadb..6fd8b572d 100644 --- a/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.createRoom-3.png +++ b/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.createRoom-3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5f29aefa3158b91801ab7291edc7ad824de57a016ed151358fb1a0a15b9e4840 -size 211860 +oid sha256:65d4ff5bf36a0da77ff4ca8b9f37508ddc3a420f4537cbf4f0eb1b8e8db1246e +size 212688 diff --git a/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.createRoom-0.png b/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.createRoom-0.png index f880932b2..22ae413e5 100644 --- a/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.createRoom-0.png +++ b/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.createRoom-0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d76422f5c7ca9259dd0af19766553c089a4af147277ba6d9b046daaed833021d -size 170045 +oid sha256:35b0814944765d14aeb6196dd58b00f30f327347ca9a342d5b4cf51237d70a93 +size 171345 diff --git a/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.createRoom-1.png b/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.createRoom-1.png index c74ee1a5a..843b4c7eb 100644 --- a/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.createRoom-1.png +++ b/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.createRoom-1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ef2da36c0dc9db449d0e370df85e2794258c29fc4dea1c2b9b778fda171e61b7 -size 146542 +oid sha256:7092d1228d25e650595b07d6bdba7eb5ae9619af5c769a28544cc456817fbf10 +size 149594 diff --git a/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.createRoom-2.png b/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.createRoom-2.png index 15a8bebdf..f9df92443 100644 --- a/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.createRoom-2.png +++ b/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.createRoom-2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:90b638967b7a6a5e9f840014c80d5628a3583f2fde3294e2c569708ae62eaee8 -size 240439 +oid sha256:9253afae073a3da9eb1439d074569cd7f15061e7f42e57e0a25252f0b909911e +size 236703 diff --git a/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.createRoom-3.png b/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.createRoom-3.png index 5a9608d4b..4efbb41d0 100644 --- a/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.createRoom-3.png +++ b/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.createRoom-3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:543af442458cb4292c3b0cd22a56cb7376d69776fdc80e0b0eefeb6b8ce867ea -size 240524 +oid sha256:980a38d3329e437ae0756d3e05d552786d9d4b05276dfe20a316e99348af812a +size 241512 diff --git a/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.createRoom-0.png b/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.createRoom-0.png index 811f7ab22..17514d26b 100644 --- a/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.createRoom-0.png +++ b/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.createRoom-0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:baf8dfb833a68eb3424121895619d6cdb62d3684784f4990f1d22397cab84f7e -size 236641 +oid sha256:cb1e31c93ef5a063d75df8d23a687e31e40078b675f3e199ff7375069068680f +size 238800 diff --git a/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.createRoom-1.png b/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.createRoom-1.png index 9f6dfe6e8..0dc825b66 100644 --- a/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.createRoom-1.png +++ b/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.createRoom-1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3076f5da83494b9c375bfa6a632806c475603cccd8408407f3020da00a645db3 -size 212717 +oid sha256:9c68ff019a95bbc7dd07060139eca4bfc6029984def7ca2246ba2e95b647935c +size 220005 diff --git a/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.createRoom-2.png b/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.createRoom-2.png index 1910b4db7..78d646c31 100644 --- a/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.createRoom-2.png +++ b/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.createRoom-2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:af0eec1935090fd30bcc49522e7f8c43502d975313ea3b45ccc3c335effd0f54 -size 222778 +oid sha256:0bbd886e810733b0799f5cbc84dcd8b6153c2af5ef2ebfe0e233f37fd1335e61 +size 223721 diff --git a/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.createRoom-3.png b/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.createRoom-3.png index e36388fa6..3b189265d 100644 --- a/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.createRoom-3.png +++ b/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.createRoom-3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8789a81c437710854ab852b937cdfaeca3d9852337f370a31c0235f587c0002e -size 223291 +oid sha256:df90685e30f3f87791154ba9af26fead49fe05336c7e19dad73f290ab9c07c37 +size 221890 diff --git a/UnitTests/Sources/CreateRoomViewModelTests.swift b/UnitTests/Sources/CreateRoomViewModelTests.swift index 47c9cc02b..5e760cc6d 100644 --- a/UnitTests/Sources/CreateRoomViewModelTests.swift +++ b/UnitTests/Sources/CreateRoomViewModelTests.swift @@ -14,6 +14,7 @@ // limitations under the License. // +import Combine import XCTest @testable import ElementX @@ -24,6 +25,9 @@ class CreateRoomScreenViewModelTests: XCTestCase { var clientProxy: MockClientProxy! var userSession: MockUserSession! + private let usersSubject = CurrentValueSubject<[UserProfile], Never>([]) + private var cancellables: Set = [] + var context: CreateRoomViewModel.Context { viewModel.context } @@ -31,10 +35,25 @@ class CreateRoomScreenViewModelTests: XCTestCase { override func setUpWithError() throws { clientProxy = MockClientProxy(userID: "@a:b.com") userSession = MockUserSession(clientProxy: clientProxy, mediaProvider: MockMediaProvider()) - let parameters = CreateRoomVolatileParameters() - parameters.selectedUsers = [.mockAlice, .mockBob, .mockCharlie] - let viewModel = CreateRoomViewModel(userSession: userSession, createRoomParameters: parameters) + let parameters = CreateRoomFlowParameters() + usersSubject.send([.mockAlice, .mockBob, .mockCharlie]) + let viewModel = CreateRoomViewModel(userSession: userSession, createRoomParameters: .init(parameters), selectedUsers: usersSubject.asCurrentValuePublisher()) 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() { diff --git a/UnitTests/Sources/InviteUsersViewModelTests.swift b/UnitTests/Sources/InviteUsersViewModelTests.swift index 8930a6a67..f50319a4a 100644 --- a/UnitTests/Sources/InviteUsersViewModelTests.swift +++ b/UnitTests/Sources/InviteUsersViewModelTests.swift @@ -14,6 +14,7 @@ // limitations under the License. // +import Combine import XCTest @testable import ElementX @@ -24,6 +25,9 @@ class InviteUsersScreenViewModelTests: XCTestCase { var clientProxy: MockClientProxy! var userDiscoveryService: UserDiscoveryServiceMock! + private let usersSubject = CurrentValueSubject<[UserProfile], Never>([]) + private var cancellables: Set = [] + var context: InviteUsersScreenViewModel.Context { viewModel.context } @@ -34,33 +38,51 @@ class InviteUsersScreenViewModelTests: XCTestCase { userDiscoveryService.fetchSuggestionsReturnValue = .success([]) userDiscoveryService.searchProfilesWithReturnValue = .success([]) let userSession = MockUserSession(clientProxy: clientProxy, mediaProvider: MockMediaProvider()) - let viewModel = InviteUsersScreenViewModel(userSession: userSession, userDiscoveryService: userDiscoveryService) + usersSubject.send([]) + let viewModel = InviteUsersScreenViewModel(selectedUsers: usersSubject.asCurrentValuePublisher(), userSession: userSession, userDiscoveryService: userDiscoveryService) viewModel.state.usersSection = .init(type: .suggestions, users: [.mockAlice, .mockBob, .mockCharlie]) self.viewModel = viewModel + + viewModel.actions.sink { [weak self] action in + guard let self else { return } + 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) } func testSelectUser() { XCTAssertTrue(context.viewState.selectedUsers.isEmpty) - context.send(viewAction: .tapUser(.mockAlice)) + context.send(viewAction: .toggleUser(.mockAlice)) XCTAssertTrue(context.viewState.selectedUsers.count == 1) XCTAssertEqual(context.viewState.selectedUsers.first?.userID, UserProfile.mockAlice.userID) } func testReselectUser() { XCTAssertTrue(context.viewState.selectedUsers.isEmpty) - context.send(viewAction: .tapUser(.mockAlice)) + context.send(viewAction: .toggleUser(.mockAlice)) XCTAssertEqual(context.viewState.selectedUsers.count, 1) XCTAssertEqual(context.viewState.selectedUsers.first?.userID, UserProfile.mockAlice.userID) - context.send(viewAction: .tapUser(.mockAlice)) + context.send(viewAction: .toggleUser(.mockAlice)) XCTAssertTrue(context.viewState.selectedUsers.isEmpty) } func testDeselectUser() { XCTAssertTrue(context.viewState.selectedUsers.isEmpty) - context.send(viewAction: .tapUser(.mockAlice)) + context.send(viewAction: .toggleUser(.mockAlice)) XCTAssertEqual(context.viewState.selectedUsers.count, 1) XCTAssertEqual(context.viewState.selectedUsers.first?.userID, UserProfile.mockAlice.userID) - context.send(viewAction: .deselectUser(.mockAlice)) + context.send(viewAction: .toggleUser(.mockAlice)) XCTAssertTrue(context.viewState.selectedUsers.isEmpty) } }