mirror of
https://github.com/element-hq/element-x-ios.git
synced 2025-03-10 21:39:12 +00:00

* New LICENSE-COMMERCIAL file * Apply dual licenses: AGPL + Element Commercial to file headers * Update README with dual licensing
203 lines
8.9 KiB
Swift
203 lines
8.9 KiB
Swift
//
|
|
// Copyright 2024 New Vector Ltd.
|
|
//
|
|
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
|
// Please see LICENSE files in the repository root for full details.
|
|
//
|
|
|
|
import Combine
|
|
import Foundation
|
|
import SwiftState
|
|
|
|
enum RoomRolesAndPermissionsFlowCoordinatorAction: Equatable {
|
|
/// The flow is complete.
|
|
case complete
|
|
}
|
|
|
|
struct RoomRolesAndPermissionsFlowCoordinatorParameters {
|
|
let roomProxy: JoinedRoomProxyProtocol
|
|
let mediaProvider: MediaProviderProtocol
|
|
let navigationStackCoordinator: NavigationStackCoordinator
|
|
let userIndicatorController: UserIndicatorControllerProtocol
|
|
let analytics: AnalyticsService
|
|
}
|
|
|
|
class RoomRolesAndPermissionsFlowCoordinator: FlowCoordinatorProtocol {
|
|
private let roomProxy: JoinedRoomProxyProtocol
|
|
private let navigationStackCoordinator: NavigationStackCoordinator
|
|
private let mediaProvider: MediaProviderProtocol
|
|
private let userIndicatorController: UserIndicatorControllerProtocol
|
|
private let analytics: AnalyticsService
|
|
|
|
enum State: StateType {
|
|
/// The state machine hasn't started.
|
|
case initial
|
|
/// The root screen for this flow.
|
|
case rolesAndPermissionsScreen
|
|
/// Changing member roles.
|
|
case changingRoles
|
|
/// Changing room permissions.
|
|
case changingPermissions
|
|
/// The flow is complete and the stack has been cleaned up.
|
|
case complete
|
|
}
|
|
|
|
enum Event: EventType {
|
|
/// The flow is being started.
|
|
case start
|
|
/// The user would like to change member roles.
|
|
case changeRoles
|
|
/// The user finished changing member roles.
|
|
case finishedChangingRoles
|
|
/// The user would like to change room permissions.
|
|
case changePermissions
|
|
/// The user finished changing room permissions.
|
|
case finishedChangingPermissions
|
|
/// The user has demoted themself.
|
|
case demotedOwnUser
|
|
}
|
|
|
|
private let stateMachine: StateMachine<State, Event>
|
|
private var cancellables: Set<AnyCancellable> = []
|
|
|
|
private let actionsSubject: PassthroughSubject<RoomRolesAndPermissionsFlowCoordinatorAction, Never> = .init()
|
|
var actionsPublisher: AnyPublisher<RoomRolesAndPermissionsFlowCoordinatorAction, Never> {
|
|
actionsSubject.eraseToAnyPublisher()
|
|
}
|
|
|
|
init(parameters: RoomRolesAndPermissionsFlowCoordinatorParameters) {
|
|
roomProxy = parameters.roomProxy
|
|
navigationStackCoordinator = parameters.navigationStackCoordinator
|
|
mediaProvider = parameters.mediaProvider
|
|
userIndicatorController = parameters.userIndicatorController
|
|
analytics = parameters.analytics
|
|
|
|
stateMachine = .init(state: .initial)
|
|
configureStateMachine()
|
|
}
|
|
|
|
func start() {
|
|
stateMachine.tryEvent(.start)
|
|
}
|
|
|
|
func handleAppRoute(_ appRoute: AppRoute, animated: Bool) {
|
|
// There aren't any routes to this screen, so always clear the stack.
|
|
clearRoute(animated: animated)
|
|
}
|
|
|
|
func clearRoute(animated: Bool) {
|
|
// As we push screens on top of an existing stack, popping to root wouldn't be safe.
|
|
switch stateMachine.state {
|
|
case .initial, .complete:
|
|
break
|
|
case .rolesAndPermissionsScreen:
|
|
navigationStackCoordinator.pop(animated: animated)
|
|
case .changingRoles, .changingPermissions:
|
|
navigationStackCoordinator.pop(animated: animated) // ChangeRoles or ChangePermissions screen.
|
|
navigationStackCoordinator.pop(animated: animated) // RolesAndPermissions screen.
|
|
}
|
|
}
|
|
|
|
// MARK: - Private
|
|
|
|
private func configureStateMachine() {
|
|
stateMachine.addRoutes(event: .start, transitions: [.initial => .rolesAndPermissionsScreen]) { [weak self] _ in
|
|
self?.presentRolesAndPermissionsScreen()
|
|
}
|
|
|
|
stateMachine.addRoutes(event: .changeRoles, transitions: [.rolesAndPermissionsScreen => .changingRoles]) { [weak self] context in
|
|
guard let role = context.userInfo as? RoomRolesAndPermissionsScreenRole else { fatalError("Expected a role") }
|
|
self?.presentChangeRolesScreen(role: role)
|
|
}
|
|
stateMachine.addRoutes(event: .finishedChangingRoles, transitions: [.changingRoles => .rolesAndPermissionsScreen])
|
|
|
|
stateMachine.addRoutes(event: .changePermissions, transitions: [.rolesAndPermissionsScreen => .changingPermissions]) { [weak self] context in
|
|
guard let (permissions, group) = context.userInfo as? (RoomPermissions, RoomRolesAndPermissionsScreenPermissionsGroup) else {
|
|
fatalError("Expected a group and the current permissions")
|
|
}
|
|
self?.presentChangePermissionsScreen(permissions: permissions, group: group)
|
|
}
|
|
stateMachine.addRoutes(event: .finishedChangingPermissions, transitions: [.changingPermissions => .rolesAndPermissionsScreen])
|
|
|
|
stateMachine.addRoutes(event: .demotedOwnUser, transitions: [.rolesAndPermissionsScreen => .complete]) { [weak self] _ in
|
|
self?.navigationStackCoordinator.pop()
|
|
}
|
|
|
|
stateMachine.addErrorHandler { context in
|
|
fatalError("Unexpected transition: \(context)")
|
|
}
|
|
}
|
|
|
|
private func presentRolesAndPermissionsScreen() {
|
|
let parameters = RoomRolesAndPermissionsScreenCoordinatorParameters(roomProxy: roomProxy,
|
|
userIndicatorController: userIndicatorController,
|
|
analytics: analytics)
|
|
let coordinator = RoomRolesAndPermissionsScreenCoordinator(parameters: parameters)
|
|
coordinator.actionsPublisher.sink { [stateMachine] action in
|
|
switch action {
|
|
case .editRoles(let role):
|
|
stateMachine.tryEvent(.changeRoles, userInfo: role)
|
|
case .editPermissions(let permissions, let group):
|
|
stateMachine.tryEvent(.changePermissions, userInfo: (permissions, group))
|
|
case .demotedOwnUser:
|
|
stateMachine.tryEvent(.demotedOwnUser)
|
|
}
|
|
}
|
|
.store(in: &cancellables)
|
|
|
|
navigationStackCoordinator.push(coordinator) { [weak self] in
|
|
self?.actionsSubject.send(.complete)
|
|
}
|
|
}
|
|
|
|
private func presentChangeRolesScreen(role: RoomRolesAndPermissionsScreenRole) {
|
|
let mode = switch role {
|
|
case .administrators: RoomMemberDetails.Role.administrator
|
|
case .moderators: RoomMemberDetails.Role.moderator
|
|
}
|
|
|
|
let parameters = RoomChangeRolesScreenCoordinatorParameters(mode: mode,
|
|
roomProxy: roomProxy,
|
|
mediaProvider: mediaProvider,
|
|
userIndicatorController: userIndicatorController,
|
|
analytics: analytics)
|
|
let coordinator = RoomChangeRolesScreenCoordinator(parameters: parameters)
|
|
coordinator.actionsPublisher.sink { [weak self] action in
|
|
guard let self else { return }
|
|
switch action {
|
|
case .complete:
|
|
// When discarding changes is finalised, either use an event or remove this action.
|
|
navigationStackCoordinator.pop()
|
|
}
|
|
}
|
|
.store(in: &cancellables)
|
|
|
|
navigationStackCoordinator.push(coordinator) { [stateMachine] in
|
|
stateMachine.tryEvent(.finishedChangingRoles)
|
|
}
|
|
}
|
|
|
|
private func presentChangePermissionsScreen(permissions: RoomPermissions, group: RoomRolesAndPermissionsScreenPermissionsGroup) {
|
|
let parameters = RoomChangePermissionsScreenCoordinatorParameters(permissions: permissions,
|
|
permissionsGroup: group,
|
|
roomProxy: roomProxy,
|
|
userIndicatorController: userIndicatorController,
|
|
analytics: analytics)
|
|
let coordinator = RoomChangePermissionsScreenCoordinator(parameters: parameters)
|
|
coordinator.actionsPublisher.sink { [weak self] action in
|
|
guard let self else { return }
|
|
|
|
switch action {
|
|
case .complete:
|
|
// When discarding changes is finalised, either use an event or remove this action.
|
|
navigationStackCoordinator.pop()
|
|
}
|
|
}
|
|
.store(in: &cancellables)
|
|
|
|
navigationStackCoordinator.push(coordinator) { [stateMachine] in
|
|
stateMachine.tryEvent(.finishedChangingPermissions)
|
|
}
|
|
}
|
|
}
|