mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-02 21:33:26 +00:00
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:
@@ -519,6 +519,7 @@
|
|||||||
61941DEE5F3834765770BE01 /* InviteUsersScreenSelectedItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10F32E0B4B83D2A11EE8D011 /* InviteUsersScreenSelectedItem.swift */; };
|
61941DEE5F3834765770BE01 /* InviteUsersScreenSelectedItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10F32E0B4B83D2A11EE8D011 /* InviteUsersScreenSelectedItem.swift */; };
|
||||||
61A36B9BB2ADE36CEFF5E98C /* Array.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E93A1BE7D8A2EBCAD51EEB4 /* Array.swift */; };
|
61A36B9BB2ADE36CEFF5E98C /* Array.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E93A1BE7D8A2EBCAD51EEB4 /* Array.swift */; };
|
||||||
62684AECDFC5C7DC989CBD9E /* SnapshotTesting in Frameworks */ = {isa = PBXBuildFile; productRef = 7B6BC3219ADD8AA0311D2B86 /* SnapshotTesting */; };
|
62684AECDFC5C7DC989CBD9E /* SnapshotTesting in Frameworks */ = {isa = PBXBuildFile; productRef = 7B6BC3219ADD8AA0311D2B86 /* SnapshotTesting */; };
|
||||||
|
627139A3D79F032BA81E3A53 /* UserSessionFlowCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FA29BAE9B0F2D90E57B261C /* UserSessionFlowCoordinatorTests.swift */; };
|
||||||
6298AB0906DDD3525CD78C6B /* KZFileWatchers in Frameworks */ = {isa = PBXBuildFile; productRef = 81DB3AB6CE996AB3954F4F03 /* KZFileWatchers */; };
|
6298AB0906DDD3525CD78C6B /* KZFileWatchers in Frameworks */ = {isa = PBXBuildFile; productRef = 81DB3AB6CE996AB3954F4F03 /* KZFileWatchers */; };
|
||||||
62A7FC3A0191BC7181AA432B /* AudioRecorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 907FA4DE17DEA1A3738EFB83 /* AudioRecorder.swift */; };
|
62A7FC3A0191BC7181AA432B /* AudioRecorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 907FA4DE17DEA1A3738EFB83 /* AudioRecorder.swift */; };
|
||||||
62C5876C4254C58C2086F0DE /* HomeScreenContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3B4B58B79A6FA250B24A1EC /* HomeScreenContent.swift */; };
|
62C5876C4254C58C2086F0DE /* HomeScreenContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3B4B58B79A6FA250B24A1EC /* HomeScreenContent.swift */; };
|
||||||
@@ -1215,6 +1216,7 @@
|
|||||||
E79D79CDAFE8BEBCC3AECA54 /* AppLockScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08283301736A6FE9D558B2CB /* AppLockScreenViewModelProtocol.swift */; };
|
E79D79CDAFE8BEBCC3AECA54 /* AppLockScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08283301736A6FE9D558B2CB /* AppLockScreenViewModelProtocol.swift */; };
|
||||||
E82E13CC3EB923CCB8F8273C /* TimelineProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9E543072DE58E751F028998 /* TimelineProxy.swift */; };
|
E82E13CC3EB923CCB8F8273C /* TimelineProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9E543072DE58E751F028998 /* TimelineProxy.swift */; };
|
||||||
E84ADFE9696936C18C2424B5 /* SecureBackupScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A00BB9CD12CF6AC98D5485 /* SecureBackupScreen.swift */; };
|
E84ADFE9696936C18C2424B5 /* SecureBackupScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A00BB9CD12CF6AC98D5485 /* SecureBackupScreen.swift */; };
|
||||||
|
E8AE3BE5C5D2E6C91751A2A6 /* StateMachineFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC73285743189AE4498A51DD /* StateMachineFactory.swift */; };
|
||||||
E8B290CBB7E5FF5E3C1B6124 /* KnockRequestsListEmptyStateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 627A8B5E798CC778C363655E /* KnockRequestsListEmptyStateView.swift */; };
|
E8B290CBB7E5FF5E3C1B6124 /* KnockRequestsListEmptyStateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 627A8B5E798CC778C363655E /* KnockRequestsListEmptyStateView.swift */; };
|
||||||
E8C4D9F93F0DCED211D5F187 /* HTMLParserStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3877D3CFAC1D33823359BAF /* HTMLParserStyle.swift */; };
|
E8C4D9F93F0DCED211D5F187 /* HTMLParserStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3877D3CFAC1D33823359BAF /* HTMLParserStyle.swift */; };
|
||||||
E8C65C19F7C40EE545172DD6 /* RoomScreenFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4137900E28201C314C835C11 /* RoomScreenFooterView.swift */; };
|
E8C65C19F7C40EE545172DD6 /* RoomScreenFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4137900E28201C314C835C11 /* RoomScreenFooterView.swift */; };
|
||||||
@@ -1849,6 +1851,7 @@
|
|||||||
4E7F7A975514E850A834B29F /* PaginationIndicatorRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginationIndicatorRoomTimelineView.swift; sourceTree = "<group>"; };
|
4E7F7A975514E850A834B29F /* PaginationIndicatorRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginationIndicatorRoomTimelineView.swift; sourceTree = "<group>"; };
|
||||||
4F5F0662483ED69791D63B16 /* et */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = et; path = et.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
4F5F0662483ED69791D63B16 /* et */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = et; path = et.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||||
4F75EF13F49DD2204E760910 /* FileRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileRoomTimelineView.swift; sourceTree = "<group>"; };
|
4F75EF13F49DD2204E760910 /* FileRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileRoomTimelineView.swift; sourceTree = "<group>"; };
|
||||||
|
4FA29BAE9B0F2D90E57B261C /* UserSessionFlowCoordinatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionFlowCoordinatorTests.swift; sourceTree = "<group>"; };
|
||||||
4FAC770857E525B51E277D8C /* SpaceListScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpaceListScreenModels.swift; sourceTree = "<group>"; };
|
4FAC770857E525B51E277D8C /* SpaceListScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpaceListScreenModels.swift; sourceTree = "<group>"; };
|
||||||
4FCB2126C091EEF2454B4D56 /* RoomFlowCoordinatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomFlowCoordinatorTests.swift; sourceTree = "<group>"; };
|
4FCB2126C091EEF2454B4D56 /* RoomFlowCoordinatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomFlowCoordinatorTests.swift; sourceTree = "<group>"; };
|
||||||
4FDD775CFD72DD2D3C8A8390 /* NotificationSettingsProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsProxyProtocol.swift; sourceTree = "<group>"; };
|
4FDD775CFD72DD2D3C8A8390 /* NotificationSettingsProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsProxyProtocol.swift; sourceTree = "<group>"; };
|
||||||
@@ -2300,6 +2303,7 @@
|
|||||||
AC43313F21511C853D34544E /* SoftLogoutScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoftLogoutScreenViewModelTests.swift; sourceTree = "<group>"; };
|
AC43313F21511C853D34544E /* SoftLogoutScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoftLogoutScreenViewModelTests.swift; sourceTree = "<group>"; };
|
||||||
AC4F10BDD56FA77FEC742333 /* VoiceMessageMediaManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessageMediaManagerTests.swift; sourceTree = "<group>"; };
|
AC4F10BDD56FA77FEC742333 /* VoiceMessageMediaManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessageMediaManagerTests.swift; sourceTree = "<group>"; };
|
||||||
AC5F5209279A752D98AAC4B2 /* CollapsibleFlowLayoutTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollapsibleFlowLayoutTests.swift; sourceTree = "<group>"; };
|
AC5F5209279A752D98AAC4B2 /* CollapsibleFlowLayoutTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollapsibleFlowLayoutTests.swift; sourceTree = "<group>"; };
|
||||||
|
AC73285743189AE4498A51DD /* StateMachineFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateMachineFactory.swift; sourceTree = "<group>"; };
|
||||||
AC9104846487244648D32C6D /* AudioPlayerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioPlayerProtocol.swift; sourceTree = "<group>"; };
|
AC9104846487244648D32C6D /* AudioPlayerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioPlayerProtocol.swift; sourceTree = "<group>"; };
|
||||||
ACA11F7F50A4A3887A18CA5A /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = "<group>"; };
|
ACA11F7F50A4A3887A18CA5A /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||||
ACD7BD6BEE21264F6677904A /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
ACD7BD6BEE21264F6677904A /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||||
@@ -2947,6 +2951,7 @@
|
|||||||
BDCEF7C3BF6D09F5611CFC8B /* SecureBackup */,
|
BDCEF7C3BF6D09F5611CFC8B /* SecureBackup */,
|
||||||
82D5AD3EAE3A5C1068A44A88 /* Session */,
|
82D5AD3EAE3A5C1068A44A88 /* Session */,
|
||||||
5329E48968EB951235E83DAE /* SessionVerification */,
|
5329E48968EB951235E83DAE /* SessionVerification */,
|
||||||
|
AA94873C78BBB969CE65EE52 /* StateMachine */,
|
||||||
FCDF06BDB123505F0334B4F9 /* Timeline */,
|
FCDF06BDB123505F0334B4F9 /* Timeline */,
|
||||||
E4E42F93A69AE52E6FAE9412 /* Users */,
|
E4E42F93A69AE52E6FAE9412 /* Users */,
|
||||||
CBBF6127C313A5412E438BC6 /* UserSession */,
|
CBBF6127C313A5412E438BC6 /* UserSession */,
|
||||||
@@ -4522,6 +4527,7 @@
|
|||||||
2429224EB0EEA34D35CE9249 /* UserIndicatorControllerTests.swift */,
|
2429224EB0EEA34D35CE9249 /* UserIndicatorControllerTests.swift */,
|
||||||
BA241DEEF7C8A7181C0AEDC9 /* UserPreferenceTests.swift */,
|
BA241DEEF7C8A7181C0AEDC9 /* UserPreferenceTests.swift */,
|
||||||
71E2E5103702D13361D09100 /* UserProfileScreenViewModelTests.swift */,
|
71E2E5103702D13361D09100 /* UserProfileScreenViewModelTests.swift */,
|
||||||
|
4FA29BAE9B0F2D90E57B261C /* UserSessionFlowCoordinatorTests.swift */,
|
||||||
283974987DA7EC61D2AB57D9 /* VoiceMessageCacheTests.swift */,
|
283974987DA7EC61D2AB57D9 /* VoiceMessageCacheTests.swift */,
|
||||||
AC4F10BDD56FA77FEC742333 /* VoiceMessageMediaManagerTests.swift */,
|
AC4F10BDD56FA77FEC742333 /* VoiceMessageMediaManagerTests.swift */,
|
||||||
D93C94C30E3135BC9290DE13 /* VoiceMessageRecorderTests.swift */,
|
D93C94C30E3135BC9290DE13 /* VoiceMessageRecorderTests.swift */,
|
||||||
@@ -5368,6 +5374,14 @@
|
|||||||
path = DevelopmentAssets;
|
path = DevelopmentAssets;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
AA94873C78BBB969CE65EE52 /* StateMachine */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
AC73285743189AE4498A51DD /* StateMachineFactory.swift */,
|
||||||
|
);
|
||||||
|
path = StateMachine;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
AAFDD509929A0CCF8BCE51EB /* Authentication */ = {
|
AAFDD509929A0CCF8BCE51EB /* Authentication */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -7187,6 +7201,7 @@
|
|||||||
A1DF0E1E526A981ED6D5DF44 /* UserIndicatorControllerTests.swift in Sources */,
|
A1DF0E1E526A981ED6D5DF44 /* UserIndicatorControllerTests.swift in Sources */,
|
||||||
04F17DE71A50206336749BAC /* UserPreferenceTests.swift in Sources */,
|
04F17DE71A50206336749BAC /* UserPreferenceTests.swift in Sources */,
|
||||||
73F547BEB41D3DAFAAF6E0AF /* UserProfileScreenViewModelTests.swift in Sources */,
|
73F547BEB41D3DAFAAF6E0AF /* UserProfileScreenViewModelTests.swift in Sources */,
|
||||||
|
627139A3D79F032BA81E3A53 /* UserSessionFlowCoordinatorTests.swift in Sources */,
|
||||||
81A7C020CB5F6232242A8414 /* UserSessionTests.swift in Sources */,
|
81A7C020CB5F6232242A8414 /* UserSessionTests.swift in Sources */,
|
||||||
21AFEFB8CEFE56A3811A1F5B /* VoiceMessageCacheTests.swift in Sources */,
|
21AFEFB8CEFE56A3811A1F5B /* VoiceMessageCacheTests.swift in Sources */,
|
||||||
44BDD670FF9095ACE240A3A2 /* VoiceMessageMediaManagerTests.swift in Sources */,
|
44BDD670FF9095ACE240A3A2 /* VoiceMessageMediaManagerTests.swift in Sources */,
|
||||||
@@ -8023,6 +8038,7 @@
|
|||||||
C051475DFF4C8EBDDF4DC8E4 /* StartChatScreenModels.swift in Sources */,
|
C051475DFF4C8EBDDF4DC8E4 /* StartChatScreenModels.swift in Sources */,
|
||||||
4A85928E27D4C1A548A06EE9 /* StartChatScreenViewModel.swift in Sources */,
|
4A85928E27D4C1A548A06EE9 /* StartChatScreenViewModel.swift in Sources */,
|
||||||
B402708F8728DD0DB7C324E2 /* StartChatScreenViewModelProtocol.swift in Sources */,
|
B402708F8728DD0DB7C324E2 /* StartChatScreenViewModelProtocol.swift in Sources */,
|
||||||
|
E8AE3BE5C5D2E6C91751A2A6 /* StateMachineFactory.swift in Sources */,
|
||||||
CB6BCBF28E4B76EA08C2926D /* StateRoomTimelineItem.swift in Sources */,
|
CB6BCBF28E4B76EA08C2926D /* StateRoomTimelineItem.swift in Sources */,
|
||||||
3B277D9538090766DA6C4566 /* StateRoomTimelineView.swift in Sources */,
|
3B277D9538090766DA6C4566 /* StateRoomTimelineView.swift in Sources */,
|
||||||
B4AAB3257A83B73F53FB2689 /* StateStoreViewModel.swift in Sources */,
|
B4AAB3257A83B73F53FB2689 /* StateStoreViewModel.swift in Sources */,
|
||||||
|
|||||||
@@ -641,6 +641,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
|
|||||||
}
|
}
|
||||||
|
|
||||||
let userSessionFlowCoordinator = UserSessionFlowCoordinator(userSession: userSession,
|
let userSessionFlowCoordinator = UserSessionFlowCoordinator(userSession: userSession,
|
||||||
|
isNewLogin: isNewLogin,
|
||||||
navigationRootCoordinator: navigationRootCoordinator,
|
navigationRootCoordinator: navigationRootCoordinator,
|
||||||
appLockService: appLockFlowCoordinator.appLockService,
|
appLockService: appLockFlowCoordinator.appLockService,
|
||||||
bugReportService: ServiceLocator.shared.bugReportService,
|
bugReportService: ServiceLocator.shared.bugReportService,
|
||||||
@@ -651,7 +652,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
|
|||||||
appHooks: appHooks,
|
appHooks: appHooks,
|
||||||
analytics: ServiceLocator.shared.analytics,
|
analytics: ServiceLocator.shared.analytics,
|
||||||
notificationManager: notificationManager,
|
notificationManager: notificationManager,
|
||||||
isNewLogin: isNewLogin)
|
stateMachineFactory: StateMachineFactory())
|
||||||
|
|
||||||
userSessionFlowCoordinator.actionsPublisher
|
userSessionFlowCoordinator.actionsPublisher
|
||||||
.sink { [weak self] action in
|
.sink { [weak self] action in
|
||||||
|
|||||||
@@ -12,11 +12,10 @@ import MatrixRustSDK
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
enum ChatsFlowCoordinatorAction {
|
enum ChatsFlowCoordinatorAction {
|
||||||
case logout
|
case showSettings
|
||||||
|
case showChatBackupSettings
|
||||||
case sessionVerification(SessionVerificationScreenFlow)
|
case sessionVerification(SessionVerificationScreenFlow)
|
||||||
case clearCache
|
case logout
|
||||||
/// Logout and disable App Lock without any confirmation. The user forgot their PIN.
|
|
||||||
case forceLogout
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class ChatsFlowCoordinator: FlowCoordinatorProtocol {
|
class ChatsFlowCoordinator: FlowCoordinatorProtocol {
|
||||||
@@ -36,8 +35,6 @@ class ChatsFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
private var roomFlowCoordinator: RoomFlowCoordinator?
|
private var roomFlowCoordinator: RoomFlowCoordinator?
|
||||||
private let timelineControllerFactory: TimelineControllerFactoryProtocol
|
private let timelineControllerFactory: TimelineControllerFactoryProtocol
|
||||||
|
|
||||||
private let settingsFlowCoordinator: SettingsFlowCoordinator
|
|
||||||
|
|
||||||
// periphery:ignore - retaining purpose
|
// periphery:ignore - retaining purpose
|
||||||
private var bugReportFlowCoordinator: BugReportFlowCoordinator?
|
private var bugReportFlowCoordinator: BugReportFlowCoordinator?
|
||||||
|
|
||||||
@@ -59,10 +56,8 @@ class ChatsFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
actionsSubject.eraseToAnyPublisher()
|
actionsSubject.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// For testing purposes.
|
|
||||||
var statePublisher: AnyPublisher<ChatsFlowCoordinatorStateMachine.State, Never> { stateMachine.statePublisher }
|
|
||||||
|
|
||||||
init(userSession: UserSessionProtocol,
|
init(userSession: UserSessionProtocol,
|
||||||
|
isNewLogin: Bool,
|
||||||
navigationSplitCoordinator: NavigationSplitCoordinator,
|
navigationSplitCoordinator: NavigationSplitCoordinator,
|
||||||
appLockService: AppLockServiceProtocol,
|
appLockService: AppLockServiceProtocol,
|
||||||
bugReportService: BugReportServiceProtocol,
|
bugReportService: BugReportServiceProtocol,
|
||||||
@@ -73,8 +68,8 @@ class ChatsFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
appHooks: AppHooks,
|
appHooks: AppHooks,
|
||||||
analytics: AnalyticsService,
|
analytics: AnalyticsService,
|
||||||
notificationManager: NotificationManagerProtocol,
|
notificationManager: NotificationManagerProtocol,
|
||||||
isNewLogin: Bool) {
|
stateMachineFactory: StateMachineFactoryProtocol) {
|
||||||
stateMachine = ChatsFlowCoordinatorStateMachine()
|
stateMachine = stateMachineFactory.makeChatsFlowStateMachine()
|
||||||
self.userSession = userSession
|
self.userSession = userSession
|
||||||
self.navigationSplitCoordinator = navigationSplitCoordinator
|
self.navigationSplitCoordinator = navigationSplitCoordinator
|
||||||
self.bugReportService = bugReportService
|
self.bugReportService = bugReportService
|
||||||
@@ -88,22 +83,9 @@ class ChatsFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
|
|
||||||
sidebarNavigationStackCoordinator = NavigationStackCoordinator(navigationSplitCoordinator: navigationSplitCoordinator)
|
sidebarNavigationStackCoordinator = NavigationStackCoordinator(navigationSplitCoordinator: navigationSplitCoordinator)
|
||||||
detailNavigationStackCoordinator = NavigationStackCoordinator(navigationSplitCoordinator: navigationSplitCoordinator)
|
detailNavigationStackCoordinator = NavigationStackCoordinator(navigationSplitCoordinator: navigationSplitCoordinator)
|
||||||
|
|
||||||
navigationSplitCoordinator.setSidebarCoordinator(sidebarNavigationStackCoordinator)
|
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()
|
setupStateMachine()
|
||||||
|
|
||||||
setupObservers()
|
setupObservers()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,8 +171,6 @@ class ChatsFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
Task { await presentCallScreen(roomID: roomID) }
|
Task { await presentCallScreen(roomID: roomID) }
|
||||||
case .genericCallLink(let url):
|
case .genericCallLink(let url):
|
||||||
presentCallScreen(genericCallLink: url)
|
presentCallScreen(genericCallLink: url)
|
||||||
case .settings, .chatBackupSettings:
|
|
||||||
settingsFlowCoordinator.handleAppRoute(appRoute, animated: animated)
|
|
||||||
case .share(let payload):
|
case .share(let payload):
|
||||||
if let roomID = payload.roomID {
|
if let roomID = payload.roomID {
|
||||||
stateMachine.processEvent(.selectRoom(roomID: roomID,
|
stateMachine.processEvent(.selectRoom(roomID: roomID,
|
||||||
@@ -200,14 +180,14 @@ class ChatsFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
} else {
|
} else {
|
||||||
stateMachine.processEvent(.showShareExtensionRoomList(sharePayload: payload), userInfo: .init(animated: animated))
|
stateMachine.processEvent(.showShareExtensionRoomList(sharePayload: payload), userInfo: .init(animated: animated))
|
||||||
}
|
}
|
||||||
case .accountProvisioningLink:
|
|
||||||
break // We always ignore this flow when logged in.
|
|
||||||
case .transferOwnership(let roomID):
|
case .transferOwnership(let roomID):
|
||||||
if stateMachine.state.roomListSelectedRoomID == roomID {
|
if stateMachine.state.roomListSelectedRoomID == roomID {
|
||||||
roomFlowCoordinator?.handleAppRoute(appRoute, animated: animated)
|
roomFlowCoordinator?.handleAppRoute(appRoute, animated: animated)
|
||||||
} else {
|
} else {
|
||||||
stateMachine.processEvent(.selectRoom(roomID: roomID, via: [], entryPoint: .transferOwnership))
|
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.
|
hideCallScreenOverlay() // Turn any active call into a PiP so that navigation from a notification is visible to the user.
|
||||||
case(.roomList, .deselectRoom, .roomList):
|
case(.roomList, .deselectRoom, .roomList):
|
||||||
dismissRoomFlow(animated: animated)
|
dismissRoomFlow(animated: animated)
|
||||||
|
|
||||||
case (.roomList, .showSettingsScreen, .settingsScreen):
|
|
||||||
break
|
|
||||||
case (.settingsScreen, .dismissedSettingsScreen, .roomList):
|
|
||||||
break
|
|
||||||
|
|
||||||
case (.roomList, .feedbackScreen, .feedbackScreen):
|
case (.roomList, .feedbackScreen, .feedbackScreen):
|
||||||
bugReportFlowCoordinator = BugReportFlowCoordinator(parameters: .init(presentationMode: .sheet(sidebarNavigationStackCoordinator),
|
bugReportFlowCoordinator = BugReportFlowCoordinator(parameters: .init(presentationMode: .sheet(sidebarNavigationStackCoordinator),
|
||||||
@@ -333,24 +308,6 @@ class ChatsFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func setupObservers() {
|
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
|
userSession.clientProxy.actionsPublisher
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.sink { [weak self] action in
|
.sink { [weak self] action in
|
||||||
@@ -439,11 +396,11 @@ class ChatsFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
clearRoute(animated: true)
|
clearRoute(animated: true)
|
||||||
}
|
}
|
||||||
case .presentSettingsScreen:
|
case .presentSettingsScreen:
|
||||||
settingsFlowCoordinator.handleAppRoute(.settings, animated: true)
|
actionsSubject.send(.showSettings)
|
||||||
case .presentFeedbackScreen:
|
case .presentFeedbackScreen:
|
||||||
stateMachine.processEvent(.feedbackScreen)
|
stateMachine.processEvent(.feedbackScreen)
|
||||||
case .presentSecureBackupSettings:
|
case .presentSecureBackupSettings:
|
||||||
settingsFlowCoordinator.handleAppRoute(.chatBackupSettings, animated: true)
|
actionsSubject.send(.showChatBackupSettings)
|
||||||
case .presentRecoveryKeyScreen:
|
case .presentRecoveryKeyScreen:
|
||||||
stateMachine.processEvent(.showRecoveryKeyScreen)
|
stateMachine.processEvent(.showRecoveryKeyScreen)
|
||||||
case .presentEncryptionResetScreen:
|
case .presentEncryptionResetScreen:
|
||||||
|
|||||||
@@ -21,9 +21,6 @@ class ChatsFlowCoordinatorStateMachine {
|
|||||||
/// Showing the feedback screen.
|
/// Showing the feedback screen.
|
||||||
case feedbackScreen(roomListSelectedRoomID: String?)
|
case feedbackScreen(roomListSelectedRoomID: String?)
|
||||||
|
|
||||||
/// Showing the settings screen
|
|
||||||
case settingsScreen(roomListSelectedRoomID: String?)
|
|
||||||
|
|
||||||
/// Showing the recovery key screen.
|
/// Showing the recovery key screen.
|
||||||
case recoveryKeyScreen(roomListSelectedRoomID: String?)
|
case recoveryKeyScreen(roomListSelectedRoomID: String?)
|
||||||
|
|
||||||
@@ -56,7 +53,6 @@ class ChatsFlowCoordinatorStateMachine {
|
|||||||
nil
|
nil
|
||||||
case .roomList(let roomListSelectedRoomID),
|
case .roomList(let roomListSelectedRoomID),
|
||||||
.feedbackScreen(let roomListSelectedRoomID),
|
.feedbackScreen(let roomListSelectedRoomID),
|
||||||
.settingsScreen(let roomListSelectedRoomID),
|
|
||||||
.recoveryKeyScreen(let roomListSelectedRoomID),
|
.recoveryKeyScreen(let roomListSelectedRoomID),
|
||||||
.encryptionResetFlow(let roomListSelectedRoomID),
|
.encryptionResetFlow(let roomListSelectedRoomID),
|
||||||
.startChatScreen(let roomListSelectedRoomID),
|
.startChatScreen(let roomListSelectedRoomID),
|
||||||
@@ -86,11 +82,6 @@ class ChatsFlowCoordinatorStateMachine {
|
|||||||
/// The room screen has been dismissed
|
/// The room screen has been dismissed
|
||||||
case deselectRoom
|
case deselectRoom
|
||||||
|
|
||||||
/// Request presentation of the settings screen
|
|
||||||
case showSettingsScreen
|
|
||||||
/// The settings screen has been dismissed
|
|
||||||
case dismissedSettingsScreen
|
|
||||||
|
|
||||||
/// Request presentation of the feedback screen
|
/// Request presentation of the feedback screen
|
||||||
case feedbackScreen
|
case feedbackScreen
|
||||||
/// The feedback screen has been dismissed
|
/// The feedback screen has been dismissed
|
||||||
@@ -137,11 +128,6 @@ class ChatsFlowCoordinatorStateMachine {
|
|||||||
stateMachine.state
|
stateMachine.state
|
||||||
}
|
}
|
||||||
|
|
||||||
var stateSubject = PassthroughSubject<State, Never>()
|
|
||||||
var statePublisher: AnyPublisher<State, Never> {
|
|
||||||
stateSubject.eraseToAnyPublisher()
|
|
||||||
}
|
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
stateMachine = StateMachine(state: .initial)
|
stateMachine = StateMachine(state: .initial)
|
||||||
configure()
|
configure()
|
||||||
@@ -156,11 +142,6 @@ class ChatsFlowCoordinatorStateMachine {
|
|||||||
return .roomList(roomListSelectedRoomID: roomID)
|
return .roomList(roomListSelectedRoomID: roomID)
|
||||||
case (.roomList, .deselectRoom):
|
case (.roomList, .deselectRoom):
|
||||||
return .roomList(roomListSelectedRoomID: nil)
|
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):
|
case (.roomList(let roomListSelectedRoomID), .feedbackScreen):
|
||||||
return .feedbackScreen(roomListSelectedRoomID: roomListSelectedRoomID)
|
return .feedbackScreen(roomListSelectedRoomID: roomListSelectedRoomID)
|
||||||
@@ -219,10 +200,6 @@ class ChatsFlowCoordinatorStateMachine {
|
|||||||
MXLog.info("Transitioning from \(context.fromState)` to `\(context.toState)`")
|
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
|
/// Attempt to move the state machine to another state through an event
|
||||||
|
|||||||
@@ -9,10 +9,9 @@ import Combine
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
enum SettingsFlowCoordinatorAction {
|
enum SettingsFlowCoordinatorAction {
|
||||||
case presentedSettings
|
case dismiss
|
||||||
case dismissedSettings
|
|
||||||
case runLogoutFlow
|
|
||||||
case clearCache
|
case clearCache
|
||||||
|
case runLogoutFlow
|
||||||
/// Logout without a confirmation. The user forgot their PIN.
|
/// Logout without a confirmation. The user forgot their PIN.
|
||||||
case forceLogout
|
case forceLogout
|
||||||
}
|
}
|
||||||
@@ -25,17 +24,14 @@ struct SettingsFlowCoordinatorParameters {
|
|||||||
let notificationSettings: NotificationSettingsProxyProtocol
|
let notificationSettings: NotificationSettingsProxyProtocol
|
||||||
let secureBackupController: SecureBackupControllerProtocol
|
let secureBackupController: SecureBackupControllerProtocol
|
||||||
let appSettings: AppSettings
|
let appSettings: AppSettings
|
||||||
let navigationSplitCoordinator: NavigationSplitCoordinator
|
let navigationStackCoordinator: NavigationStackCoordinator
|
||||||
let userIndicatorController: UserIndicatorControllerProtocol
|
let userIndicatorController: UserIndicatorControllerProtocol
|
||||||
let analytics: AnalyticsService
|
let analytics: AnalyticsService
|
||||||
}
|
}
|
||||||
|
|
||||||
class SettingsFlowCoordinator: FlowCoordinatorProtocol {
|
class SettingsFlowCoordinator: FlowCoordinatorProtocol {
|
||||||
private let parameters: SettingsFlowCoordinatorParameters
|
private let parameters: SettingsFlowCoordinatorParameters
|
||||||
|
private var navigationStackCoordinator: NavigationStackCoordinator { parameters.navigationStackCoordinator }
|
||||||
private var navigationStackCoordinator: NavigationStackCoordinator!
|
|
||||||
|
|
||||||
private var cancellables = Set<AnyCancellable>()
|
|
||||||
|
|
||||||
// periphery:ignore - retaining purpose
|
// periphery:ignore - retaining purpose
|
||||||
private var appLockSetupFlowCoordinator: AppLockSetupFlowCoordinator?
|
private var appLockSetupFlowCoordinator: AppLockSetupFlowCoordinator?
|
||||||
@@ -44,6 +40,8 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
// periphery:ignore - retaining purpose
|
// periphery:ignore - retaining purpose
|
||||||
private var encryptionSettingsFlowCoordinator: EncryptionSettingsFlowCoordinator?
|
private var encryptionSettingsFlowCoordinator: EncryptionSettingsFlowCoordinator?
|
||||||
|
|
||||||
|
private var cancellables = Set<AnyCancellable>()
|
||||||
|
|
||||||
private let actionsSubject: PassthroughSubject<SettingsFlowCoordinatorAction, Never> = .init()
|
private let actionsSubject: PassthroughSubject<SettingsFlowCoordinatorAction, Never> = .init()
|
||||||
var actions: AnyPublisher<SettingsFlowCoordinatorAction, Never> {
|
var actions: AnyPublisher<SettingsFlowCoordinatorAction, Never> {
|
||||||
actionsSubject.eraseToAnyPublisher()
|
actionsSubject.eraseToAnyPublisher()
|
||||||
@@ -62,15 +60,7 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
case .settings:
|
case .settings:
|
||||||
presentSettingsScreen(animated: animated)
|
presentSettingsScreen(animated: animated)
|
||||||
case .chatBackupSettings:
|
case .chatBackupSettings:
|
||||||
if navigationStackCoordinator == nil {
|
startEncryptionSettingsFlow(animated: animated)
|
||||||
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)
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -83,8 +73,6 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
|
|
||||||
private func presentSettingsScreen(animated: Bool) {
|
private func presentSettingsScreen(animated: Bool) {
|
||||||
navigationStackCoordinator = NavigationStackCoordinator()
|
|
||||||
|
|
||||||
let settingsScreenCoordinator = SettingsScreenCoordinator(parameters: .init(userSession: parameters.userSession,
|
let settingsScreenCoordinator = SettingsScreenCoordinator(parameters: .init(userSession: parameters.userSession,
|
||||||
appSettings: parameters.appSettings,
|
appSettings: parameters.appSettings,
|
||||||
isBugReportServiceEnabled: parameters.bugReportService.isEnabled))
|
isBugReportServiceEnabled: parameters.bugReportService.isEnabled))
|
||||||
@@ -95,14 +83,9 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
|
|
||||||
switch action {
|
switch action {
|
||||||
case .dismiss:
|
case .dismiss:
|
||||||
parameters.navigationSplitCoordinator.setSheetCoordinator(nil)
|
actionsSubject.send(.dismiss)
|
||||||
case .logout:
|
case .logout:
|
||||||
parameters.navigationSplitCoordinator.setSheetCoordinator(nil)
|
actionsSubject.send(.runLogoutFlow)
|
||||||
|
|
||||||
// The settings sheet needs to be dismissed before the alert can be shown
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
|
||||||
self.actionsSubject.send(.runLogoutFlow)
|
|
||||||
}
|
|
||||||
case .secureBackup:
|
case .secureBackup:
|
||||||
startEncryptionSettingsFlow(animated: true)
|
startEncryptionSettingsFlow(animated: true)
|
||||||
case .userDetails:
|
case .userDetails:
|
||||||
@@ -136,15 +119,6 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
navigationStackCoordinator.setRootCoordinator(settingsScreenCoordinator, animated: animated)
|
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) {
|
private func startEncryptionSettingsFlow(animated: Bool) {
|
||||||
@@ -174,13 +148,13 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
userIndicatorController: parameters.userIndicatorController,
|
userIndicatorController: parameters.userIndicatorController,
|
||||||
appSettings: parameters.appSettings))
|
appSettings: parameters.appSettings))
|
||||||
|
|
||||||
navigationStackCoordinator?.push(coordinator)
|
navigationStackCoordinator.push(coordinator)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func presentAnalyticsScreen() {
|
private func presentAnalyticsScreen() {
|
||||||
let coordinator = AnalyticsSettingsScreenCoordinator(parameters: .init(appSettings: parameters.appSettings,
|
let coordinator = AnalyticsSettingsScreenCoordinator(parameters: .init(appSettings: parameters.appSettings,
|
||||||
analytics: parameters.analytics))
|
analytics: parameters.analytics))
|
||||||
navigationStackCoordinator?.push(coordinator)
|
navigationStackCoordinator.push(coordinator)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func presentAppLockSetupFlow() {
|
private func presentAppLockSetupFlow() {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
import Combine
|
import Combine
|
||||||
import Compound
|
import Compound
|
||||||
import MatrixRustSDK
|
import MatrixRustSDK
|
||||||
|
import SwiftState
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
enum UserSessionFlowCoordinatorAction {
|
enum UserSessionFlowCoordinatorAction {
|
||||||
@@ -23,8 +24,11 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
private let userSession: UserSessionProtocol
|
private let userSession: UserSessionProtocol
|
||||||
private let navigationRootCoordinator: NavigationRootCoordinator
|
private let navigationRootCoordinator: NavigationRootCoordinator
|
||||||
private let navigationTabCoordinator: NavigationTabCoordinator<HomeTab>
|
private let navigationTabCoordinator: NavigationTabCoordinator<HomeTab>
|
||||||
|
private let appLockService: AppLockServiceProtocol
|
||||||
|
private let bugReportService: BugReportServiceProtocol
|
||||||
private let appMediator: AppMediatorProtocol
|
private let appMediator: AppMediatorProtocol
|
||||||
private let appSettings: AppSettings
|
private let appSettings: AppSettings
|
||||||
|
private let analytics: AnalyticsService
|
||||||
|
|
||||||
private let onboardingFlowCoordinator: OnboardingFlowCoordinator
|
private let onboardingFlowCoordinator: OnboardingFlowCoordinator
|
||||||
private let onboardingStackCoordinator: NavigationStackCoordinator
|
private let onboardingStackCoordinator: NavigationStackCoordinator
|
||||||
@@ -33,14 +37,38 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
private let spaceExplorerFlowCoordinator: SpaceExplorerFlowCoordinator
|
private let spaceExplorerFlowCoordinator: SpaceExplorerFlowCoordinator
|
||||||
private let spacesTabDetails: NavigationTabCoordinator<HomeTab>.TabDetails
|
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()
|
private let actionsSubject: PassthroughSubject<UserSessionFlowCoordinatorAction, Never> = .init()
|
||||||
var actionsPublisher: AnyPublisher<UserSessionFlowCoordinatorAction, Never> {
|
var actionsPublisher: AnyPublisher<UserSessionFlowCoordinatorAction, Never> {
|
||||||
actionsSubject.eraseToAnyPublisher()
|
actionsSubject.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
private var cancellables: Set<AnyCancellable> = []
|
|
||||||
|
|
||||||
init(userSession: UserSessionProtocol,
|
init(userSession: UserSessionProtocol,
|
||||||
|
isNewLogin: Bool,
|
||||||
navigationRootCoordinator: NavigationRootCoordinator,
|
navigationRootCoordinator: NavigationRootCoordinator,
|
||||||
appLockService: AppLockServiceProtocol,
|
appLockService: AppLockServiceProtocol,
|
||||||
bugReportService: BugReportServiceProtocol,
|
bugReportService: BugReportServiceProtocol,
|
||||||
@@ -51,17 +79,21 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
appHooks: AppHooks,
|
appHooks: AppHooks,
|
||||||
analytics: AnalyticsService,
|
analytics: AnalyticsService,
|
||||||
notificationManager: NotificationManagerProtocol,
|
notificationManager: NotificationManagerProtocol,
|
||||||
isNewLogin: Bool) {
|
stateMachineFactory: StateMachineFactoryProtocol) {
|
||||||
self.userSession = userSession
|
self.userSession = userSession
|
||||||
self.navigationRootCoordinator = navigationRootCoordinator
|
self.navigationRootCoordinator = navigationRootCoordinator
|
||||||
|
self.appLockService = appLockService
|
||||||
|
self.bugReportService = bugReportService
|
||||||
self.appMediator = appMediator
|
self.appMediator = appMediator
|
||||||
self.appSettings = appSettings
|
self.appSettings = appSettings
|
||||||
|
self.analytics = analytics
|
||||||
|
|
||||||
navigationTabCoordinator = NavigationTabCoordinator()
|
navigationTabCoordinator = NavigationTabCoordinator()
|
||||||
navigationRootCoordinator.setRootCoordinator(navigationTabCoordinator)
|
navigationRootCoordinator.setRootCoordinator(navigationTabCoordinator)
|
||||||
|
|
||||||
let chatsSplitCoordinator = NavigationSplitCoordinator(placeholderCoordinator: PlaceholderScreenCoordinator())
|
let chatsSplitCoordinator = NavigationSplitCoordinator(placeholderCoordinator: PlaceholderScreenCoordinator())
|
||||||
chatsFlowCoordinator = ChatsFlowCoordinator(userSession: userSession,
|
chatsFlowCoordinator = ChatsFlowCoordinator(userSession: userSession,
|
||||||
|
isNewLogin: isNewLogin,
|
||||||
navigationSplitCoordinator: chatsSplitCoordinator,
|
navigationSplitCoordinator: chatsSplitCoordinator,
|
||||||
appLockService: appLockService,
|
appLockService: appLockService,
|
||||||
bugReportService: bugReportService,
|
bugReportService: bugReportService,
|
||||||
@@ -72,7 +104,7 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
appHooks: appHooks,
|
appHooks: appHooks,
|
||||||
analytics: analytics,
|
analytics: analytics,
|
||||||
notificationManager: notificationManager,
|
notificationManager: notificationManager,
|
||||||
isNewLogin: isNewLogin)
|
stateMachineFactory: stateMachineFactory)
|
||||||
chatsTabDetails = .init(tag: HomeTab.chats, title: L10n.screenHomeTabChats, icon: \.chat, selectedIcon: \.chatSolid)
|
chatsTabDetails = .init(tag: HomeTab.chats, title: L10n.screenHomeTabChats, icon: \.chat, selectedIcon: \.chatSolid)
|
||||||
chatsTabDetails.barVisibility = .hidden
|
chatsTabDetails.barVisibility = .hidden
|
||||||
|
|
||||||
@@ -98,16 +130,14 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
.init(coordinator: spacesSplitCoordinator, details: spacesTabDetails)
|
.init(coordinator: spacesSplitCoordinator, details: spacesTabDetails)
|
||||||
])
|
])
|
||||||
|
|
||||||
|
stateMachine = stateMachineFactory.makeUserSessionFlowStateMachine(state: .initial)
|
||||||
|
configureStateMachine()
|
||||||
|
|
||||||
setupObservers()
|
setupObservers()
|
||||||
}
|
}
|
||||||
|
|
||||||
func start() {
|
func start() {
|
||||||
#warning("This flow still needs a state machine.")
|
stateMachine.tryEvent(.start)
|
||||||
|
|
||||||
chatsFlowCoordinator.start()
|
|
||||||
spaceExplorerFlowCoordinator.start()
|
|
||||||
|
|
||||||
attemptStartingOnboarding()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func stop() {
|
func stop() {
|
||||||
@@ -115,13 +145,31 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func handleAppRoute(_ appRoute: AppRoute, animated: Bool) {
|
func handleAppRoute(_ appRoute: AppRoute, animated: Bool) {
|
||||||
// There aren't any routes that directly target this flow yet, so pass them directly to the
|
switch appRoute {
|
||||||
// chats flow coordinator.
|
case .accountProvisioningLink:
|
||||||
chatsFlowCoordinator.handleAppRoute(appRoute, animated: animated)
|
break // We always ignore this flow when logged in.
|
||||||
navigationTabCoordinator.selectedTab = .chats
|
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) {
|
func clearRoute(animated: Bool) {
|
||||||
|
switch stateMachine.state {
|
||||||
|
case .initial, .tabBar:
|
||||||
|
break
|
||||||
|
case .settingsScreen:
|
||||||
|
navigationTabCoordinator.setSheetCoordinator(nil, animated: animated)
|
||||||
|
}
|
||||||
chatsFlowCoordinator.clearRoute(animated: animated)
|
chatsFlowCoordinator.clearRoute(animated: animated)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,19 +180,40 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
|
|
||||||
// MARK: - Private
|
// 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() {
|
private func setupObservers() {
|
||||||
chatsFlowCoordinator.actionsPublisher
|
chatsFlowCoordinator.actionsPublisher
|
||||||
.sink { [weak self] action in
|
.sink { [weak self] action in
|
||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
switch action {
|
switch action {
|
||||||
case .logout:
|
case .showSettings:
|
||||||
Task { await self.runLogoutFlow() }
|
handleAppRoute(.settings, animated: true)
|
||||||
|
case .showChatBackupSettings:
|
||||||
|
handleAppRoute(.chatBackupSettings, animated: true)
|
||||||
case .sessionVerification(let flow):
|
case .sessionVerification(let flow):
|
||||||
presentSessionVerificationScreen(flow: flow)
|
presentSessionVerificationScreen(flow: flow)
|
||||||
case .clearCache:
|
case .logout:
|
||||||
actionsSubject.send(.clearCache)
|
Task { await self.runLogoutFlow() }
|
||||||
case .forceLogout:
|
|
||||||
actionsSubject.send(.forceLogout)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
@@ -154,7 +223,7 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
switch action {
|
switch action {
|
||||||
case .showSettings:
|
case .showSettings:
|
||||||
break
|
stateMachine.tryEvent(.showSettingsScreen)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.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
|
// MARK: - Session Verification
|
||||||
|
|
||||||
private func setupSessionVerificationRequestsObserver() {
|
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
|
appMediator.underlyingWindowManager = windowManager
|
||||||
|
|
||||||
let flowCoordinator = UserSessionFlowCoordinator(userSession: UserSessionMock(.init(clientProxy: clientProxy)),
|
let flowCoordinator = UserSessionFlowCoordinator(userSession: UserSessionMock(.init(clientProxy: clientProxy)),
|
||||||
|
isNewLogin: false,
|
||||||
navigationRootCoordinator: navigationRootCoordinator,
|
navigationRootCoordinator: navigationRootCoordinator,
|
||||||
appLockService: AppLockService(keychainController: KeychainControllerMock(),
|
appLockService: AppLockService(keychainController: KeychainControllerMock(),
|
||||||
appSettings: ServiceLocator.shared.settings),
|
appSettings: ServiceLocator.shared.settings),
|
||||||
@@ -600,7 +601,7 @@ class MockScreen: Identifiable {
|
|||||||
appHooks: AppHooks(),
|
appHooks: AppHooks(),
|
||||||
analytics: ServiceLocator.shared.analytics,
|
analytics: ServiceLocator.shared.analytics,
|
||||||
notificationManager: NotificationManagerMock(),
|
notificationManager: NotificationManagerMock(),
|
||||||
isNewLogin: false)
|
stateMachineFactory: StateMachineFactory())
|
||||||
|
|
||||||
flowCoordinator.start()
|
flowCoordinator.start()
|
||||||
|
|
||||||
@@ -746,6 +747,7 @@ class MockScreen: Identifiable {
|
|||||||
appSettings: ServiceLocator.shared.settings)
|
appSettings: ServiceLocator.shared.settings)
|
||||||
|
|
||||||
let flowCoordinator = ChatsFlowCoordinator(userSession: UserSessionMock(.init(clientProxy: clientProxy)),
|
let flowCoordinator = ChatsFlowCoordinator(userSession: UserSessionMock(.init(clientProxy: clientProxy)),
|
||||||
|
isNewLogin: false,
|
||||||
navigationSplitCoordinator: navigationSplitCoordinator,
|
navigationSplitCoordinator: navigationSplitCoordinator,
|
||||||
appLockService: AppLockService(keychainController: KeychainControllerMock(),
|
appLockService: AppLockService(keychainController: KeychainControllerMock(),
|
||||||
appSettings: ServiceLocator.shared.settings),
|
appSettings: ServiceLocator.shared.settings),
|
||||||
@@ -757,7 +759,7 @@ class MockScreen: Identifiable {
|
|||||||
appHooks: AppHooks(),
|
appHooks: AppHooks(),
|
||||||
analytics: ServiceLocator.shared.analytics,
|
analytics: ServiceLocator.shared.analytics,
|
||||||
notificationManager: NotificationManagerMock(),
|
notificationManager: NotificationManagerMock(),
|
||||||
isNewLogin: false)
|
stateMachineFactory: StateMachineFactory())
|
||||||
|
|
||||||
flowCoordinator.start()
|
flowCoordinator.start()
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ class ChatsFlowCoordinatorTests: XCTestCase {
|
|||||||
var chatsFlowCoordinator: ChatsFlowCoordinator!
|
var chatsFlowCoordinator: ChatsFlowCoordinator!
|
||||||
var splitCoordinator: NavigationSplitCoordinator!
|
var splitCoordinator: NavigationSplitCoordinator!
|
||||||
var notificationManager: NotificationManagerMock!
|
var notificationManager: NotificationManagerMock!
|
||||||
|
let stateMachineFactory = PublishedStateMachineFactory()
|
||||||
|
|
||||||
var cancellables = Set<AnyCancellable>()
|
var cancellables = Set<AnyCancellable>()
|
||||||
|
|
||||||
@@ -33,6 +34,7 @@ class ChatsFlowCoordinatorTests: XCTestCase {
|
|||||||
notificationManager = NotificationManagerMock()
|
notificationManager = NotificationManagerMock()
|
||||||
|
|
||||||
chatsFlowCoordinator = ChatsFlowCoordinator(userSession: UserSessionMock(.init(clientProxy: clientProxy)),
|
chatsFlowCoordinator = ChatsFlowCoordinator(userSession: UserSessionMock(.init(clientProxy: clientProxy)),
|
||||||
|
isNewLogin: false,
|
||||||
navigationSplitCoordinator: splitCoordinator,
|
navigationSplitCoordinator: splitCoordinator,
|
||||||
appLockService: AppLockServiceMock(),
|
appLockService: AppLockServiceMock(),
|
||||||
bugReportService: BugReportServiceMock(.init()),
|
bugReportService: BugReportServiceMock(.init()),
|
||||||
@@ -43,9 +45,9 @@ class ChatsFlowCoordinatorTests: XCTestCase {
|
|||||||
appHooks: AppHooks(),
|
appHooks: AppHooks(),
|
||||||
analytics: ServiceLocator.shared.analytics,
|
analytics: ServiceLocator.shared.analytics,
|
||||||
notificationManager: notificationManager,
|
notificationManager: notificationManager,
|
||||||
isNewLogin: false)
|
stateMachineFactory: stateMachineFactory)
|
||||||
|
|
||||||
let deferred = deferFulfillment(chatsFlowCoordinator.statePublisher) { $0 == .roomList(roomListSelectedRoomID: nil) }
|
let deferred = deferFulfillment(stateMachineFactory.chatsFlowStatePublisher) { $0 == .roomList(roomListSelectedRoomID: nil) }
|
||||||
chatsFlowCoordinator.start()
|
chatsFlowCoordinator.start()
|
||||||
try await deferred.fulfill()
|
try await deferred.fulfill()
|
||||||
}
|
}
|
||||||
@@ -127,7 +129,7 @@ class ChatsFlowCoordinatorTests: XCTestCase {
|
|||||||
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomDetailsScreenCoordinator)
|
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomDetailsScreenCoordinator)
|
||||||
XCTAssertNotNil(detailCoordinator)
|
XCTAssertNotNil(detailCoordinator)
|
||||||
|
|
||||||
let unexpectedFulfillment = deferFailure(chatsFlowCoordinator.statePublisher, timeout: 1) { _ in true }
|
let unexpectedFulfillment = deferFailure(stateMachineFactory.chatsFlowStatePublisher, timeout: 1) { _ in true }
|
||||||
chatsFlowCoordinator.handleAppRoute(.roomDetails(roomID: "1"), animated: true)
|
chatsFlowCoordinator.handleAppRoute(.roomDetails(roomID: "1"), animated: true)
|
||||||
try await unexpectedFulfillment.fulfill()
|
try await unexpectedFulfillment.fulfill()
|
||||||
|
|
||||||
@@ -150,7 +152,7 @@ class ChatsFlowCoordinatorTests: XCTestCase {
|
|||||||
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
|
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
|
||||||
XCTAssertNotNil(detailCoordinator)
|
XCTAssertNotNil(detailCoordinator)
|
||||||
|
|
||||||
let unexpectedFulfillment = deferFailure(chatsFlowCoordinator.statePublisher, timeout: 1) { _ in true }
|
let unexpectedFulfillment = deferFailure(stateMachineFactory.chatsFlowStatePublisher, timeout: 1) { _ in true }
|
||||||
chatsFlowCoordinator.handleAppRoute(.roomDetails(roomID: "1"), animated: true)
|
chatsFlowCoordinator.handleAppRoute(.roomDetails(roomID: "1"), animated: true)
|
||||||
try await unexpectedFulfillment.fulfill()
|
try await unexpectedFulfillment.fulfill()
|
||||||
|
|
||||||
@@ -241,17 +243,6 @@ class ChatsFlowCoordinatorTests: XCTestCase {
|
|||||||
"A new timeline should be created for the same room ID, so that the screen isn't stale while loading.")
|
"A new timeline should be created for the same room ID, so that the screen isn't stale while loading.")
|
||||||
}
|
}
|
||||||
|
|
||||||
func testShareMediaRouteWithoutRoom() async throws {
|
|
||||||
try await process(route: .settings, expectedState: .settingsScreen(roomListSelectedRoomID: nil))
|
|
||||||
XCTAssertTrue((splitCoordinator?.sheetCoordinator as? NavigationStackCoordinator)?.rootCoordinator is SettingsScreenCoordinator)
|
|
||||||
|
|
||||||
let sharePayload: ShareExtensionPayload = .mediaFiles(roomID: nil, mediaFiles: [.init(url: .picturesDirectory, suggestedName: nil)])
|
|
||||||
try await process(route: .share(sharePayload),
|
|
||||||
expectedState: .shareExtensionRoomList(sharePayload: sharePayload))
|
|
||||||
|
|
||||||
XCTAssertTrue((splitCoordinator?.sheetCoordinator as? NavigationStackCoordinator)?.rootCoordinator is RoomSelectionScreenCoordinator)
|
|
||||||
}
|
|
||||||
|
|
||||||
func testShareMediaRouteWithRoom() async throws {
|
func testShareMediaRouteWithRoom() async throws {
|
||||||
try await process(route: .event(eventID: "1", roomID: "1", via: []), expectedState: .roomList(roomListSelectedRoomID: "1"))
|
try await process(route: .event(eventID: "1", roomID: "1", via: []), expectedState: .roomList(roomListSelectedRoomID: "1"))
|
||||||
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
|
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
|
||||||
@@ -264,17 +255,6 @@ class ChatsFlowCoordinatorTests: XCTestCase {
|
|||||||
XCTAssertTrue((splitCoordinator?.sheetCoordinator as? NavigationStackCoordinator)?.rootCoordinator is MediaUploadPreviewScreenCoordinator)
|
XCTAssertTrue((splitCoordinator?.sheetCoordinator as? NavigationStackCoordinator)?.rootCoordinator is MediaUploadPreviewScreenCoordinator)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testShareTextRouteWithoutRoom() async throws {
|
|
||||||
try await process(route: .settings, expectedState: .settingsScreen(roomListSelectedRoomID: nil))
|
|
||||||
XCTAssertTrue((splitCoordinator?.sheetCoordinator as? NavigationStackCoordinator)?.rootCoordinator is SettingsScreenCoordinator)
|
|
||||||
|
|
||||||
let sharePayload: ShareExtensionPayload = .text(roomID: nil, text: "Important Text")
|
|
||||||
try await process(route: .share(sharePayload),
|
|
||||||
expectedState: .shareExtensionRoomList(sharePayload: sharePayload))
|
|
||||||
|
|
||||||
XCTAssertTrue((splitCoordinator?.sheetCoordinator as? NavigationStackCoordinator)?.rootCoordinator is RoomSelectionScreenCoordinator)
|
|
||||||
}
|
|
||||||
|
|
||||||
func testShareTextRouteWithRoom() async throws {
|
func testShareTextRouteWithRoom() async throws {
|
||||||
try await process(route: .event(eventID: "1", roomID: "1", via: []), expectedState: .roomList(roomListSelectedRoomID: "1"))
|
try await process(route: .event(eventID: "1", roomID: "1", via: []), expectedState: .roomList(roomListSelectedRoomID: "1"))
|
||||||
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
|
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
|
||||||
@@ -291,7 +271,7 @@ class ChatsFlowCoordinatorTests: XCTestCase {
|
|||||||
|
|
||||||
private func process(route: AppRoute, expectedState: ChatsFlowCoordinatorStateMachine.State) async throws {
|
private func process(route: AppRoute, expectedState: ChatsFlowCoordinatorStateMachine.State) async throws {
|
||||||
// Sometimes the state machine's state changes before the coordinators have updated the stack.
|
// Sometimes the state machine's state changes before the coordinators have updated the stack.
|
||||||
let delayedPublisher = chatsFlowCoordinator.statePublisher.delay(for: .milliseconds(100), scheduler: DispatchQueue.main)
|
let delayedPublisher = stateMachineFactory.chatsFlowStatePublisher.delay(for: .milliseconds(100), scheduler: DispatchQueue.main)
|
||||||
|
|
||||||
let deferred = deferFulfillment(delayedPublisher) { $0 == expectedState }
|
let deferred = deferFulfillment(delayedPublisher) { $0 == expectedState }
|
||||||
chatsFlowCoordinator.handleAppRoute(route, animated: true)
|
chatsFlowCoordinator.handleAppRoute(route, animated: true)
|
||||||
|
|||||||
@@ -0,0 +1,152 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2023, 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 XCTest
|
||||||
|
|
||||||
|
import Combine
|
||||||
|
@testable import ElementX
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
class UserSessionFlowCoordinatorTests: XCTestCase {
|
||||||
|
var clientProxy: ClientProxyMock!
|
||||||
|
var timelineControllerFactory: TimelineControllerFactoryMock!
|
||||||
|
var userSessionFlowCoordinator: UserSessionFlowCoordinator!
|
||||||
|
var rootCoordinator: NavigationRootCoordinator!
|
||||||
|
var notificationManager: NotificationManagerMock!
|
||||||
|
let stateMachineFactory = PublishedStateMachineFactory()
|
||||||
|
|
||||||
|
var cancellables = Set<AnyCancellable>()
|
||||||
|
|
||||||
|
var tabCoordinator: NavigationTabCoordinator<UserSessionFlowCoordinator.HomeTab>? { rootCoordinator?.rootCoordinator as? NavigationTabCoordinator }
|
||||||
|
var chatsSplitCoordinator: NavigationSplitCoordinator? { tabCoordinator?.tabCoordinators.first as? NavigationSplitCoordinator }
|
||||||
|
var detailCoordinator: CoordinatorProtocol? { chatsSplitCoordinator?.detailCoordinator }
|
||||||
|
var detailNavigationStack: NavigationStackCoordinator? { detailCoordinator as? NavigationStackCoordinator }
|
||||||
|
|
||||||
|
override func setUp() async throws {
|
||||||
|
cancellables.removeAll()
|
||||||
|
clientProxy = ClientProxyMock(.init(userID: "hi@bob", roomSummaryProvider: RoomSummaryProviderMock(.init(state: .loaded(.mockRooms)))))
|
||||||
|
timelineControllerFactory = TimelineControllerFactoryMock(.init())
|
||||||
|
|
||||||
|
rootCoordinator = NavigationRootCoordinator()
|
||||||
|
|
||||||
|
notificationManager = NotificationManagerMock()
|
||||||
|
|
||||||
|
userSessionFlowCoordinator = UserSessionFlowCoordinator(userSession: UserSessionMock(.init(clientProxy: clientProxy)),
|
||||||
|
isNewLogin: false,
|
||||||
|
navigationRootCoordinator: rootCoordinator,
|
||||||
|
appLockService: AppLockServiceMock(),
|
||||||
|
bugReportService: BugReportServiceMock(.init()),
|
||||||
|
elementCallService: ElementCallServiceMock(.init()),
|
||||||
|
timelineControllerFactory: timelineControllerFactory,
|
||||||
|
appMediator: AppMediatorMock.default,
|
||||||
|
appSettings: ServiceLocator.shared.settings,
|
||||||
|
appHooks: AppHooks(),
|
||||||
|
analytics: ServiceLocator.shared.analytics,
|
||||||
|
notificationManager: notificationManager,
|
||||||
|
stateMachineFactory: stateMachineFactory)
|
||||||
|
|
||||||
|
userSessionFlowCoordinator.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
func testInitialState() async throws {
|
||||||
|
XCTAssertNotNil(chatsSplitCoordinator)
|
||||||
|
XCTAssertNil(detailCoordinator)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSettingsPresentation() async throws {
|
||||||
|
try await process(route: .settings, expectedUserSessionState: .settingsScreen)
|
||||||
|
XCTAssertTrue((tabCoordinator?.sheetCoordinator as? NavigationStackCoordinator)?.rootCoordinator is SettingsScreenCoordinator)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testRoomPresentation() async throws {
|
||||||
|
try await process(route: .room(roomID: "1", via: []), expectedChatsState: .roomList(roomListSelectedRoomID: "1"))
|
||||||
|
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
|
||||||
|
XCTAssertNotNil(detailCoordinator)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testShareMediaRouteWithoutRoom() async throws {
|
||||||
|
try await process(route: .settings, expectedUserSessionState: .settingsScreen)
|
||||||
|
XCTAssertTrue((tabCoordinator?.sheetCoordinator as? NavigationStackCoordinator)?.rootCoordinator is SettingsScreenCoordinator)
|
||||||
|
XCTAssertNil(chatsSplitCoordinator?.sheetCoordinator)
|
||||||
|
|
||||||
|
let sharePayload: ShareExtensionPayload = .mediaFiles(roomID: nil, mediaFiles: [.init(url: .picturesDirectory, suggestedName: nil)])
|
||||||
|
try await process(route: .share(sharePayload),
|
||||||
|
expectedUserSessionState: .tabBar,
|
||||||
|
expectedChatsState: .shareExtensionRoomList(sharePayload: sharePayload))
|
||||||
|
XCTAssertNil(tabCoordinator?.sheetCoordinator)
|
||||||
|
XCTAssertTrue((chatsSplitCoordinator?.sheetCoordinator as? NavigationStackCoordinator)?.rootCoordinator is RoomSelectionScreenCoordinator)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testShareMediaRouteWithRoom() async throws {
|
||||||
|
try await process(route: .event(eventID: "1", roomID: "1", via: []), expectedChatsState: .roomList(roomListSelectedRoomID: "1"))
|
||||||
|
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
|
||||||
|
XCTAssertNil(tabCoordinator?.sheetCoordinator)
|
||||||
|
XCTAssertNil(chatsSplitCoordinator?.sheetCoordinator)
|
||||||
|
|
||||||
|
let sharePayload: ShareExtensionPayload = .mediaFiles(roomID: "2", mediaFiles: [.init(url: .picturesDirectory, suggestedName: nil)])
|
||||||
|
try await process(route: .share(sharePayload),
|
||||||
|
expectedChatsState: .roomList(roomListSelectedRoomID: "2"))
|
||||||
|
|
||||||
|
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
|
||||||
|
XCTAssertNil(tabCoordinator?.sheetCoordinator)
|
||||||
|
XCTAssertTrue((chatsSplitCoordinator?.sheetCoordinator as? NavigationStackCoordinator)?.rootCoordinator is MediaUploadPreviewScreenCoordinator)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testShareTextRouteWithoutRoom() async throws {
|
||||||
|
try await process(route: .settings, expectedUserSessionState: .settingsScreen)
|
||||||
|
XCTAssertTrue((tabCoordinator?.sheetCoordinator as? NavigationStackCoordinator)?.rootCoordinator is SettingsScreenCoordinator)
|
||||||
|
XCTAssertNil(chatsSplitCoordinator?.sheetCoordinator)
|
||||||
|
|
||||||
|
let sharePayload: ShareExtensionPayload = .text(roomID: nil, text: "Important Text")
|
||||||
|
try await process(route: .share(sharePayload),
|
||||||
|
expectedUserSessionState: .tabBar,
|
||||||
|
expectedChatsState: .shareExtensionRoomList(sharePayload: sharePayload))
|
||||||
|
XCTAssertNil(tabCoordinator?.sheetCoordinator)
|
||||||
|
XCTAssertTrue((chatsSplitCoordinator?.sheetCoordinator as? NavigationStackCoordinator)?.rootCoordinator is RoomSelectionScreenCoordinator)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testShareTextRouteWithRoom() async throws {
|
||||||
|
try await process(route: .event(eventID: "1", roomID: "1", via: []), expectedChatsState: .roomList(roomListSelectedRoomID: "1"))
|
||||||
|
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
|
||||||
|
XCTAssertNil(tabCoordinator?.sheetCoordinator)
|
||||||
|
XCTAssertNil(chatsSplitCoordinator?.sheetCoordinator)
|
||||||
|
|
||||||
|
let sharePayload: ShareExtensionPayload = .text(roomID: "2", text: "Important text")
|
||||||
|
try await process(route: .share(sharePayload),
|
||||||
|
expectedChatsState: .roomList(roomListSelectedRoomID: "2"))
|
||||||
|
|
||||||
|
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
|
||||||
|
XCTAssertNil(tabCoordinator?.sheetCoordinator)
|
||||||
|
XCTAssertNil(chatsSplitCoordinator?.sheetCoordinator, "The media upload sheet shouldn't be shown when sharing text.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Helpers
|
||||||
|
|
||||||
|
private func process(route: AppRoute,
|
||||||
|
expectedUserSessionState: UserSessionFlowCoordinator.State? = nil,
|
||||||
|
expectedChatsState: ChatsFlowCoordinatorStateMachine.State? = nil) async throws {
|
||||||
|
let deferredUserSession: DeferredFulfillment? = if let expectedUserSessionState {
|
||||||
|
deferFulfillment(stateMachineFactory.userSessionFlowStatePublisher.delay(for: .milliseconds(100), scheduler: DispatchQueue.main)) {
|
||||||
|
$0 == expectedUserSessionState
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let deferredChatsState: DeferredFulfillment? = if let expectedChatsState {
|
||||||
|
deferFulfillment(stateMachineFactory.chatsFlowStatePublisher.delay(for: .milliseconds(100), scheduler: DispatchQueue.main)) {
|
||||||
|
$0 == expectedChatsState
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
nil
|
||||||
|
}
|
||||||
|
|
||||||
|
userSessionFlowCoordinator.handleAppRoute(route, animated: true)
|
||||||
|
try await deferredUserSession?.fulfill()
|
||||||
|
try await deferredChatsState?.fulfill()
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user