Move the Settings flow from the Chats flow up one level to the UserSession flow. (#4390)

* Add the basis for a state machine to UserSessionFlowCoordinator.

* Move the Settings flow from the Chats flow to UserSession flow.

It now works from the Spaces tab.

* Add a StateMachineFactory and use it to publish the state in the tests.
This commit is contained in:
Doug
2025-08-07 13:13:02 +01:00
committed by GitHub
parent c47ec8a7cc
commit c5cd3ea79e
10 changed files with 388 additions and 164 deletions
@@ -641,6 +641,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
}
let userSessionFlowCoordinator = UserSessionFlowCoordinator(userSession: userSession,
isNewLogin: isNewLogin,
navigationRootCoordinator: navigationRootCoordinator,
appLockService: appLockFlowCoordinator.appLockService,
bugReportService: ServiceLocator.shared.bugReportService,
@@ -651,7 +652,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
appHooks: appHooks,
analytics: ServiceLocator.shared.analytics,
notificationManager: notificationManager,
isNewLogin: isNewLogin)
stateMachineFactory: StateMachineFactory())
userSessionFlowCoordinator.actionsPublisher
.sink { [weak self] action in
@@ -12,11 +12,10 @@ import MatrixRustSDK
import SwiftUI
enum ChatsFlowCoordinatorAction {
case logout
case showSettings
case showChatBackupSettings
case sessionVerification(SessionVerificationScreenFlow)
case clearCache
/// Logout and disable App Lock without any confirmation. The user forgot their PIN.
case forceLogout
case logout
}
class ChatsFlowCoordinator: FlowCoordinatorProtocol {
@@ -36,8 +35,6 @@ class ChatsFlowCoordinator: FlowCoordinatorProtocol {
private var roomFlowCoordinator: RoomFlowCoordinator?
private let timelineControllerFactory: TimelineControllerFactoryProtocol
private let settingsFlowCoordinator: SettingsFlowCoordinator
// periphery:ignore - retaining purpose
private var bugReportFlowCoordinator: BugReportFlowCoordinator?
@@ -59,10 +56,8 @@ class ChatsFlowCoordinator: FlowCoordinatorProtocol {
actionsSubject.eraseToAnyPublisher()
}
/// For testing purposes.
var statePublisher: AnyPublisher<ChatsFlowCoordinatorStateMachine.State, Never> { stateMachine.statePublisher }
init(userSession: UserSessionProtocol,
isNewLogin: Bool,
navigationSplitCoordinator: NavigationSplitCoordinator,
appLockService: AppLockServiceProtocol,
bugReportService: BugReportServiceProtocol,
@@ -73,8 +68,8 @@ class ChatsFlowCoordinator: FlowCoordinatorProtocol {
appHooks: AppHooks,
analytics: AnalyticsService,
notificationManager: NotificationManagerProtocol,
isNewLogin: Bool) {
stateMachine = ChatsFlowCoordinatorStateMachine()
stateMachineFactory: StateMachineFactoryProtocol) {
stateMachine = stateMachineFactory.makeChatsFlowStateMachine()
self.userSession = userSession
self.navigationSplitCoordinator = navigationSplitCoordinator
self.bugReportService = bugReportService
@@ -88,22 +83,9 @@ class ChatsFlowCoordinator: FlowCoordinatorProtocol {
sidebarNavigationStackCoordinator = NavigationStackCoordinator(navigationSplitCoordinator: navigationSplitCoordinator)
detailNavigationStackCoordinator = NavigationStackCoordinator(navigationSplitCoordinator: navigationSplitCoordinator)
navigationSplitCoordinator.setSidebarCoordinator(sidebarNavigationStackCoordinator)
settingsFlowCoordinator = SettingsFlowCoordinator(parameters: .init(userSession: userSession,
windowManager: appMediator.windowManager,
appLockService: appLockService,
bugReportService: bugReportService,
notificationSettings: userSession.clientProxy.notificationSettings,
secureBackupController: userSession.clientProxy.secureBackupController,
appSettings: appSettings,
navigationSplitCoordinator: navigationSplitCoordinator,
userIndicatorController: ServiceLocator.shared.userIndicatorController,
analytics: analytics))
setupStateMachine()
setupObservers()
}
@@ -189,8 +171,6 @@ class ChatsFlowCoordinator: FlowCoordinatorProtocol {
Task { await presentCallScreen(roomID: roomID) }
case .genericCallLink(let url):
presentCallScreen(genericCallLink: url)
case .settings, .chatBackupSettings:
settingsFlowCoordinator.handleAppRoute(appRoute, animated: animated)
case .share(let payload):
if let roomID = payload.roomID {
stateMachine.processEvent(.selectRoom(roomID: roomID,
@@ -200,14 +180,14 @@ class ChatsFlowCoordinator: FlowCoordinatorProtocol {
} else {
stateMachine.processEvent(.showShareExtensionRoomList(sharePayload: payload), userInfo: .init(animated: animated))
}
case .accountProvisioningLink:
break // We always ignore this flow when logged in.
case .transferOwnership(let roomID):
if stateMachine.state.roomListSelectedRoomID == roomID {
roomFlowCoordinator?.handleAppRoute(appRoute, animated: animated)
} else {
stateMachine.processEvent(.selectRoom(roomID: roomID, via: [], entryPoint: .transferOwnership))
}
case .accountProvisioningLink, .settings, .chatBackupSettings:
break // These routes cannot be handled.
}
}
@@ -247,11 +227,6 @@ class ChatsFlowCoordinator: FlowCoordinatorProtocol {
hideCallScreenOverlay() // Turn any active call into a PiP so that navigation from a notification is visible to the user.
case(.roomList, .deselectRoom, .roomList):
dismissRoomFlow(animated: animated)
case (.roomList, .showSettingsScreen, .settingsScreen):
break
case (.settingsScreen, .dismissedSettingsScreen, .roomList):
break
case (.roomList, .feedbackScreen, .feedbackScreen):
bugReportFlowCoordinator = BugReportFlowCoordinator(parameters: .init(presentationMode: .sheet(sidebarNavigationStackCoordinator),
@@ -333,24 +308,6 @@ class ChatsFlowCoordinator: FlowCoordinatorProtocol {
}
private func setupObservers() {
settingsFlowCoordinator.actions.sink { [weak self] action in
guard let self else { return }
switch action {
case .presentedSettings:
stateMachine.processEvent(.showSettingsScreen)
case .dismissedSettings:
stateMachine.processEvent(.dismissedSettingsScreen)
case .runLogoutFlow:
actionsSubject.send(.logout)
case .clearCache:
actionsSubject.send(.clearCache)
case .forceLogout:
actionsSubject.send(.forceLogout)
}
}
.store(in: &cancellables)
userSession.clientProxy.actionsPublisher
.receive(on: DispatchQueue.main)
.sink { [weak self] action in
@@ -439,11 +396,11 @@ class ChatsFlowCoordinator: FlowCoordinatorProtocol {
clearRoute(animated: true)
}
case .presentSettingsScreen:
settingsFlowCoordinator.handleAppRoute(.settings, animated: true)
actionsSubject.send(.showSettings)
case .presentFeedbackScreen:
stateMachine.processEvent(.feedbackScreen)
case .presentSecureBackupSettings:
settingsFlowCoordinator.handleAppRoute(.chatBackupSettings, animated: true)
actionsSubject.send(.showChatBackupSettings)
case .presentRecoveryKeyScreen:
stateMachine.processEvent(.showRecoveryKeyScreen)
case .presentEncryptionResetScreen:
@@ -21,9 +21,6 @@ class ChatsFlowCoordinatorStateMachine {
/// Showing the feedback screen.
case feedbackScreen(roomListSelectedRoomID: String?)
/// Showing the settings screen
case settingsScreen(roomListSelectedRoomID: String?)
/// Showing the recovery key screen.
case recoveryKeyScreen(roomListSelectedRoomID: String?)
@@ -56,7 +53,6 @@ class ChatsFlowCoordinatorStateMachine {
nil
case .roomList(let roomListSelectedRoomID),
.feedbackScreen(let roomListSelectedRoomID),
.settingsScreen(let roomListSelectedRoomID),
.recoveryKeyScreen(let roomListSelectedRoomID),
.encryptionResetFlow(let roomListSelectedRoomID),
.startChatScreen(let roomListSelectedRoomID),
@@ -86,11 +82,6 @@ class ChatsFlowCoordinatorStateMachine {
/// The room screen has been dismissed
case deselectRoom
/// Request presentation of the settings screen
case showSettingsScreen
/// The settings screen has been dismissed
case dismissedSettingsScreen
/// Request presentation of the feedback screen
case feedbackScreen
/// The feedback screen has been dismissed
@@ -137,11 +128,6 @@ class ChatsFlowCoordinatorStateMachine {
stateMachine.state
}
var stateSubject = PassthroughSubject<State, Never>()
var statePublisher: AnyPublisher<State, Never> {
stateSubject.eraseToAnyPublisher()
}
init() {
stateMachine = StateMachine(state: .initial)
configure()
@@ -156,11 +142,6 @@ class ChatsFlowCoordinatorStateMachine {
return .roomList(roomListSelectedRoomID: roomID)
case (.roomList, .deselectRoom):
return .roomList(roomListSelectedRoomID: nil)
case (.roomList(let roomListSelectedRoomID), .showSettingsScreen):
return .settingsScreen(roomListSelectedRoomID: roomListSelectedRoomID)
case (.settingsScreen(let roomListSelectedRoomID), .dismissedSettingsScreen):
return .roomList(roomListSelectedRoomID: roomListSelectedRoomID)
case (.roomList(let roomListSelectedRoomID), .feedbackScreen):
return .feedbackScreen(roomListSelectedRoomID: roomListSelectedRoomID)
@@ -219,10 +200,6 @@ class ChatsFlowCoordinatorStateMachine {
MXLog.info("Transitioning from \(context.fromState)` to `\(context.toState)`")
}
}
addTransitionHandler { [weak self] context in
self?.stateSubject.send(context.toState)
}
}
/// Attempt to move the state machine to another state through an event
@@ -9,10 +9,9 @@ import Combine
import SwiftUI
enum SettingsFlowCoordinatorAction {
case presentedSettings
case dismissedSettings
case runLogoutFlow
case dismiss
case clearCache
case runLogoutFlow
/// Logout without a confirmation. The user forgot their PIN.
case forceLogout
}
@@ -25,17 +24,14 @@ struct SettingsFlowCoordinatorParameters {
let notificationSettings: NotificationSettingsProxyProtocol
let secureBackupController: SecureBackupControllerProtocol
let appSettings: AppSettings
let navigationSplitCoordinator: NavigationSplitCoordinator
let navigationStackCoordinator: NavigationStackCoordinator
let userIndicatorController: UserIndicatorControllerProtocol
let analytics: AnalyticsService
}
class SettingsFlowCoordinator: FlowCoordinatorProtocol {
private let parameters: SettingsFlowCoordinatorParameters
private var navigationStackCoordinator: NavigationStackCoordinator!
private var cancellables = Set<AnyCancellable>()
private var navigationStackCoordinator: NavigationStackCoordinator { parameters.navigationStackCoordinator }
// periphery:ignore - retaining purpose
private var appLockSetupFlowCoordinator: AppLockSetupFlowCoordinator?
@@ -44,6 +40,8 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol {
// periphery:ignore - retaining purpose
private var encryptionSettingsFlowCoordinator: EncryptionSettingsFlowCoordinator?
private var cancellables = Set<AnyCancellable>()
private let actionsSubject: PassthroughSubject<SettingsFlowCoordinatorAction, Never> = .init()
var actions: AnyPublisher<SettingsFlowCoordinatorAction, Never> {
actionsSubject.eraseToAnyPublisher()
@@ -62,15 +60,7 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol {
case .settings:
presentSettingsScreen(animated: animated)
case .chatBackupSettings:
if navigationStackCoordinator == nil {
presentSettingsScreen(animated: animated)
}
// The navigation stack doesn't like it if the root and the push happen
// on the same loop run
DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) {
self.startEncryptionSettingsFlow(animated: animated)
}
startEncryptionSettingsFlow(animated: animated)
default:
break
}
@@ -83,8 +73,6 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol {
// MARK: - Private
private func presentSettingsScreen(animated: Bool) {
navigationStackCoordinator = NavigationStackCoordinator()
let settingsScreenCoordinator = SettingsScreenCoordinator(parameters: .init(userSession: parameters.userSession,
appSettings: parameters.appSettings,
isBugReportServiceEnabled: parameters.bugReportService.isEnabled))
@@ -95,14 +83,9 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol {
switch action {
case .dismiss:
parameters.navigationSplitCoordinator.setSheetCoordinator(nil)
actionsSubject.send(.dismiss)
case .logout:
parameters.navigationSplitCoordinator.setSheetCoordinator(nil)
// The settings sheet needs to be dismissed before the alert can be shown
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self.actionsSubject.send(.runLogoutFlow)
}
actionsSubject.send(.runLogoutFlow)
case .secureBackup:
startEncryptionSettingsFlow(animated: true)
case .userDetails:
@@ -136,15 +119,6 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol {
.store(in: &cancellables)
navigationStackCoordinator.setRootCoordinator(settingsScreenCoordinator, animated: animated)
parameters.navigationSplitCoordinator.setSheetCoordinator(navigationStackCoordinator) { [weak self] in
guard let self else { return }
navigationStackCoordinator = nil
actionsSubject.send(.dismissedSettings)
}
actionsSubject.send(.presentedSettings)
}
private func startEncryptionSettingsFlow(animated: Bool) {
@@ -174,13 +148,13 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol {
userIndicatorController: parameters.userIndicatorController,
appSettings: parameters.appSettings))
navigationStackCoordinator?.push(coordinator)
navigationStackCoordinator.push(coordinator)
}
private func presentAnalyticsScreen() {
let coordinator = AnalyticsSettingsScreenCoordinator(parameters: .init(appSettings: parameters.appSettings,
analytics: parameters.analytics))
navigationStackCoordinator?.push(coordinator)
navigationStackCoordinator.push(coordinator)
}
private func presentAppLockSetupFlow() {
@@ -8,6 +8,7 @@
import Combine
import Compound
import MatrixRustSDK
import SwiftState
import SwiftUI
enum UserSessionFlowCoordinatorAction {
@@ -23,8 +24,11 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
private let userSession: UserSessionProtocol
private let navigationRootCoordinator: NavigationRootCoordinator
private let navigationTabCoordinator: NavigationTabCoordinator<HomeTab>
private let appLockService: AppLockServiceProtocol
private let bugReportService: BugReportServiceProtocol
private let appMediator: AppMediatorProtocol
private let appSettings: AppSettings
private let analytics: AnalyticsService
private let onboardingFlowCoordinator: OnboardingFlowCoordinator
private let onboardingStackCoordinator: NavigationStackCoordinator
@@ -33,14 +37,38 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
private let spaceExplorerFlowCoordinator: SpaceExplorerFlowCoordinator
private let spacesTabDetails: NavigationTabCoordinator<HomeTab>.TabDetails
// periphery:ignore - retaining purpose
private var settingsFlowCoordinator: SettingsFlowCoordinator?
enum State: StateType {
/// The state machine hasn't started.
case initial
/// The root screen for this flow.
case tabBar
/// Showing the settings screen.
case settingsScreen
}
enum Event: EventType {
/// The flow is being started.
case start
/// Request presentation of the settings screen.
case showSettingsScreen
/// The settings screen has been dismissed.
case dismissedSettingsScreen
}
private let stateMachine: StateMachine<State, Event>
private var cancellables: Set<AnyCancellable> = []
private let actionsSubject: PassthroughSubject<UserSessionFlowCoordinatorAction, Never> = .init()
var actionsPublisher: AnyPublisher<UserSessionFlowCoordinatorAction, Never> {
actionsSubject.eraseToAnyPublisher()
}
private var cancellables: Set<AnyCancellable> = []
init(userSession: UserSessionProtocol,
isNewLogin: Bool,
navigationRootCoordinator: NavigationRootCoordinator,
appLockService: AppLockServiceProtocol,
bugReportService: BugReportServiceProtocol,
@@ -51,17 +79,21 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
appHooks: AppHooks,
analytics: AnalyticsService,
notificationManager: NotificationManagerProtocol,
isNewLogin: Bool) {
stateMachineFactory: StateMachineFactoryProtocol) {
self.userSession = userSession
self.navigationRootCoordinator = navigationRootCoordinator
self.appLockService = appLockService
self.bugReportService = bugReportService
self.appMediator = appMediator
self.appSettings = appSettings
self.analytics = analytics
navigationTabCoordinator = NavigationTabCoordinator()
navigationRootCoordinator.setRootCoordinator(navigationTabCoordinator)
let chatsSplitCoordinator = NavigationSplitCoordinator(placeholderCoordinator: PlaceholderScreenCoordinator())
chatsFlowCoordinator = ChatsFlowCoordinator(userSession: userSession,
isNewLogin: isNewLogin,
navigationSplitCoordinator: chatsSplitCoordinator,
appLockService: appLockService,
bugReportService: bugReportService,
@@ -72,7 +104,7 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
appHooks: appHooks,
analytics: analytics,
notificationManager: notificationManager,
isNewLogin: isNewLogin)
stateMachineFactory: stateMachineFactory)
chatsTabDetails = .init(tag: HomeTab.chats, title: L10n.screenHomeTabChats, icon: \.chat, selectedIcon: \.chatSolid)
chatsTabDetails.barVisibility = .hidden
@@ -98,16 +130,14 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
.init(coordinator: spacesSplitCoordinator, details: spacesTabDetails)
])
stateMachine = stateMachineFactory.makeUserSessionFlowStateMachine(state: .initial)
configureStateMachine()
setupObservers()
}
func start() {
#warning("This flow still needs a state machine.")
chatsFlowCoordinator.start()
spaceExplorerFlowCoordinator.start()
attemptStartingOnboarding()
stateMachine.tryEvent(.start)
}
func stop() {
@@ -115,13 +145,31 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
}
func handleAppRoute(_ appRoute: AppRoute, animated: Bool) {
// There aren't any routes that directly target this flow yet, so pass them directly to the
// chats flow coordinator.
chatsFlowCoordinator.handleAppRoute(appRoute, animated: animated)
navigationTabCoordinator.selectedTab = .chats
switch appRoute {
case .accountProvisioningLink:
break // We always ignore this flow when logged in.
case .settings, .chatBackupSettings:
if stateMachine.state != .settingsScreen {
stateMachine.tryEvent(.showSettingsScreen)
}
settingsFlowCoordinator?.handleAppRoute(appRoute, animated: animated)
case .roomList, .room, .roomAlias, .childRoom, .childRoomAlias,
.roomDetails, .roomMemberDetails, .userProfile,
.event, .eventOnRoomAlias, .childEvent, .childEventOnRoomAlias,
.call, .genericCallLink, .share, .transferOwnership:
clearRoute(animated: animated) // Make sure the presented route is visible.
chatsFlowCoordinator.handleAppRoute(appRoute, animated: animated)
navigationTabCoordinator.selectedTab = .chats
}
}
func clearRoute(animated: Bool) {
switch stateMachine.state {
case .initial, .tabBar:
break
case .settingsScreen:
navigationTabCoordinator.setSheetCoordinator(nil, animated: animated)
}
chatsFlowCoordinator.clearRoute(animated: animated)
}
@@ -132,19 +180,40 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
// MARK: - Private
private func configureStateMachine() {
stateMachine.addRoutes(event: .start, transitions: [.initial => .tabBar]) { [weak self] _ in
guard let self else { return }
chatsFlowCoordinator.start()
spaceExplorerFlowCoordinator.start()
attemptStartingOnboarding()
}
stateMachine.addRoutes(event: .showSettingsScreen, transitions: [.tabBar => .settingsScreen]) { [weak self] _ in
self?.startSettingsFlow()
}
stateMachine.addRoutes(event: .dismissedSettingsScreen, transitions: [.settingsScreen => .tabBar]) { [weak self] _ in
self?.settingsFlowCoordinator = nil
}
stateMachine.addErrorHandler { context in
fatalError("Unexpected transition: \(context)")
}
}
private func setupObservers() {
chatsFlowCoordinator.actionsPublisher
.sink { [weak self] action in
guard let self else { return }
switch action {
case .logout:
Task { await self.runLogoutFlow() }
case .showSettings:
handleAppRoute(.settings, animated: true)
case .showChatBackupSettings:
handleAppRoute(.chatBackupSettings, animated: true)
case .sessionVerification(let flow):
presentSessionVerificationScreen(flow: flow)
case .clearCache:
actionsSubject.send(.clearCache)
case .forceLogout:
actionsSubject.send(.forceLogout)
case .logout:
Task { await self.runLogoutFlow() }
}
}
.store(in: &cancellables)
@@ -154,7 +223,7 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
guard let self else { return }
switch action {
case .showSettings:
break
stateMachine.tryEvent(.showSettingsScreen)
}
}
.store(in: &cancellables)
@@ -198,6 +267,51 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
}
}
// MARK: - Settings
private func startSettingsFlow() {
let navigationStackCoordinator = NavigationStackCoordinator()
let coordinator = SettingsFlowCoordinator(parameters: .init(userSession: userSession,
windowManager: appMediator.windowManager,
appLockService: appLockService,
bugReportService: bugReportService,
notificationSettings: userSession.clientProxy.notificationSettings,
secureBackupController: userSession.clientProxy.secureBackupController,
appSettings: appSettings,
navigationStackCoordinator: navigationStackCoordinator,
userIndicatorController: ServiceLocator.shared.userIndicatorController,
analytics: analytics))
coordinator.actions.sink { [weak self] action in
guard let self else { return }
switch action {
case .dismiss:
navigationTabCoordinator.setSheetCoordinator(nil)
case .clearCache:
actionsSubject.send(.clearCache)
case .runLogoutFlow:
Task {
self.navigationTabCoordinator.setSheetCoordinator(nil)
// The sheet needs to be dismissed before the alert can be shown
try await Task.sleep(for: .milliseconds(100))
await self.runLogoutFlow()
}
case .forceLogout:
actionsSubject.send(.forceLogout)
}
}
.store(in: &cancellables)
settingsFlowCoordinator = coordinator
coordinator.handleAppRoute(.settings, animated: false)
navigationTabCoordinator.setSheetCoordinator(navigationStackCoordinator) { [weak self] in
self?.stateMachine.tryEvent(.dismissedSettingsScreen)
}
}
// MARK: - Session Verification
private func setupSessionVerificationRequestsObserver() {
@@ -0,0 +1,51 @@
//
// Copyright 2025 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
protocol StateMachineFactoryProtocol {
func makeUserSessionFlowStateMachine(state: UserSessionFlowCoordinator.State) -> StateMachine<UserSessionFlowCoordinator.State, UserSessionFlowCoordinator.Event>
func makeChatsFlowStateMachine() -> ChatsFlowCoordinatorStateMachine
}
struct StateMachineFactory: StateMachineFactoryProtocol {
func makeUserSessionFlowStateMachine(state: UserSessionFlowCoordinator.State) -> StateMachine<UserSessionFlowCoordinator.State, UserSessionFlowCoordinator.Event> {
.init(state: state)
}
func makeChatsFlowStateMachine() -> ChatsFlowCoordinatorStateMachine {
.init()
}
}
// MARK: For testing
class PublishedStateMachineFactory: StateMachineFactoryProtocol {
let baseFactory = StateMachineFactory()
// MARK: UserSessionFlowCoordinator
let userSessionFlowStatePublisher = PassthroughSubject<UserSessionFlowCoordinator.State, Never>()
func makeUserSessionFlowStateMachine(state: UserSessionFlowCoordinator.State) -> StateMachine<UserSessionFlowCoordinator.State, UserSessionFlowCoordinator.Event> {
let stateMachine = baseFactory.makeUserSessionFlowStateMachine(state: state)
stateMachine.addAnyHandler(.any => .any) { [weak self] in self?.userSessionFlowStatePublisher.send($0.toState) }
return stateMachine
}
// MARK: ChatsFlowCoordinator
let chatsFlowStatePublisher = PassthroughSubject<ChatsFlowCoordinatorStateMachine.State, Never>()
func makeChatsFlowStateMachine() -> ChatsFlowCoordinatorStateMachine {
let stateMachine = baseFactory.makeChatsFlowStateMachine()
stateMachine.addTransitionHandler { [weak self] in self?.chatsFlowStatePublisher.send($0.toState) }
return stateMachine
}
}
@@ -589,6 +589,7 @@ class MockScreen: Identifiable {
appMediator.underlyingWindowManager = windowManager
let flowCoordinator = UserSessionFlowCoordinator(userSession: UserSessionMock(.init(clientProxy: clientProxy)),
isNewLogin: false,
navigationRootCoordinator: navigationRootCoordinator,
appLockService: AppLockService(keychainController: KeychainControllerMock(),
appSettings: ServiceLocator.shared.settings),
@@ -600,7 +601,7 @@ class MockScreen: Identifiable {
appHooks: AppHooks(),
analytics: ServiceLocator.shared.analytics,
notificationManager: NotificationManagerMock(),
isNewLogin: false)
stateMachineFactory: StateMachineFactory())
flowCoordinator.start()
@@ -746,6 +747,7 @@ class MockScreen: Identifiable {
appSettings: ServiceLocator.shared.settings)
let flowCoordinator = ChatsFlowCoordinator(userSession: UserSessionMock(.init(clientProxy: clientProxy)),
isNewLogin: false,
navigationSplitCoordinator: navigationSplitCoordinator,
appLockService: AppLockService(keychainController: KeychainControllerMock(),
appSettings: ServiceLocator.shared.settings),
@@ -757,7 +759,7 @@ class MockScreen: Identifiable {
appHooks: AppHooks(),
analytics: ServiceLocator.shared.analytics,
notificationManager: NotificationManagerMock(),
isNewLogin: false)
stateMachineFactory: StateMachineFactory())
flowCoordinator.start()