From 4eb8906b6bf8fb12f43221cdda10a954535fa1b1 Mon Sep 17 00:00:00 2001 From: Flavio Alescio Date: Fri, 21 Apr 2023 17:56:04 +0200 Subject: [PATCH] Create room: add selectable state for security, add navigation to screen --- .../CreateRoom/CreateRoomCoordinator.swift | 6 ++- .../Screens/CreateRoom/CreateRoomModels.swift | 1 + .../CreateRoom/CreateRoomViewModel.swift | 8 +-- .../CreateRoom/View/CreateRoomScreen.swift | 49 ++++++++++++------- .../InviteUsersScreenCoordinator.swift | 10 ++++ .../InviteUsersScreenModels.swift | 1 + .../InviteUsersScreenViewModel.swift | 2 +- .../StartChatScreenCoordinator.swift | 2 +- .../UITests/UITestsAppCoordinator.swift | 7 ++- 9 files changed, 57 insertions(+), 29 deletions(-) diff --git a/ElementX/Sources/Screens/CreateRoom/CreateRoomCoordinator.swift b/ElementX/Sources/Screens/CreateRoom/CreateRoomCoordinator.swift index b33e10742..bddbea6ca 100644 --- a/ElementX/Sources/Screens/CreateRoom/CreateRoomCoordinator.swift +++ b/ElementX/Sources/Screens/CreateRoom/CreateRoomCoordinator.swift @@ -18,6 +18,7 @@ import Combine import SwiftUI struct CreateRoomCoordinatorParameters { + let userSession: UserSessionProtocol let selectedUsers: [UserProfile] } @@ -37,14 +38,15 @@ final class CreateRoomCoordinator: CoordinatorProtocol { init(parameters: CreateRoomCoordinatorParameters) { self.parameters = parameters - - viewModel = CreateRoomViewModel(selectedUsers: parameters.selectedUsers) + // TODO: Check if any stored data are present and restore them + viewModel = CreateRoomViewModel(userSession: parameters.userSession, selectedUsers: parameters.selectedUsers) } func start() { viewModel.actions.sink { [weak self] action in guard let self else { return } switch action { + // TODO: Store data for previous screen case .createRoom: self.actionsSubject.send(.createRoom) } diff --git a/ElementX/Sources/Screens/CreateRoom/CreateRoomModels.swift b/ElementX/Sources/Screens/CreateRoom/CreateRoomModels.swift index 032e417aa..f3ad3d826 100644 --- a/ElementX/Sources/Screens/CreateRoom/CreateRoomModels.swift +++ b/ElementX/Sources/Screens/CreateRoom/CreateRoomModels.swift @@ -32,6 +32,7 @@ struct CreateRoomViewState: BindableState { struct CreateRoomViewStateBindings { var roomName = "" var roomTopic = "" + var isRoomPrivate = true } enum CreateRoomViewAction { diff --git a/ElementX/Sources/Screens/CreateRoom/CreateRoomViewModel.swift b/ElementX/Sources/Screens/CreateRoom/CreateRoomViewModel.swift index c30c0176d..4ef43ed1b 100644 --- a/ElementX/Sources/Screens/CreateRoom/CreateRoomViewModel.swift +++ b/ElementX/Sources/Screens/CreateRoom/CreateRoomViewModel.swift @@ -26,8 +26,8 @@ class CreateRoomViewModel: CreateRoomViewModelType, CreateRoomViewModelProtocol actionsSubject.eraseToAnyPublisher() } - init(selectedUsers: [UserProfile]) { - super.init(initialViewState: CreateRoomViewState(selectedUsers: selectedUsers)) + init(userSession: UserSessionProtocol, selectedUsers: [UserProfile]) { + super.init(initialViewState: CreateRoomViewState(selectedUsers: selectedUsers), imageProvider: userSession.mediaProvider) } // MARK: - Public @@ -39,9 +39,9 @@ class CreateRoomViewModel: CreateRoomViewModelType, CreateRoomViewModelProtocol case .deselectUser(let user): state.selectedUsers.removeAll(where: { $0.userID == user.userID }) case .selectPrivateRoom: - break + state.bindings.isRoomPrivate = true case .selectPublicRoom: - break + state.bindings.isRoomPrivate = false } } } diff --git a/ElementX/Sources/Screens/CreateRoom/View/CreateRoomScreen.swift b/ElementX/Sources/Screens/CreateRoom/View/CreateRoomScreen.swift index cb9c6e1ea..1cfc4c651 100644 --- a/ElementX/Sources/Screens/CreateRoom/View/CreateRoomScreen.swift +++ b/ElementX/Sources/Screens/CreateRoom/View/CreateRoomScreen.swift @@ -35,8 +35,9 @@ struct CreateRoomScreen: View { /// The main content of the view to be shown in a scroll view. var mainContent: some View { VStack(alignment: .center, spacing: 24) { + // TODO: check spacing + roomSection Form { - roomSection topicSection } selectedUsersSection @@ -48,30 +49,34 @@ struct CreateRoomScreen: View { } private var roomSection: some View { - Section { - TextField(L10n.screenCreateRoomRoomNameLabel, - text: $context.roomName, - prompt: Text(L10n.screenCreateRoomRoomNamePlaceholder).compoundFormTextFieldPlaceholder(), - axis: .horizontal) - .textFieldStyle(.compoundForm) - } header: { - Text(L10n.screenCreateRoomRoomNameLabel) - .compoundFormSectionFooter() + // TODO: Style better + HStack(alignment: .top, spacing: 0) { + Image(systemName: "camera") + .frame(width: 70, height: 70) + .clipShape(Circle()) + Form { + Section { + TextField(L10n.screenCreateRoomRoomNameLabel, + text: $context.roomName, + prompt: Text(L10n.screenCreateRoomRoomNamePlaceholder), + axis: .horizontal) + } header: { + Text(L10n.screenCreateRoomRoomNameLabel) + } + .formSectionStyle() + } } - .compoundFormSection() } private var topicSection: some View { Section { TextField(L10n.screenCreateRoomTopicLabel, text: $context.roomTopic, - prompt: Text(L10n.screenCreateRoomTopicPlaceholder).compoundFormTextFieldPlaceholder(), + prompt: Text(L10n.screenCreateRoomTopicPlaceholder), axis: .vertical) .lineLimit(3, reservesSpace: false) - .textFieldStyle(.compoundForm) } header: { Text(L10n.screenCreateRoomTopicLabel) - .compoundFormSectionFooter() } .formSectionStyle() } @@ -81,7 +86,7 @@ struct CreateRoomScreen: View { ScrollView(.horizontal, showsIndicators: false) { HStack(spacing: 28) { ForEach(context.viewState.selectedUsers, id: \.userID) { user in - InviteUsersSelectedItem(user: user, imageProvider: context.imageProvider) { + InviteUsersScreenSelectedItem(user: user, imageProvider: context.imageProvider) { deselect(user) } .frame(width: cellWidth) @@ -93,15 +98,17 @@ struct CreateRoomScreen: View { private var securitySection: some View { Section { + // TODO: style this items with descrition text and proper selection image Button(action: selectPrivate) { Label(L10n.screenCreateRoomPrivateOptionTitle, systemImage: "lock.shield") } - .buttonStyle(FormButtonStyle(accessory: .navigationLink)) - Button(action: selectPrivate) { + .buttonStyle(FormButtonStyle(accessory: .selection(isSelected: context.isRoomPrivate))) + Button(action: selectPublic) { Label(L10n.screenCreateRoomPublicOptionTitle, systemImage: "exclamationmark.shield") } - .buttonStyle(FormButtonStyle(accessory: .navigationLink)) + .buttonStyle(FormButtonStyle(accessory: .selection(isSelected: !context.isRoomPrivate))) } header: { + // TODO: localize Text("SECURITY") } .formSectionStyle() @@ -130,7 +137,11 @@ struct CreateRoomScreen: View { // MARK: - Previews struct CreateRoom_Previews: PreviewProvider { - static let viewModel = CreateRoomViewModel(selectedUsers: [.mockAlice, .mockBob, .mockCharlie]) + static let viewModel = { + let userSession = MockUserSession(clientProxy: MockClientProxy(userID: "@userid:example.com"), + mediaProvider: MockMediaProvider()) + return CreateRoomViewModel(userSession: userSession, selectedUsers: [.mockAlice, .mockBob, .mockCharlie]) + }() static var previews: some View { NavigationView { diff --git a/ElementX/Sources/Screens/InviteUsersScreen/InviteUsersScreenCoordinator.swift b/ElementX/Sources/Screens/InviteUsersScreen/InviteUsersScreenCoordinator.swift index f64ee59d7..6a8bc8fe1 100644 --- a/ElementX/Sources/Screens/InviteUsersScreen/InviteUsersScreenCoordinator.swift +++ b/ElementX/Sources/Screens/InviteUsersScreen/InviteUsersScreenCoordinator.swift @@ -18,6 +18,7 @@ import Combine import SwiftUI struct InviteUsersScreenCoordinatorParameters { + let navigationStackCoordinator: NavigationStackCoordinator? let userSession: UserSessionProtocol let userDiscoveryService: UserDiscoveryServiceProtocol } @@ -47,7 +48,10 @@ final class InviteUsersScreenCoordinator: CoordinatorProtocol { guard let self else { return } switch action { case .close: + self.actionsSubject.send(.close) + case .proceed(let users): + self.openCreateRoomScreenWith(users) } } .store(in: &cancellables) @@ -56,4 +60,10 @@ final class InviteUsersScreenCoordinator: CoordinatorProtocol { func toPresentable() -> AnyView { AnyView(InviteUsersScreen(context: viewModel.context)) } + + private func openCreateRoomScreenWith(_ users: [UserProfile]) { + let paramenters = CreateRoomCoordinatorParameters(userSession: parameters.userSession, selectedUsers: users) + let createRoomCoordinator = CreateRoomCoordinator(parameters: paramenters) + parameters.navigationStackCoordinator?.push(createRoomCoordinator) + } } diff --git a/ElementX/Sources/Screens/InviteUsersScreen/InviteUsersScreenModels.swift b/ElementX/Sources/Screens/InviteUsersScreen/InviteUsersScreenModels.swift index f0c13e6c6..5b44f1c1a 100644 --- a/ElementX/Sources/Screens/InviteUsersScreen/InviteUsersScreenModels.swift +++ b/ElementX/Sources/Screens/InviteUsersScreen/InviteUsersScreenModels.swift @@ -22,6 +22,7 @@ enum InviteUsersScreenErrorType: Error { enum InviteUsersScreenViewModelAction { case close + case proceed(users: [UserProfile]) } struct InviteUsersScreenViewState: BindableState { diff --git a/ElementX/Sources/Screens/InviteUsersScreen/InviteUsersScreenViewModel.swift b/ElementX/Sources/Screens/InviteUsersScreen/InviteUsersScreenViewModel.swift index 6d559bafd..36ccd801a 100644 --- a/ElementX/Sources/Screens/InviteUsersScreen/InviteUsersScreenViewModel.swift +++ b/ElementX/Sources/Screens/InviteUsersScreen/InviteUsersScreenViewModel.swift @@ -43,7 +43,7 @@ class InviteUsersScreenViewModel: InviteUsersScreenViewModelType, InviteUsersScr case .close: actionsSubject.send(.close) case .proceed: - break + actionsSubject.send(.proceed(users: state.selectedUsers)) case .tapUser(let user): if state.isUserSelected(user) { deselect(user) diff --git a/ElementX/Sources/Screens/StartChatScreen/StartChatScreenCoordinator.swift b/ElementX/Sources/Screens/StartChatScreen/StartChatScreenCoordinator.swift index df84999f4..daedda732 100644 --- a/ElementX/Sources/Screens/StartChatScreen/StartChatScreenCoordinator.swift +++ b/ElementX/Sources/Screens/StartChatScreen/StartChatScreenCoordinator.swift @@ -70,7 +70,7 @@ final class StartChatScreenCoordinator: CoordinatorProtocol { // MARK: - Private private func presentInviteUsersScreen() { - let inviteParameters = InviteUsersScreenCoordinatorParameters(userSession: parameters.userSession, userDiscoveryService: parameters.userDiscoveryService) + let inviteParameters = InviteUsersScreenCoordinatorParameters(navigationStackCoordinator: parameters.navigationStackCoordinator, userSession: parameters.userSession, userDiscoveryService: parameters.userDiscoveryService) let coordinator = InviteUsersScreenCoordinator(parameters: inviteParameters) coordinator.actions.sink { [weak self] result in switch result { diff --git a/ElementX/Sources/UITests/UITestsAppCoordinator.swift b/ElementX/Sources/UITests/UITestsAppCoordinator.swift index ccf11ee5c..fb2e02110 100644 --- a/ElementX/Sources/UITests/UITestsAppCoordinator.swift +++ b/ElementX/Sources/UITests/UITestsAppCoordinator.swift @@ -408,12 +408,15 @@ class MockScreen: Identifiable { let userDiscoveryMock = UserDiscoveryServiceMock() userDiscoveryMock.fetchSuggestionsReturnValue = .success([.mockAlice, .mockBob, .mockCharlie]) userDiscoveryMock.searchProfilesWithReturnValue = .success([]) - let coordinator = InviteUsersScreenCoordinator(parameters: .init(userSession: MockUserSession(clientProxy: MockClientProxy(userID: "@mock:client.com"), mediaProvider: MockMediaProvider()), userDiscoveryService: userDiscoveryMock)) + let userSession = MockUserSession(clientProxy: MockClientProxy(userID: "@mock:client.com"), mediaProvider: MockMediaProvider()) + let coordinator = InviteUsersScreenCoordinator(parameters: .init(navigationStackCoordinator: navigationStackCoordinator, userSession: userSession, userDiscoveryService: userDiscoveryMock)) navigationStackCoordinator.setRootCoordinator(coordinator) return navigationStackCoordinator case .createRoom: let navigationStackCoordinator = NavigationStackCoordinator() - let parameters = CreateRoomCoordinatorParameters(selectedUsers: [.mockAlice, .mockBob, .mockCharlie]) + let clientProxy = MockClientProxy(userID: "@mock:client.com") + let mockUserSession = MockUserSession(clientProxy: clientProxy, mediaProvider: MockMediaProvider()) + let parameters = CreateRoomCoordinatorParameters(userSession: mockUserSession, selectedUsers: [.mockAlice, .mockBob, .mockCharlie]) let coordinator = CreateRoomCoordinator(parameters: parameters) navigationStackCoordinator.setRootCoordinator(coordinator) return navigationStackCoordinator