add files for create room screen, add selected users section with test

This commit is contained in:
Flavio Alescio 2023-04-20 16:00:05 +02:00 committed by Flescio
parent f4bbe33e62
commit 06147cd99c
9 changed files with 305 additions and 0 deletions

View File

@ -0,0 +1,58 @@
//
// 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
struct CreateRoomCoordinatorParameters {
let selectedUsers: [UserProfile]
}
enum CreateRoomCoordinatorAction {
case createRoom
}
final class CreateRoomCoordinator: CoordinatorProtocol {
private let parameters: CreateRoomCoordinatorParameters
private var viewModel: CreateRoomViewModelProtocol
private let actionsSubject: PassthroughSubject<CreateRoomCoordinatorAction, Never> = .init()
private var cancellables: Set<AnyCancellable> = .init()
var actions: AnyPublisher<CreateRoomCoordinatorAction, Never> {
actionsSubject.eraseToAnyPublisher()
}
init(parameters: CreateRoomCoordinatorParameters) {
self.parameters = parameters
viewModel = CreateRoomViewModel(selectedUsers: parameters.selectedUsers)
}
func start() {
viewModel.actions.sink { [weak self] action in
guard let self else { return }
switch action {
case .createRoom:
self.actionsSubject.send(.createRoom)
}
}
.store(in: &cancellables)
}
func toPresentable() -> AnyView {
AnyView(CreateRoomScreen(context: viewModel.context))
}
}

View File

@ -0,0 +1,30 @@
//
// 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 Foundation
enum CreateRoomViewModelAction {
case createRoom
}
struct CreateRoomViewState: BindableState {
var selectedUsers: [UserProfile]
}
enum CreateRoomViewAction {
case createRoom
case deselectUser(UserProfile)
}

View File

@ -0,0 +1,43 @@
//
// 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 CreateRoomViewModelType = StateStoreViewModel<CreateRoomViewState, CreateRoomViewAction>
class CreateRoomViewModel: CreateRoomViewModelType, CreateRoomViewModelProtocol {
private var actionsSubject: PassthroughSubject<CreateRoomViewModelAction, Never> = .init()
var actions: AnyPublisher<CreateRoomViewModelAction, Never> {
actionsSubject.eraseToAnyPublisher()
}
init(selectedUsers: [UserProfile]) {
super.init(initialViewState: CreateRoomViewState(selectedUsers: selectedUsers))
}
// MARK: - Public
override func process(viewAction: CreateRoomViewAction) {
switch viewAction {
case .createRoom:
actionsSubject.send(.createRoom)
case .deselectUser(let user):
state.selectedUsers.removeAll(where: { $0.userID == user.userID })
}
}
}

View File

@ -0,0 +1,23 @@
//
// 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
@MainActor
protocol CreateRoomViewModelProtocol {
var actions: AnyPublisher<CreateRoomViewModelAction, Never> { get }
var context: CreateRoomViewModelType.Context { get }
}

View File

@ -0,0 +1,76 @@
//
// 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 SwiftUI
struct CreateRoomScreen: View {
@ObservedObject var context: CreateRoomViewModel.Context
var body: some View {
ScrollView {
mainContent
}
.scrollContentBackground(.hidden)
.background(Color.element.formBackground.ignoresSafeArea())
.navigationTitle(L10n.actionCreateARoom)
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .confirmationAction) {
createButton
}
}
}
/// The main content of the view to be shown in a scroll view.
var mainContent: some View {
selectedUsersSection
}
@ScaledMetric private var cellWidth: CGFloat = 64
private var selectedUsersSection: some View {
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 28) {
ForEach(context.viewState.selectedUsers, id: \.userID) { user in
InviteUsersSelectedItem(user: user, imageProvider: context.imageProvider) {
deselect(user)
}
.frame(width: cellWidth)
}
}
.padding(.horizontal, 18)
}
}
private var createButton: some View {
Button { context.send(viewAction: .createRoom) } label: {
Text(L10n.actionCreate)
}
}
private func deselect(_ user: UserProfile) {
context.send(viewAction: .deselectUser(user))
}
}
// MARK: - Previews
struct CreateRoom_Previews: PreviewProvider {
static let viewModel = CreateRoomViewModel(selectedUsers: [.mockAlice, .mockBob, .mockCharlie])
static var previews: some View {
CreateRoomScreen(context: viewModel.context)
}
}

View File

@ -411,6 +411,12 @@ class MockScreen: Identifiable {
let coordinator = InviteUsersScreenCoordinator(parameters: .init(userSession: MockUserSession(clientProxy: MockClientProxy(userID: "@mock:client.com"), mediaProvider: MockMediaProvider()), userDiscoveryService: userDiscoveryMock))
navigationStackCoordinator.setRootCoordinator(coordinator)
return navigationStackCoordinator
case .createRoom:
let navigationStackCoordinator = NavigationStackCoordinator()
let parameters = CreateRoomCoordinatorParameters(selectedUsers: [.mockAlice, .mockBob, .mockCharlie])
let coordinator = CreateRoomCoordinator(parameters: parameters)
navigationStackCoordinator.setRootCoordinator(coordinator)
return navigationStackCoordinator
}
}()
}

View File

@ -56,6 +56,7 @@ enum UITestsScreenIdentifier: String {
case invitesWithBadges
case invitesNoInvites
case inviteUsers
case createRoom
}
extension UITestsScreenIdentifier: CustomStringConvertible {

View File

@ -0,0 +1,25 @@
//
// 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 ElementX
import XCTest
class CreateRoomScreenUITests: XCTestCase {
func testLanding() {
let app = Application.launch(.createRoom)
app.assertScreenshot(.createRoom)
}
}

View File

@ -0,0 +1,43 @@
//
// 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 XCTest
@testable import ElementX
@MainActor
class CreateRoomScreenViewModelTests: XCTestCase {
var viewModel: CreateRoomViewModelProtocol!
var clientProxy: MockClientProxy!
var context: CreateRoomViewModel.Context {
viewModel.context
}
override func setUpWithError() throws {
clientProxy = .init(userID: "")
let viewModel = CreateRoomViewModel(selectedUsers: [.mockAlice, .mockBob, .mockCharlie])
self.viewModel = viewModel
}
func testDeselectUser() {
XCTAssertFalse(context.viewState.selectedUsers.isEmpty)
XCTAssertEqual(context.viewState.selectedUsers.count, 3)
XCTAssertEqual(context.viewState.selectedUsers.first?.userID, UserProfile.mockAlice.userID)
context.send(viewAction: .deselectUser(.mockAlice))
XCTAssertNotEqual(context.viewState.selectedUsers.first?.userID, UserProfile.mockAlice.userID)
}
}