Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create a room screen (UI only) #877

Merged
merged 20 commits into from
May 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 56 additions & 8 deletions ElementX.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion ElementX/Sources/Application/AppCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -446,7 +446,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationCoordinatorDelegate,

// Allow for everything to deallocate properly
Task {
try await Task.sleep(for: .seconds(2))
try? await Task.sleep(for: .seconds(2))
userSessionStore.clearCache(for: userID)
stateMachine.processEvent(.startWithExistingSession)
hideLoadingIndicator()
Expand Down
6 changes: 6 additions & 0 deletions ElementX/Sources/Other/AccessibilityIdentifiers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ struct A11yIdentifiers {
static let softLogoutScreen = SoftLogoutScreen()
static let startChatScreen = StartChatScreen()
static let roomMemberDetailsScreen = RoomMemberDetailsScreen()
static let createRoomScreen = CreateRoomScreen()
static let invitesScreen = InvitesScreen()

struct AnalyticsPromptScreen {
Expand Down Expand Up @@ -130,4 +131,9 @@ struct A11yIdentifiers {
let inviteFriends = "start_chat-invite_friends"
let searchNoResults = "start_chat-search_no_results"
}

struct CreateRoomScreen {
let roomName = "create_room-room_name"
let roomTopic = "create_room-room_topic"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,15 @@ enum FormRowAccessory: View {
/// The primitive style is needed to set the list row insets to `0`. The inner style is then needed
/// to change the background colour depending on whether the button is currently pressed or not.
struct FormButtonStyle: PrimitiveButtonStyle {
var iconAlignment: VerticalAlignment = .firstTextBaseline

/// An accessory to be added on the trailing side of the row.
var accessory: FormRowAccessory?

func makeBody(configuration: Configuration) -> some View {
Button(action: configuration.trigger) {
configuration.label
.labelStyle(FormRowLabelStyle(role: configuration.role))
.labelStyle(FormRowLabelStyle(alignment: iconAlignment, role: configuration.role))
.frame(maxHeight: .infinity) // Make sure the label fills the cell vertically.
}
.buttonStyle(Style(accessory: accessory))
Expand Down
67 changes: 67 additions & 0 deletions ElementX/Sources/Screens/CreateRoom/CreateRoomCoordinator.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
//
// 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 userSession: UserSessionProtocol
let createRoomParameters: CurrentValuePublisher<CreateRoomFlowParameters, Never>
let selectedUsers: CurrentValuePublisher<[UserProfile], Never>
}

enum CreateRoomCoordinatorAction {
case createRoom
case deselectUser(UserProfile)
case updateDetails(CreateRoomFlowParameters)
}

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(userSession: parameters.userSession,
createRoomParameters: parameters.createRoomParameters,
selectedUsers: parameters.selectedUsers)
}

func start() {
viewModel.actions.sink { [weak self] action in
guard let self else { return }
switch action {
case .deselectUser(let user):
self.actionsSubject.send(.deselectUser(user))
case .createRoom:
self.actionsSubject.send(.createRoom)
case .updateDetails(let details):
self.actionsSubject.send(.updateDetails(details))
}
}
.store(in: &cancellables)
}

func toPresentable() -> AnyView {
AnyView(CreateRoomScreen(context: viewModel.context))
}
}
43 changes: 43 additions & 0 deletions ElementX/Sources/Screens/CreateRoom/CreateRoomModels.swift
Original file line number Diff line number Diff line change
@@ -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 Foundation

enum CreateRoomViewModelAction {
case createRoom
case deselectUser(UserProfile)
case updateDetails(CreateRoomFlowParameters)
}

struct CreateRoomViewState: BindableState {
var selectedUsers: [UserProfile]
var bindings: CreateRoomViewStateBindings

var canCreateRoom: Bool {
!bindings.roomName.isEmpty
}
}

struct CreateRoomViewStateBindings {
var roomName: String
var roomTopic: String
var isRoomPrivate: Bool
}

enum CreateRoomViewAction {
case createRoom
case deselectUser(UserProfile)
}
74 changes: 74 additions & 0 deletions ElementX/Sources/Screens/CreateRoom/CreateRoomViewModel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
//
// 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()
private var createRoomParameters: CreateRoomFlowParameters

var actions: AnyPublisher<CreateRoomViewModelAction, Never> {
flescio marked this conversation as resolved.
Show resolved Hide resolved
actionsSubject.eraseToAnyPublisher()
}

init(userSession: UserSessionProtocol,
createRoomParameters: CurrentValuePublisher<CreateRoomFlowParameters, Never>,
selectedUsers: CurrentValuePublisher<[UserProfile], Never>) {
let parameters = createRoomParameters.value
self.createRoomParameters = parameters
let bindings = CreateRoomViewStateBindings(roomName: parameters.name, roomTopic: parameters.topic, isRoomPrivate: parameters.isRoomPrivate)

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()
}

// MARK: - Public

override func process(viewAction: CreateRoomViewAction) {
switch viewAction {
case .createRoom:
actionsSubject.send(.createRoom)
case .deselectUser(let user):
actionsSubject.send(.deselectUser(user))
}
}

// MARK: - Private

private func setupBindings() {
context.$viewState
.map(\.bindings)
.throttle(for: 0.5, scheduler: DispatchQueue.main, latest: true)
.sink { [weak self] bindings in
guard let self else { return }
createRoomParameters.name = bindings.roomName
createRoomParameters.topic = bindings.roomTopic
createRoomParameters.isRoomPrivate = bindings.isRoomPrivate
actionsSubject.send(.updateDetails(createRoomParameters))
}
.store(in: &cancellables)
}
}
Original file line number Diff line number Diff line change
@@ -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 }
}
Loading