mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 06:55:59 +00:00
Create space flow (#4957)
* create space flow implementation # Conflicts: # ElementX/Sources/FlowCoordinators/SpacesTabFlowCoordinator.swift * create space flow fully implemented and working * updated tests and updated the create room camera button UI * updated the avatar button in the create room screen, and added power level overrides for spaces * update power level content overrides to behave just as EW, and removed ask to join when creating a space regardless of the FF * updated UI tests snapshots * invite for a public space should always be forced to 50 * pr suggestions + PL override fix * fix a missed code change
This commit is contained in:
@@ -567,7 +567,7 @@ class ChatsTabFlowCoordinator: FlowCoordinatorProtocol {
|
||||
|
||||
private func startStartChatFlow(animated: Bool) {
|
||||
let navigationStackCoordinator = NavigationStackCoordinator()
|
||||
let coordinator = StartChatFlowCoordinator(isSpace: false,
|
||||
let coordinator = StartChatFlowCoordinator(entryPoint: .startChat,
|
||||
userDiscoveryService: UserDiscoveryService(clientProxy: userSession.clientProxy),
|
||||
navigationStackCoordinator: navigationStackCoordinator,
|
||||
flowParameters: flowParameters)
|
||||
@@ -576,11 +576,17 @@ class ChatsTabFlowCoordinator: FlowCoordinatorProtocol {
|
||||
.sink { [weak self] action in
|
||||
guard let self else { return }
|
||||
switch action {
|
||||
case .finished(let roomID):
|
||||
case .finished(let result):
|
||||
navigationSplitCoordinator.setSheetCoordinator(nil)
|
||||
|
||||
if let roomID {
|
||||
|
||||
switch result {
|
||||
case .room(let roomID):
|
||||
stateMachine.processEvent(.selectRoom(roomID: roomID, via: [], entryPoint: .room))
|
||||
case .space(let spaceRoomListProxy):
|
||||
// This also automatically handles selecting the space.
|
||||
stateMachine.processEvent(.selectRoom(roomID: spaceRoomListProxy.id, via: [], entryPoint: .room))
|
||||
case .cancelled:
|
||||
break
|
||||
}
|
||||
case .showRoomDirectory:
|
||||
navigationSplitCoordinator.setSheetCoordinator(nil)
|
||||
|
||||
@@ -25,12 +25,15 @@ class SpacesTabFlowCoordinator: FlowCoordinatorProtocol {
|
||||
private let detailNavigationStackCoordinator: NavigationStackCoordinator
|
||||
|
||||
private var spaceFlowCoordinator: SpaceFlowCoordinator?
|
||||
private var startChatFlowCoordinator: StartChatFlowCoordinator?
|
||||
|
||||
enum State: StateType {
|
||||
/// The state machine hasn't started.
|
||||
case initial
|
||||
/// The root screen for this flow.
|
||||
case spacesScreen(selectedSpaceID: String?)
|
||||
/// The create space flow is currently being presented
|
||||
case createSpaceFlow
|
||||
}
|
||||
|
||||
enum Event: EventType {
|
||||
@@ -42,6 +45,10 @@ class SpacesTabFlowCoordinator: FlowCoordinatorProtocol {
|
||||
case selectSpace
|
||||
/// The space screen has been dismissed.
|
||||
case deselectSpace
|
||||
/// Start the create a new space flow
|
||||
case startCreateSpaceFlow
|
||||
/// Create space has finished
|
||||
case dismissedCreateSpaceFlow
|
||||
}
|
||||
|
||||
private let stateMachine: StateMachine<State, Event>
|
||||
@@ -81,16 +88,20 @@ class SpacesTabFlowCoordinator: FlowCoordinatorProtocol {
|
||||
switch stateMachine.state {
|
||||
case .initial, .spacesScreen:
|
||||
break
|
||||
case .createSpaceFlow:
|
||||
startChatFlowCoordinator?.clearRoute(animated: animated)
|
||||
clearRoute(animated: animated)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
// swiftlint:disable:next cyclomatic_complexity
|
||||
private func configureStateMachine() {
|
||||
stateMachine.addRoutes(event: .start, transitions: [.initial => .spacesScreen(selectedSpaceID: nil)]) { [weak self] _ in
|
||||
self?.presentSpacesScreen()
|
||||
}
|
||||
|
||||
|
||||
stateMachine.addRouteMapping { event, fromState, userInfo in
|
||||
guard event == .selectSpace, case .spacesScreen = fromState else { return nil }
|
||||
guard let spaceRoomListProxy = userInfo as? SpaceRoomListProxyProtocol else { fatalError("A space proxy must be provided.") }
|
||||
@@ -110,6 +121,24 @@ class SpacesTabFlowCoordinator: FlowCoordinatorProtocol {
|
||||
spaceFlowCoordinator = nil
|
||||
}
|
||||
|
||||
stateMachine.addRouteMapping { event, fromState, _ in
|
||||
guard event == .startCreateSpaceFlow, case .spacesScreen = fromState else { return nil }
|
||||
return .createSpaceFlow
|
||||
} handler: { [weak self] _ in
|
||||
self?.startCreateSpaceFlow()
|
||||
}
|
||||
|
||||
stateMachine.addRouteMapping { event, fromState, userInfo in
|
||||
guard event == .dismissedCreateSpaceFlow, case .createSpaceFlow = fromState else { return nil }
|
||||
return .spacesScreen(selectedSpaceID: (userInfo as? SpaceRoomListProxyProtocol)?.id)
|
||||
} handler: { [weak self] context in
|
||||
guard let self else { return }
|
||||
startChatFlowCoordinator = nil
|
||||
if let spaceRoomListProxy = context.userInfo as? SpaceRoomListProxyProtocol {
|
||||
startSpaceFlow(spaceRoomListProxy: spaceRoomListProxy)
|
||||
}
|
||||
}
|
||||
|
||||
stateMachine.addErrorHandler { context in
|
||||
fatalError("Unexpected transition: \(context)")
|
||||
}
|
||||
@@ -129,6 +158,8 @@ class SpacesTabFlowCoordinator: FlowCoordinatorProtocol {
|
||||
stateMachine.tryEvent(.selectSpace, userInfo: spaceRoomListProxy)
|
||||
case .showSettings:
|
||||
actionsSubject.send(.showSettings)
|
||||
case .showCreateSpace:
|
||||
stateMachine.tryEvent(.startCreateSpaceFlow)
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
@@ -167,4 +198,38 @@ class SpacesTabFlowCoordinator: FlowCoordinatorProtocol {
|
||||
coordinator.start()
|
||||
selectedSpaceSubject.send(spaceRoomListProxy.id)
|
||||
}
|
||||
|
||||
private func startCreateSpaceFlow() {
|
||||
let coordinator = NavigationStackCoordinator()
|
||||
let flowCoordinator = StartChatFlowCoordinator(entryPoint: .createSpace,
|
||||
userDiscoveryService: UserDiscoveryService(clientProxy: flowParameters.userSession.clientProxy),
|
||||
navigationStackCoordinator: coordinator,
|
||||
flowParameters: flowParameters)
|
||||
|
||||
var spaceRoomListProxy: SpaceRoomListProxyProtocol?
|
||||
flowCoordinator.actionsPublisher
|
||||
.sink { [weak self] action in
|
||||
guard let self else { return }
|
||||
switch action {
|
||||
case .finished(let result):
|
||||
switch result {
|
||||
case .space(let value):
|
||||
spaceRoomListProxy = value
|
||||
case .room, .cancelled:
|
||||
break
|
||||
}
|
||||
navigationSplitCoordinator.setSheetCoordinator(nil)
|
||||
case .showRoomDirectory:
|
||||
fatalError("Not handled here")
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
navigationSplitCoordinator.setSheetCoordinator(coordinator) { [weak self] in
|
||||
self?.stateMachine.tryEvent(.dismissedCreateSpaceFlow, userInfo: spaceRoomListProxy)
|
||||
}
|
||||
|
||||
flowCoordinator.start(animated: true)
|
||||
startChatFlowCoordinator = flowCoordinator
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,12 +11,29 @@ import Foundation
|
||||
import SwiftState
|
||||
|
||||
enum StartChatFlowCoordinatorAction {
|
||||
case finished(roomID: String?)
|
||||
case finished(Result)
|
||||
case showRoomDirectory
|
||||
|
||||
enum Result {
|
||||
case room(id: String)
|
||||
case space(spaceRoomListProxy: SpaceRoomListProxyProtocol)
|
||||
case cancelled
|
||||
}
|
||||
}
|
||||
|
||||
/// A value that represents where the flow will be started.
|
||||
enum StartChatFlowCoordinatorEntryPoint {
|
||||
case startChat
|
||||
case createSpace
|
||||
}
|
||||
|
||||
class StartChatFlowCoordinator: FlowCoordinatorProtocol {
|
||||
private let isSpace: Bool
|
||||
struct CreatedRoomResult {
|
||||
let roomProxy: JoinedRoomProxyProtocol
|
||||
let spaceRoomListProxy: SpaceRoomListProxyProtocol?
|
||||
}
|
||||
|
||||
private let entryPoint: StartChatFlowCoordinatorEntryPoint
|
||||
private let userDiscoveryService: UserDiscoveryServiceProtocol
|
||||
private let navigationStackCoordinator: NavigationStackCoordinator
|
||||
|
||||
@@ -40,7 +57,7 @@ class StartChatFlowCoordinator: FlowCoordinatorProtocol {
|
||||
|
||||
enum Event: EventType {
|
||||
/// The flow is being started.
|
||||
case start
|
||||
case start(entryPoint: StartChatFlowCoordinatorEntryPoint)
|
||||
|
||||
/// The user would like to create a room.
|
||||
case createRoom
|
||||
@@ -64,11 +81,11 @@ class StartChatFlowCoordinator: FlowCoordinatorProtocol {
|
||||
actionsSubject.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
init(isSpace: Bool,
|
||||
init(entryPoint: StartChatFlowCoordinatorEntryPoint,
|
||||
userDiscoveryService: UserDiscoveryServiceProtocol,
|
||||
navigationStackCoordinator: NavigationStackCoordinator,
|
||||
flowParameters: CommonFlowParameters) {
|
||||
self.isSpace = isSpace
|
||||
self.entryPoint = entryPoint
|
||||
self.userDiscoveryService = userDiscoveryService
|
||||
self.navigationStackCoordinator = navigationStackCoordinator
|
||||
|
||||
@@ -79,7 +96,7 @@ class StartChatFlowCoordinator: FlowCoordinatorProtocol {
|
||||
}
|
||||
|
||||
func start(animated: Bool) {
|
||||
stateMachine.tryEvent(.start)
|
||||
stateMachine.tryEvent(.start(entryPoint: entryPoint))
|
||||
}
|
||||
|
||||
func handleAppRoute(_ appRoute: AppRoute, animated: Bool) {
|
||||
@@ -109,12 +126,16 @@ class StartChatFlowCoordinator: FlowCoordinatorProtocol {
|
||||
// MARK: - Private
|
||||
|
||||
private func configureStateMachine() {
|
||||
stateMachine.addRoutes(event: .start, transitions: [.initial => .startChat]) { [weak self] _ in
|
||||
stateMachine.addRoutes(event: .start(entryPoint: .startChat), transitions: [.initial => .startChat]) { [weak self] _ in
|
||||
self?.presentStartChatScreen()
|
||||
}
|
||||
|
||||
stateMachine.addRoutes(event: .start(entryPoint: .createSpace), transitions: [.initial => .createRoom]) { [weak self] _ in
|
||||
self?.presentCreateRoomScreen(isSpace: true, isRoot: true)
|
||||
}
|
||||
|
||||
stateMachine.addRoutes(event: .createRoom, transitions: [.startChat => .createRoom]) { [weak self] _ in
|
||||
self?.presentCreateRoomScreen()
|
||||
self?.presentCreateRoomScreen(isSpace: false, isRoot: false)
|
||||
}
|
||||
stateMachine.addRoutes(event: .dismissedCreateRoom, transitions: [.createRoom => .startChat]) { [weak self] _ in
|
||||
self?.createRoomScreenCoordinator = nil
|
||||
@@ -129,10 +150,10 @@ class StartChatFlowCoordinator: FlowCoordinatorProtocol {
|
||||
stateMachine.addRoutes(event: .dismissedRoomAvatarPicker, transitions: [.roomAvatarPicker => .createRoom])
|
||||
|
||||
stateMachine.addRoutes(event: .createdRoom, transitions: [.createRoom => .inviteUsers]) { [weak self] context in
|
||||
guard let roomProxy = context.userInfo as? JoinedRoomProxyProtocol else {
|
||||
guard let result = context.userInfo as? CreatedRoomResult else {
|
||||
fatalError("A room proxy is required to invite users.")
|
||||
}
|
||||
self?.presentInviteUsersScreen(roomProxy: roomProxy)
|
||||
self?.presentInviteUsersScreen(roomProxy: result.roomProxy, spaceRoomListProxy: result.spaceRoomListProxy)
|
||||
}
|
||||
|
||||
stateMachine.addErrorHandler { context in
|
||||
@@ -156,11 +177,11 @@ class StartChatFlowCoordinator: FlowCoordinatorProtocol {
|
||||
guard let self else { return }
|
||||
switch action {
|
||||
case .close:
|
||||
actionsSubject.send(.finished(roomID: nil))
|
||||
actionsSubject.send(.finished(.cancelled))
|
||||
case .createRoom:
|
||||
stateMachine.tryEvent(.createRoom)
|
||||
case .openRoom(let roomID):
|
||||
actionsSubject.send(.finished(roomID: roomID))
|
||||
actionsSubject.send(.finished(.room(id: roomID)))
|
||||
case .openRoomDirectorySearch:
|
||||
actionsSubject.send(.showRoomDirectory)
|
||||
}
|
||||
@@ -170,8 +191,9 @@ class StartChatFlowCoordinator: FlowCoordinatorProtocol {
|
||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||
}
|
||||
|
||||
private func presentCreateRoomScreen() {
|
||||
private func presentCreateRoomScreen(isSpace: Bool, isRoot: Bool) {
|
||||
let createParameters = CreateRoomScreenCoordinatorParameters(isSpace: isSpace,
|
||||
shouldShowCancelButton: isRoot,
|
||||
userSession: flowParameters.userSession,
|
||||
userIndicatorController: flowParameters.userIndicatorController,
|
||||
appSettings: flowParameters.appSettings,
|
||||
@@ -180,17 +202,25 @@ class StartChatFlowCoordinator: FlowCoordinatorProtocol {
|
||||
coordinator.actions.sink { [weak self] action in
|
||||
guard let self else { return }
|
||||
switch action {
|
||||
case .createdRoom(let roomProxy):
|
||||
stateMachine.tryEvent(.createdRoom, userInfo: roomProxy)
|
||||
case .createdRoom(let roomProxy, let spaceRoomListProxy):
|
||||
stateMachine.tryEvent(.createdRoom, userInfo: CreatedRoomResult(roomProxy: roomProxy, spaceRoomListProxy: spaceRoomListProxy))
|
||||
case .displayMediaPickerWithMode(let mode):
|
||||
stateMachine.tryEvent(.presentRoomAvatarPicker, userInfo: mode)
|
||||
case .dismiss:
|
||||
// Only used when isRoot
|
||||
actionsSubject.send(.finished(.cancelled))
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
createRoomScreenCoordinator = coordinator
|
||||
navigationStackCoordinator.push(coordinator) { [weak self] in
|
||||
self?.stateMachine.tryEvent(.dismissedCreateRoom)
|
||||
|
||||
if isRoot {
|
||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||
} else {
|
||||
navigationStackCoordinator.push(coordinator) { [weak self] in
|
||||
self?.stateMachine.tryEvent(.dismissedCreateRoom)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,7 +249,7 @@ class StartChatFlowCoordinator: FlowCoordinatorProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
private func presentInviteUsersScreen(roomProxy: JoinedRoomProxyProtocol) {
|
||||
private func presentInviteUsersScreen(roomProxy: JoinedRoomProxyProtocol, spaceRoomListProxy: SpaceRoomListProxyProtocol?) {
|
||||
let inviteParameters = InviteUsersScreenCoordinatorParameters(userSession: flowParameters.userSession,
|
||||
roomProxy: roomProxy,
|
||||
isSkippable: true,
|
||||
@@ -231,7 +261,11 @@ class StartChatFlowCoordinator: FlowCoordinatorProtocol {
|
||||
guard let self else { return }
|
||||
switch action {
|
||||
case .dismiss:
|
||||
actionsSubject.send(.finished(roomID: roomProxy.id))
|
||||
if let spaceRoomListProxy {
|
||||
actionsSubject.send(.finished(.space(spaceRoomListProxy: spaceRoomListProxy)))
|
||||
} else {
|
||||
actionsSubject.send(.finished(.room(id: roomProxy.id)))
|
||||
}
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
Reference in New Issue
Block a user