mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-06 04:18:31 +00:00
Hard code the room flow to a single room proxy. (#2599)
* Don't give the room flow a split coordinator any more.
This commit is contained in:
@@ -596,6 +596,7 @@
|
|||||||
9095B9E40DB5CF8BA26CE0D8 /* ReactionsSummaryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 153726EDCE1ACBB3D466A916 /* ReactionsSummaryView.swift */; };
|
9095B9E40DB5CF8BA26CE0D8 /* ReactionsSummaryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 153726EDCE1ACBB3D466A916 /* ReactionsSummaryView.swift */; };
|
||||||
90DF83A6A347F7EE7EDE89EE /* AttributedStringBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF25E364AE85090A70AE4644 /* AttributedStringBuilderTests.swift */; };
|
90DF83A6A347F7EE7EDE89EE /* AttributedStringBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF25E364AE85090A70AE4644 /* AttributedStringBuilderTests.swift */; };
|
||||||
90EB25D13AE6EEF034BDE9D2 /* Assets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71D52BAA5BADB06E5E8C295D /* Assets.swift */; };
|
90EB25D13AE6EEF034BDE9D2 /* Assets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71D52BAA5BADB06E5E8C295D /* Assets.swift */; };
|
||||||
|
912B88362BADD6EA00CD00F6 /* UserSessionFlowCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 912B88352BADD6EA00CD00F6 /* UserSessionFlowCoordinatorTests.swift */; };
|
||||||
915B4CDAF220D9AEB4047D45 /* PollInteractionHandlerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 259E5B05BDE6E20C26CF11B4 /* PollInteractionHandlerProtocol.swift */; };
|
915B4CDAF220D9AEB4047D45 /* PollInteractionHandlerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 259E5B05BDE6E20C26CF11B4 /* PollInteractionHandlerProtocol.swift */; };
|
||||||
91ABC91758A6E4A5FAA2E9C4 /* ReadReceipt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314F1C79850BE46E8ABEAFCB /* ReadReceipt.swift */; };
|
91ABC91758A6E4A5FAA2E9C4 /* ReadReceipt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314F1C79850BE46E8ABEAFCB /* ReadReceipt.swift */; };
|
||||||
91C6AC0E9D2B9C0C76CC6AD4 /* RoomDirectorySearchScreenScreenModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3984C93B8E9B10C92DADF9EE /* RoomDirectorySearchScreenScreenModelProtocol.swift */; };
|
91C6AC0E9D2B9C0C76CC6AD4 /* RoomDirectorySearchScreenScreenModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3984C93B8E9B10C92DADF9EE /* RoomDirectorySearchScreenScreenModelProtocol.swift */; };
|
||||||
@@ -1656,6 +1657,7 @@
|
|||||||
90A55430639712CFACA34F43 /* TextRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextRoomTimelineItem.swift; sourceTree = "<group>"; };
|
90A55430639712CFACA34F43 /* TextRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextRoomTimelineItem.swift; sourceTree = "<group>"; };
|
||||||
90DFF217B3D9D0941283278C /* RoomRolesAndPermissionsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomRolesAndPermissionsScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
90DFF217B3D9D0941283278C /* RoomRolesAndPermissionsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomRolesAndPermissionsScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||||
90F2F8998E5632668B0AD848 /* RoomTimelineItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemView.swift; sourceTree = "<group>"; };
|
90F2F8998E5632668B0AD848 /* RoomTimelineItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemView.swift; sourceTree = "<group>"; };
|
||||||
|
912B88352BADD6EA00CD00F6 /* UserSessionFlowCoordinatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionFlowCoordinatorTests.swift; sourceTree = "<group>"; };
|
||||||
913C8E13B8B602C7B6C0C4AE /* PillTextAttachmentData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PillTextAttachmentData.swift; sourceTree = "<group>"; };
|
913C8E13B8B602C7B6C0C4AE /* PillTextAttachmentData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PillTextAttachmentData.swift; sourceTree = "<group>"; };
|
||||||
91868EB98818044E6FEBE532 /* NotificationPermissionsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationPermissionsScreenCoordinator.swift; sourceTree = "<group>"; };
|
91868EB98818044E6FEBE532 /* NotificationPermissionsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationPermissionsScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||||
91CF6F7D08228D16BA69B63B /* zh-Hant-TW */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant-TW"; path = "zh-Hant-TW.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
91CF6F7D08228D16BA69B63B /* zh-Hant-TW */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant-TW"; path = "zh-Hant-TW.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
||||||
@@ -3535,6 +3537,7 @@
|
|||||||
7583EAC171059A86B767209F /* MediaProvider */,
|
7583EAC171059A86B767209F /* MediaProvider */,
|
||||||
7DBC911559934065993A5FF4 /* NotificationManager */,
|
7DBC911559934065993A5FF4 /* NotificationManager */,
|
||||||
1C62F5382CC9D9F7DCEC344A /* UserDiscoveryService */,
|
1C62F5382CC9D9F7DCEC344A /* UserDiscoveryService */,
|
||||||
|
912B88352BADD6EA00CD00F6 /* UserSessionFlowCoordinatorTests.swift */,
|
||||||
);
|
);
|
||||||
path = Sources;
|
path = Sources;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -5624,6 +5627,7 @@
|
|||||||
50381244BA280451771BE3ED /* PINTextFieldTests.swift in Sources */,
|
50381244BA280451771BE3ED /* PINTextFieldTests.swift in Sources */,
|
||||||
27E9263DA75E266690A37EB1 /* PermalinkBuilderTests.swift in Sources */,
|
27E9263DA75E266690A37EB1 /* PermalinkBuilderTests.swift in Sources */,
|
||||||
3982E60F9C126437D5E488A3 /* PillContextTests.swift in Sources */,
|
3982E60F9C126437D5E488A3 /* PillContextTests.swift in Sources */,
|
||||||
|
912B88362BADD6EA00CD00F6 /* UserSessionFlowCoordinatorTests.swift in Sources */,
|
||||||
FF7E8ECC8E7E1D1851517536 /* PollFormScreenViewModelTests.swift in Sources */,
|
FF7E8ECC8E7E1D1851517536 /* PollFormScreenViewModelTests.swift in Sources */,
|
||||||
D415764645491F10344FC6AC /* Publisher.swift in Sources */,
|
D415764645491F10344FC6AC /* Publisher.swift in Sources */,
|
||||||
D53B80EF02C1062E68659EDD /* ReportContentViewModelTests.swift in Sources */,
|
D53B80EF02C1062E68659EDD /* ReportContentViewModelTests.swift in Sources */,
|
||||||
|
|||||||
@@ -54,3 +54,6 @@ protocol WindowManagerProtocol: AnyObject, OrientationManagerProtocol {
|
|||||||
|
|
||||||
func hideGlobalSearch()
|
func hideGlobalSearch()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sourcery: AutoMockable
|
||||||
|
extension WindowManagerProtocol { }
|
||||||
|
|||||||
@@ -20,35 +20,35 @@ import SwiftState
|
|||||||
import UserNotifications
|
import UserNotifications
|
||||||
|
|
||||||
enum RoomFlowCoordinatorAction: Equatable {
|
enum RoomFlowCoordinatorAction: Equatable {
|
||||||
case presentedRoom(String)
|
case presentRoom(roomID: String)
|
||||||
case dismissedRoom
|
|
||||||
case presentCallScreen(roomProxy: RoomProxyProtocol)
|
case presentCallScreen(roomProxy: RoomProxyProtocol)
|
||||||
|
case finished
|
||||||
|
|
||||||
static func == (lhs: RoomFlowCoordinatorAction, rhs: RoomFlowCoordinatorAction) -> Bool {
|
static func == (lhs: RoomFlowCoordinatorAction, rhs: RoomFlowCoordinatorAction) -> Bool {
|
||||||
switch (lhs, rhs) {
|
switch (lhs, rhs) {
|
||||||
case (.presentedRoom(let lhsRoomID), .presentedRoom(let rhsRoomID)):
|
case (.presentRoom(let lhsRoomID), .presentRoom(let rhsRoomID)):
|
||||||
return lhsRoomID == rhsRoomID
|
lhsRoomID == rhsRoomID
|
||||||
case (.dismissedRoom, .dismissedRoom):
|
|
||||||
return true
|
|
||||||
case (.presentCallScreen(let lhsRoomProxy), .presentCallScreen(let rhsRoomProxy)):
|
case (.presentCallScreen(let lhsRoomProxy), .presentCallScreen(let rhsRoomProxy)):
|
||||||
return lhsRoomProxy.id == rhsRoomProxy.id
|
lhsRoomProxy.id == rhsRoomProxy.id
|
||||||
|
case (.finished, .finished):
|
||||||
|
true
|
||||||
default:
|
default:
|
||||||
return false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// swiftlint:disable file_length
|
// swiftlint:disable file_length
|
||||||
class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
||||||
private let orientationManager: OrientationManagerProtocol
|
private let roomProxy: RoomProxyProtocol
|
||||||
private let userSession: UserSessionProtocol
|
private let userSession: UserSessionProtocol
|
||||||
private let roomTimelineControllerFactory: RoomTimelineControllerFactoryProtocol
|
private let roomTimelineControllerFactory: RoomTimelineControllerFactoryProtocol
|
||||||
private let navigationStackCoordinator: NavigationStackCoordinator
|
private let navigationStackCoordinator: NavigationStackCoordinator
|
||||||
private let navigationSplitCoordinator: NavigationSplitCoordinator
|
|
||||||
private let emojiProvider: EmojiProviderProtocol
|
private let emojiProvider: EmojiProviderProtocol
|
||||||
private let appSettings: AppSettings
|
private let appSettings: AppSettings
|
||||||
private let analytics: AnalyticsService
|
private let analytics: AnalyticsService
|
||||||
private let userIndicatorController: UserIndicatorControllerProtocol
|
private let userIndicatorController: UserIndicatorControllerProtocol
|
||||||
|
private let orientationManager: OrientationManagerProtocol
|
||||||
|
|
||||||
// periphery:ignore - used to avoid deallocation
|
// periphery:ignore - used to avoid deallocation
|
||||||
private var rolesAndPermissionsFlowCoordinator: RoomRolesAndPermissionsFlowCoordinator?
|
private var rolesAndPermissionsFlowCoordinator: RoomRolesAndPermissionsFlowCoordinator?
|
||||||
@@ -62,34 +62,35 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
actionsSubject.eraseToAnyPublisher()
|
actionsSubject.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
private var roomProxy: RoomProxyProtocol? {
|
|
||||||
didSet {
|
|
||||||
oldValue?.unsubscribeFromUpdates()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var timelineController: RoomTimelineControllerProtocol?
|
private var timelineController: RoomTimelineControllerProtocol?
|
||||||
|
|
||||||
init(userSession: UserSessionProtocol,
|
init(roomProxy: RoomProxyProtocol,
|
||||||
|
userSession: UserSessionProtocol,
|
||||||
roomTimelineControllerFactory: RoomTimelineControllerFactoryProtocol,
|
roomTimelineControllerFactory: RoomTimelineControllerFactoryProtocol,
|
||||||
navigationStackCoordinator: NavigationStackCoordinator,
|
navigationStackCoordinator: NavigationStackCoordinator,
|
||||||
navigationSplitCoordinator: NavigationSplitCoordinator,
|
|
||||||
emojiProvider: EmojiProviderProtocol,
|
emojiProvider: EmojiProviderProtocol,
|
||||||
appSettings: AppSettings,
|
appSettings: AppSettings,
|
||||||
analytics: AnalyticsService,
|
analytics: AnalyticsService,
|
||||||
userIndicatorController: UserIndicatorControllerProtocol,
|
userIndicatorController: UserIndicatorControllerProtocol,
|
||||||
orientationManager: OrientationManagerProtocol) {
|
orientationManager: OrientationManagerProtocol) async {
|
||||||
|
self.roomProxy = roomProxy
|
||||||
self.userSession = userSession
|
self.userSession = userSession
|
||||||
self.roomTimelineControllerFactory = roomTimelineControllerFactory
|
self.roomTimelineControllerFactory = roomTimelineControllerFactory
|
||||||
self.navigationStackCoordinator = navigationStackCoordinator
|
self.navigationStackCoordinator = navigationStackCoordinator
|
||||||
self.navigationSplitCoordinator = navigationSplitCoordinator
|
|
||||||
self.emojiProvider = emojiProvider
|
self.emojiProvider = emojiProvider
|
||||||
self.appSettings = appSettings
|
self.appSettings = appSettings
|
||||||
self.analytics = analytics
|
self.analytics = analytics
|
||||||
self.userIndicatorController = userIndicatorController
|
self.userIndicatorController = userIndicatorController
|
||||||
self.orientationManager = orientationManager
|
self.orientationManager = orientationManager
|
||||||
|
|
||||||
|
// The SDK needs to handle multiple subscription calls before we can start adding child room flows.
|
||||||
|
let subscriptionTask = Task { await roomProxy.subscribeForUpdates() }
|
||||||
|
|
||||||
setupStateMachine()
|
setupStateMachine()
|
||||||
|
|
||||||
|
analytics.signpost.beginRoomFlow(roomProxy.id)
|
||||||
|
|
||||||
|
_ = await subscriptionTask.result
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - FlowCoordinatorProtocol
|
// MARK: - FlowCoordinatorProtocol
|
||||||
@@ -101,18 +102,15 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
func handleAppRoute(_ appRoute: AppRoute, animated: Bool) {
|
func handleAppRoute(_ appRoute: AppRoute, animated: Bool) {
|
||||||
switch appRoute {
|
switch appRoute {
|
||||||
case .room(let roomID):
|
case .room(let roomID):
|
||||||
if case .room(let identifier) = stateMachine.state,
|
guard roomID == roomProxy.id else { fatalError("Navigation route doesn't belong to this room flow.") }
|
||||||
roomID == identifier {
|
stateMachine.tryEvent(.presentRoom, userInfo: EventUserInfo(animated: animated))
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
stateMachine.tryEvent(.presentRoom(roomID: roomID), userInfo: EventUserInfo(animated: animated))
|
|
||||||
case .roomDetails(let roomID):
|
case .roomDetails(let roomID):
|
||||||
stateMachine.tryEvent(.presentRoomDetails(roomID: roomID), userInfo: EventUserInfo(animated: animated))
|
guard roomID == roomProxy.id else { fatalError("Navigation route doesn't belong to this room flow.") }
|
||||||
|
stateMachine.tryEvent(.presentRoomDetails, userInfo: EventUserInfo(animated: animated))
|
||||||
case .roomList:
|
case .roomList:
|
||||||
stateMachine.tryEvent(.dismissRoom, userInfo: EventUserInfo(animated: animated))
|
stateMachine.tryEvent(.dismissRoom, userInfo: EventUserInfo(animated: animated))
|
||||||
case .roomMemberDetails(let userID):
|
case .roomMemberDetails(let userID):
|
||||||
stateMachine.tryEvent(.presentRoomMemberDetails(userID: userID))
|
stateMachine.tryEvent(.presentRoomMemberDetails(userID: userID), userInfo: EventUserInfo(animated: animated))
|
||||||
case .genericCallLink, .oidcCallback, .settings, .chatBackupSettings:
|
case .genericCallLink, .oidcCallback, .settings, .chatBackupSettings:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -131,107 +129,105 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
private func setupStateMachine() {
|
private func setupStateMachine() {
|
||||||
stateMachine.addRouteMapping { event, fromState, _ in
|
stateMachine.addRouteMapping { event, fromState, _ in
|
||||||
switch (fromState, event) {
|
switch (fromState, event) {
|
||||||
case (_, .presentRoom(let roomID)):
|
case (_, .presentRoom):
|
||||||
return .room(roomID: roomID)
|
return .room
|
||||||
case (.room, .dismissRoom):
|
case (.room, .dismissRoom):
|
||||||
return .initial
|
return .complete
|
||||||
|
|
||||||
case (.initial, .presentRoomDetails(let roomID)):
|
case (.initial, .presentRoomDetails):
|
||||||
return .roomDetails(roomID: roomID, isRoot: true)
|
return .roomDetails(isRoot: true)
|
||||||
case (.room(let currentRoomID), .presentRoomDetails(let roomID)):
|
case (.room, .presentRoomDetails):
|
||||||
return .roomDetails(roomID: roomID, isRoot: roomID != currentRoomID)
|
return .roomDetails(isRoot: false)
|
||||||
case (.roomDetails(let currentRoomID, _), .presentRoomDetails(let roomID)):
|
case (.roomDetails, .dismissRoomDetails):
|
||||||
return .roomDetails(roomID: roomID, isRoot: roomID != currentRoomID)
|
return .room
|
||||||
case (.roomDetails(let roomID, _), .dismissRoomDetails):
|
|
||||||
return .room(roomID: roomID)
|
|
||||||
case (.roomDetails, .dismissRoom):
|
case (.roomDetails, .dismissRoom):
|
||||||
return .initial
|
return .complete
|
||||||
|
|
||||||
case (.roomDetails(let roomID, _), .presentRoomDetailsEditScreen):
|
case (.roomDetails, .presentRoomDetailsEditScreen):
|
||||||
return .roomDetailsEditScreen(roomID: roomID)
|
return .roomDetailsEditScreen
|
||||||
case (.roomDetailsEditScreen(let roomID), .dismissRoomDetailsEditScreen):
|
case (.roomDetailsEditScreen, .dismissRoomDetailsEditScreen):
|
||||||
return .roomDetails(roomID: roomID, isRoot: false)
|
return .roomDetails(isRoot: false)
|
||||||
|
|
||||||
case (.roomDetails(let roomID, _), .presentNotificationSettingsScreen):
|
case (.roomDetails, .presentNotificationSettingsScreen):
|
||||||
return .notificationSettings(roomID: roomID)
|
return .notificationSettings
|
||||||
case (.notificationSettings(let roomID), .dismissNotificationSettingsScreen):
|
case (.notificationSettings, .dismissNotificationSettingsScreen):
|
||||||
return .roomDetails(roomID: roomID, isRoot: false)
|
return .roomDetails(isRoot: false)
|
||||||
|
|
||||||
case (.notificationSettings(let roomID), .presentGlobalNotificationSettingsScreen):
|
case (.notificationSettings, .presentGlobalNotificationSettingsScreen):
|
||||||
return .globalNotificationSettings(roomID: roomID)
|
return .globalNotificationSettings
|
||||||
case (.globalNotificationSettings(let roomID), .dismissGlobalNotificationSettingsScreen):
|
case (.globalNotificationSettings, .dismissGlobalNotificationSettingsScreen):
|
||||||
return .notificationSettings(roomID: roomID)
|
return .notificationSettings
|
||||||
|
|
||||||
case (.roomDetails(let roomID, _), .presentRoomMembersList):
|
case (.roomDetails, .presentRoomMembersList):
|
||||||
return .roomMembersList(roomID: roomID)
|
return .roomMembersList
|
||||||
case (.roomMembersList(let roomID), .dismissRoomMembersList):
|
case (.roomMembersList, .dismissRoomMembersList):
|
||||||
return .roomDetails(roomID: roomID, isRoot: false)
|
return .roomDetails(isRoot: false)
|
||||||
|
|
||||||
case (.room(let roomID), .presentRoomMemberDetails(userID: let userID)):
|
case (.room, .presentRoomMemberDetails(userID: let userID)):
|
||||||
return .roomMemberDetails(roomID: roomID, userID: userID, fromRoomMembersList: false)
|
return .roomMemberDetails(userID: userID, fromRoomMembersList: false)
|
||||||
case (.roomMembersList(let roomID), .presentRoomMemberDetails(userID: let userID)):
|
case (.roomMembersList, .presentRoomMemberDetails(userID: let userID)):
|
||||||
return .roomMemberDetails(roomID: roomID, userID: userID, fromRoomMembersList: true)
|
return .roomMemberDetails(userID: userID, fromRoomMembersList: true)
|
||||||
case (.roomMemberDetails(let roomID, _, let fromRoomMembersList), .dismissRoomMemberDetails):
|
case (.roomMemberDetails(_, let fromRoomMembersList), .dismissRoomMemberDetails):
|
||||||
return fromRoomMembersList ? .roomMembersList(roomID: roomID) : .room(roomID: roomID)
|
return fromRoomMembersList ? .roomMembersList : .room
|
||||||
|
|
||||||
case (.roomDetails(let roomID, _), .presentInviteUsersScreen):
|
case (.roomDetails, .presentInviteUsersScreen):
|
||||||
return .inviteUsersScreen(roomID: roomID, fromRoomMembersList: false)
|
return .inviteUsersScreen(fromRoomMembersList: false)
|
||||||
case (.roomMembersList(let roomID), .presentInviteUsersScreen):
|
case (.roomMembersList, .presentInviteUsersScreen):
|
||||||
return .inviteUsersScreen(roomID: roomID, fromRoomMembersList: true)
|
return .inviteUsersScreen(fromRoomMembersList: true)
|
||||||
case (.inviteUsersScreen(let roomID, let fromRoomMembersList), .dismissInviteUsersScreen):
|
case (.inviteUsersScreen(let fromRoomMembersList), .dismissInviteUsersScreen):
|
||||||
return fromRoomMembersList ? .roomMembersList(roomID: roomID) : .roomDetails(roomID: roomID, isRoot: false)
|
return fromRoomMembersList ? .roomMembersList : .roomDetails(isRoot: false)
|
||||||
|
|
||||||
case (.room(let roomID), .presentReportContent(let itemID, let senderID)):
|
case (.room, .presentReportContent(let itemID, let senderID)):
|
||||||
return .reportContent(roomID: roomID, itemID: itemID, senderID: senderID)
|
return .reportContent(itemID: itemID, senderID: senderID)
|
||||||
case (.reportContent(let roomID, _, _), .dismissReportContent):
|
case (.reportContent, .dismissReportContent):
|
||||||
return .room(roomID: roomID)
|
return .room
|
||||||
|
|
||||||
case (.room(let roomID), .presentMediaUploadPicker(let source)):
|
case (.room, .presentMediaUploadPicker(let source)):
|
||||||
return .mediaUploadPicker(roomID: roomID, source: source)
|
return .mediaUploadPicker(source: source)
|
||||||
case (.mediaUploadPicker(let roomID, _), .dismissMediaUploadPicker):
|
case (.mediaUploadPicker, .dismissMediaUploadPicker):
|
||||||
return .room(roomID: roomID)
|
return .room
|
||||||
|
|
||||||
case (.mediaUploadPicker(let roomID, _), .presentMediaUploadPreview(let fileURL)):
|
case (.mediaUploadPicker, .presentMediaUploadPreview(let fileURL)):
|
||||||
return .mediaUploadPreview(roomID: roomID, fileURL: fileURL)
|
return .mediaUploadPreview(fileURL: fileURL)
|
||||||
case (.room(let roomID), .presentMediaUploadPreview(let fileURL)):
|
case (.room, .presentMediaUploadPreview(let fileURL)):
|
||||||
return .mediaUploadPreview(roomID: roomID, fileURL: fileURL)
|
return .mediaUploadPreview(fileURL: fileURL)
|
||||||
case (.mediaUploadPreview(let roomID, _), .dismissMediaUploadPreview):
|
case (.mediaUploadPreview, .dismissMediaUploadPreview):
|
||||||
return .room(roomID: roomID)
|
return .room
|
||||||
|
|
||||||
case (.room(let roomID), .presentEmojiPicker(let itemID, let selectedEmoji)):
|
case (.room, .presentEmojiPicker(let itemID, let selectedEmoji)):
|
||||||
return .emojiPicker(roomID: roomID, itemID: itemID, selectedEmojis: selectedEmoji)
|
return .emojiPicker(itemID: itemID, selectedEmojis: selectedEmoji)
|
||||||
case (.emojiPicker(let roomID, _, _), .dismissEmojiPicker):
|
case (.emojiPicker, .dismissEmojiPicker):
|
||||||
return .room(roomID: roomID)
|
return .room
|
||||||
|
|
||||||
case (.room(let roomID), .presentMessageForwarding(let itemID)):
|
case (.room, .presentMessageForwarding(let itemID)):
|
||||||
return .messageForwarding(roomID: roomID, itemID: itemID)
|
return .messageForwarding(itemID: itemID)
|
||||||
case (.messageForwarding(let roomID, _), .dismissMessageForwarding):
|
case (.messageForwarding, .dismissMessageForwarding):
|
||||||
return .room(roomID: roomID)
|
return .room
|
||||||
|
|
||||||
case (.room(let roomID), .presentMapNavigator):
|
case (.room, .presentMapNavigator):
|
||||||
return .mapNavigator(roomID: roomID)
|
return .mapNavigator
|
||||||
case (.mapNavigator(let roomID), .dismissMapNavigator):
|
case (.mapNavigator, .dismissMapNavigator):
|
||||||
return .room(roomID: roomID)
|
return .room
|
||||||
|
|
||||||
case (.room(let roomID), .presentPollForm):
|
case (.room, .presentPollForm):
|
||||||
return .pollForm(roomID: roomID)
|
return .pollForm
|
||||||
case (.pollForm(let roomID), .dismissPollForm):
|
case (.pollForm, .dismissPollForm):
|
||||||
return .room(roomID: roomID)
|
return .room
|
||||||
|
|
||||||
case (.roomDetails(let roomID, _), .presentPollsHistory):
|
case (.roomDetails, .presentPollsHistory):
|
||||||
return .pollsHistory(roomID: roomID)
|
return .pollsHistory
|
||||||
case (.pollsHistory(let roomID), .dismissPollsHistory):
|
case (.pollsHistory, .dismissPollsHistory):
|
||||||
return .roomDetails(roomID: roomID, isRoot: false)
|
return .roomDetails(isRoot: false)
|
||||||
|
|
||||||
case (.pollsHistory(let roomID), .presentPollForm):
|
case (.pollsHistory, .presentPollForm):
|
||||||
return .pollsHistoryForm(roomID: roomID)
|
return .pollsHistoryForm
|
||||||
case (.pollsHistoryForm(let roomID), .dismissPollForm):
|
case (.pollsHistoryForm, .dismissPollForm):
|
||||||
return .pollsHistory(roomID: roomID)
|
return .pollsHistory
|
||||||
|
|
||||||
case (.roomDetails(let roomID, _), .presentRolesAndPermissionsScreen):
|
case (.roomDetails, .presentRolesAndPermissionsScreen):
|
||||||
return .rolesAndPermissions(roomID: roomID)
|
return .rolesAndPermissions
|
||||||
case (.rolesAndPermissions(let roomID), .dismissRolesAndPermissionsScreen):
|
case (.rolesAndPermissions, .dismissRolesAndPermissionsScreen):
|
||||||
return .roomDetails(roomID: roomID, isRoot: false)
|
return .roomDetails(isRoot: false)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
@@ -244,25 +240,19 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
let animated = (context.userInfo as? EventUserInfo)?.animated ?? true
|
let animated = (context.userInfo as? EventUserInfo)?.animated ?? true
|
||||||
|
|
||||||
switch (context.fromState, context.event, context.toState) {
|
switch (context.fromState, context.event, context.toState) {
|
||||||
case (.roomDetails(roomID: let currentRoomID, true), .presentRoom(let roomID), .room) where currentRoomID == roomID:
|
case (_, .presentRoom, .room):
|
||||||
dismissRoom(animated: animated)
|
Task { await self.presentRoom(animated: animated) }
|
||||||
presentRoom(roomID, animated: animated)
|
case (.room, .dismissRoom, .complete):
|
||||||
case (_, .presentRoom(let roomID), .room):
|
dismissFlow(animated: animated)
|
||||||
let destinationRoomProxy = (context.userInfo as? EventUserInfo)?.destinationRoomProxy
|
|
||||||
presentRoom(roomID, animated: animated, destinationRoomProxy: destinationRoomProxy)
|
|
||||||
case (.room, .dismissRoom, .initial):
|
|
||||||
dismissRoom(animated: animated)
|
|
||||||
|
|
||||||
case (.roomDetails(let currentRoomID, _), .presentRoomDetails, .roomDetails(let roomID, _)) where currentRoomID == roomID:
|
case (.initial, .presentRoomDetails, .roomDetails(let isRoot)),
|
||||||
break
|
(.room, .presentRoomDetails, .roomDetails(let isRoot)),
|
||||||
case (.initial, .presentRoomDetails, .roomDetails(let roomID, let isRoot)),
|
(.roomDetails, .presentRoomDetails, .roomDetails(let isRoot)):
|
||||||
(.room, .presentRoomDetails, .roomDetails(let roomID, let isRoot)),
|
Task { await self.presentRoomDetails(isRoot: isRoot, animated: animated) }
|
||||||
(.roomDetails, .presentRoomDetails, .roomDetails(let roomID, let isRoot)):
|
|
||||||
self.presentRoomDetails(roomID: roomID, isRoot: isRoot, animated: animated)
|
|
||||||
case (.roomDetails, .dismissRoomDetails, .room):
|
case (.roomDetails, .dismissRoomDetails, .room):
|
||||||
break
|
break
|
||||||
case (.roomDetails, .dismissRoom, .initial):
|
case (.roomDetails, .dismissRoom, .complete):
|
||||||
dismissRoom(animated: animated)
|
dismissFlow(animated: animated)
|
||||||
|
|
||||||
case (.roomDetails, .presentRoomDetailsEditScreen, .roomDetailsEditScreen):
|
case (.roomDetails, .presentRoomDetailsEditScreen, .roomDetailsEditScreen):
|
||||||
presentRoomDetailsEditScreen()
|
presentRoomDetailsEditScreen()
|
||||||
@@ -284,12 +274,12 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
case (.roomMembersList, .dismissRoomMembersList, .roomDetails):
|
case (.roomMembersList, .dismissRoomMembersList, .roomDetails):
|
||||||
break
|
break
|
||||||
|
|
||||||
case (.room, .presentRoomMemberDetails, .roomMemberDetails(_, let userID, _)):
|
case (.room, .presentRoomMemberDetails, .roomMemberDetails(let userID, _)):
|
||||||
presentRoomMemberDetails(userID: userID)
|
presentRoomMemberDetails(userID: userID)
|
||||||
case (.roomMemberDetails, .dismissRoomMemberDetails, .room):
|
case (.roomMemberDetails, .dismissRoomMemberDetails, .room):
|
||||||
break
|
break
|
||||||
|
|
||||||
case (.roomMembersList, .presentRoomMemberDetails, .roomMemberDetails(_, let userID, _)):
|
case (.roomMembersList, .presentRoomMemberDetails, .roomMemberDetails(let userID, _)):
|
||||||
presentRoomMemberDetails(userID: userID)
|
presentRoomMemberDetails(userID: userID)
|
||||||
case (.roomMemberDetails, .dismissRoomMemberDetails, .roomMembersList):
|
case (.roomMemberDetails, .dismissRoomMemberDetails, .roomMembersList):
|
||||||
break
|
break
|
||||||
@@ -304,24 +294,24 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
case (.inviteUsersScreen, .dismissInviteUsersScreen, .roomMembersList):
|
case (.inviteUsersScreen, .dismissInviteUsersScreen, .roomMembersList):
|
||||||
break
|
break
|
||||||
|
|
||||||
case (.room, .presentReportContent, .reportContent(_, let itemID, let senderID)):
|
case (.room, .presentReportContent, .reportContent(let itemID, let senderID)):
|
||||||
presentReportContent(for: itemID, from: senderID)
|
presentReportContent(for: itemID, from: senderID)
|
||||||
case (.reportContent, .dismissReportContent, .room):
|
case (.reportContent, .dismissReportContent, .room):
|
||||||
break
|
break
|
||||||
|
|
||||||
case (.room, .presentMediaUploadPicker, .mediaUploadPicker(_, let source)):
|
case (.room, .presentMediaUploadPicker, .mediaUploadPicker(let source)):
|
||||||
presentMediaUploadPickerWithSource(source)
|
presentMediaUploadPickerWithSource(source)
|
||||||
case (.mediaUploadPicker, .dismissMediaUploadPicker, .room):
|
case (.mediaUploadPicker, .dismissMediaUploadPicker, .room):
|
||||||
break
|
break
|
||||||
|
|
||||||
case (.mediaUploadPicker, .presentMediaUploadPreview, .mediaUploadPreview(_, let fileURL)):
|
case (.mediaUploadPicker, .presentMediaUploadPreview, .mediaUploadPreview(let fileURL)):
|
||||||
presentMediaUploadPreviewScreen(for: fileURL)
|
presentMediaUploadPreviewScreen(for: fileURL)
|
||||||
case (.room, .presentMediaUploadPreview, .mediaUploadPreview(_, let fileURL)):
|
case (.room, .presentMediaUploadPreview, .mediaUploadPreview(let fileURL)):
|
||||||
presentMediaUploadPreviewScreen(for: fileURL)
|
presentMediaUploadPreviewScreen(for: fileURL)
|
||||||
case (.mediaUploadPreview, .dismissMediaUploadPreview, .room):
|
case (.mediaUploadPreview, .dismissMediaUploadPreview, .room):
|
||||||
break
|
break
|
||||||
|
|
||||||
case (.room, .presentEmojiPicker, .emojiPicker(_, let itemID, let selectedEmoji)):
|
case (.room, .presentEmojiPicker, .emojiPicker(let itemID, let selectedEmoji)):
|
||||||
presentEmojiPicker(for: itemID, selectedEmoji: selectedEmoji)
|
presentEmojiPicker(for: itemID, selectedEmoji: selectedEmoji)
|
||||||
case (.emojiPicker, .dismissEmojiPicker, .room):
|
case (.emojiPicker, .dismissEmojiPicker, .room):
|
||||||
break
|
break
|
||||||
@@ -385,42 +375,11 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
/// - destinationRoomProxy: an optional already build roomProxy for the target room. It is currently used when
|
/// - destinationRoomProxy: an optional already build roomProxy for the target room. It is currently used when
|
||||||
/// forwarding messages so that we can take advantage of the local echo
|
/// forwarding messages so that we can take advantage of the local echo
|
||||||
/// and have the message already there when presenting the room
|
/// and have the message already there when presenting the room
|
||||||
private func presentRoom(_ roomID: String, animated: Bool, destinationRoomProxy: RoomProxyProtocol? = nil) {
|
private func presentRoom(animated: Bool) async {
|
||||||
Task {
|
|
||||||
await asyncPresentRoom(roomID, animated: animated, destinationRoomProxy: destinationRoomProxy)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func asyncPresentRoom(_ roomID: String, animated: Bool, destinationRoomProxy: RoomProxyProtocol? = nil) async {
|
|
||||||
// If any sheets are presented dismiss them, rely on their dismissal callbacks to transition the state machine
|
// If any sheets are presented dismiss them, rely on their dismissal callbacks to transition the state machine
|
||||||
// through the correct states before presenting the room
|
// through the correct states before presenting the room
|
||||||
navigationStackCoordinator.setSheetCoordinator(nil)
|
navigationStackCoordinator.setSheetCoordinator(nil)
|
||||||
|
|
||||||
if let roomProxy, roomProxy.id == roomID {
|
|
||||||
navigationStackCoordinator.popToRoot()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let roomProxy: RoomProxyProtocol
|
|
||||||
|
|
||||||
if let destinationRoomProxy {
|
|
||||||
roomProxy = destinationRoomProxy
|
|
||||||
} else {
|
|
||||||
guard let proxy = await userSession.clientProxy.roomForIdentifier(roomID) else {
|
|
||||||
MXLog.error("Invalid room identifier: \(roomID)")
|
|
||||||
stateMachine.tryEvent(.dismissRoom)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
roomProxy = proxy
|
|
||||||
}
|
|
||||||
|
|
||||||
await roomProxy.subscribeForUpdates()
|
|
||||||
|
|
||||||
actionsSubject.send(.presentedRoom(roomID))
|
|
||||||
|
|
||||||
self.roomProxy = roomProxy
|
|
||||||
|
|
||||||
Task {
|
Task {
|
||||||
// Flag the room as read on entering, the timeline will take care of the read receipts
|
// Flag the room as read on entering, the timeline will take care of the read receipts
|
||||||
await roomProxy.flagAsUnread(false)
|
await roomProxy.flagAsUnread(false)
|
||||||
@@ -456,7 +415,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
|
|
||||||
switch action {
|
switch action {
|
||||||
case .presentRoomDetails:
|
case .presentRoomDetails:
|
||||||
stateMachine.tryEvent(.presentRoomDetails(roomID: roomID))
|
stateMachine.tryEvent(.presentRoomDetails)
|
||||||
case .presentReportContent(let itemID, let senderID):
|
case .presentReportContent(let itemID, let senderID):
|
||||||
stateMachine.tryEvent(.presentReportContent(itemID: itemID, senderID: senderID))
|
stateMachine.tryEvent(.presentReportContent(itemID: itemID, senderID: senderID))
|
||||||
case .presentMediaUploadPicker(let source):
|
case .presentMediaUploadPicker(let source):
|
||||||
@@ -476,10 +435,6 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
case .presentMessageForwarding(let itemID):
|
case .presentMessageForwarding(let itemID):
|
||||||
stateMachine.tryEvent(.presentMessageForwarding(itemID: itemID))
|
stateMachine.tryEvent(.presentMessageForwarding(itemID: itemID))
|
||||||
case .presentCallScreen:
|
case .presentCallScreen:
|
||||||
guard let roomProxy = self.roomProxy else {
|
|
||||||
fatalError()
|
|
||||||
}
|
|
||||||
|
|
||||||
actionsSubject.send(.presentCallScreen(roomProxy: roomProxy))
|
actionsSubject.send(.presentCallScreen(roomProxy: roomProxy))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -489,49 +444,22 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
// Move the state machine to no room selected if the room currently being dismissed
|
// Move the state machine to no room selected if the room currently being dismissed
|
||||||
// is the same as the one selected in the state machine.
|
// is the same as the one selected in the state machine.
|
||||||
// This generally happens when popping the room screen while in a compact layout
|
// This generally happens when popping the room screen while in a compact layout
|
||||||
switch self?.stateMachine.state {
|
self?.stateMachine.tryEvent(.dismissRoom)
|
||||||
case let .room(selectedRoomID) where selectedRoomID == roomID:
|
|
||||||
self?.stateMachine.tryEvent(.dismissRoom)
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if navigationSplitCoordinator.detailCoordinator == nil {
|
|
||||||
navigationSplitCoordinator.setDetailCoordinator(navigationStackCoordinator, animated: animated)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func dismissRoom(animated: Bool) {
|
private func dismissFlow(animated: Bool) {
|
||||||
// DON'T CHANGE THE ORDER IN WHICH POP AND SET ARE DONE, IT CAN CAUSE A CRASH
|
|
||||||
navigationStackCoordinator.popToRoot(animated: false)
|
navigationStackCoordinator.popToRoot(animated: false)
|
||||||
navigationSplitCoordinator.setDetailCoordinator(nil, animated: animated)
|
navigationStackCoordinator.setRootCoordinator(nil, animated: false)
|
||||||
roomProxy = nil
|
|
||||||
|
roomProxy.unsubscribeFromUpdates()
|
||||||
timelineController = nil
|
timelineController = nil
|
||||||
|
|
||||||
actionsSubject.send(.dismissedRoom)
|
actionsSubject.send(.finished)
|
||||||
|
analytics.signpost.endRoomFlow()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func presentRoomDetails(roomID: String, isRoot: Bool, animated: Bool) {
|
private func presentRoomDetails(isRoot: Bool, animated: Bool) async {
|
||||||
Task {
|
|
||||||
await asyncPresentRoomDetails(roomID: roomID, isRoot: isRoot, animated: animated)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func asyncPresentRoomDetails(roomID: String, isRoot: Bool, animated: Bool) async {
|
|
||||||
if isRoot {
|
|
||||||
roomProxy = await userSession.clientProxy.roomForIdentifier(roomID)
|
|
||||||
await roomProxy?.subscribeForUpdates()
|
|
||||||
} else {
|
|
||||||
await asyncPresentRoom(roomID, animated: animated)
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let roomProxy else {
|
|
||||||
MXLog.error("Invalid room identifier: \(roomID)")
|
|
||||||
stateMachine.tryEvent(.dismissRoom)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let params = RoomDetailsScreenCoordinatorParameters(roomProxy: roomProxy,
|
let params = RoomDetailsScreenCoordinatorParameters(roomProxy: roomProxy,
|
||||||
clientProxy: userSession.clientProxy,
|
clientProxy: userSession.clientProxy,
|
||||||
mediaProvider: userSession.mediaProvider,
|
mediaProvider: userSession.mediaProvider,
|
||||||
@@ -547,7 +475,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
|
|
||||||
switch action {
|
switch action {
|
||||||
case .leftRoom:
|
case .leftRoom:
|
||||||
dismissRoom(animated: animated)
|
stateMachine.tryEvent(.dismissRoom)
|
||||||
case .presentRoomMembersList:
|
case .presentRoomMembersList:
|
||||||
stateMachine.tryEvent(.presentRoomMembersList)
|
stateMachine.tryEvent(.presentRoomMembersList)
|
||||||
case .presentRoomDetailsEditScreen:
|
case .presentRoomDetailsEditScreen:
|
||||||
@@ -566,17 +494,8 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
|
|
||||||
if isRoot {
|
if isRoot {
|
||||||
navigationStackCoordinator.setRootCoordinator(coordinator, animated: animated) { [weak self] in
|
navigationStackCoordinator.setRootCoordinator(coordinator, animated: animated) { [weak self] in
|
||||||
guard let self else { return }
|
self?.stateMachine.tryEvent(.dismissRoom) // This seems wrong but it isn't, Maybe the event should be called dismissFlow?
|
||||||
if case .roomDetails(let detailsRoomID, _) = stateMachine.state, detailsRoomID == roomID {
|
|
||||||
stateMachine.tryEvent(.dismissRoom)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if navigationSplitCoordinator.detailCoordinator == nil {
|
|
||||||
navigationSplitCoordinator.setDetailCoordinator(navigationStackCoordinator, animated: animated)
|
|
||||||
}
|
|
||||||
|
|
||||||
actionsSubject.send(.presentedRoom(roomID))
|
|
||||||
} else {
|
} else {
|
||||||
navigationStackCoordinator.push(coordinator, animated: animated) { [weak self] in
|
navigationStackCoordinator.push(coordinator, animated: animated) { [weak self] in
|
||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
@@ -588,10 +507,6 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func presentRoomMembersList() {
|
private func presentRoomMembersList() {
|
||||||
guard let roomProxy else {
|
|
||||||
fatalError()
|
|
||||||
}
|
|
||||||
|
|
||||||
let parameters = RoomMembersListScreenCoordinatorParameters(mediaProvider: userSession.mediaProvider,
|
let parameters = RoomMembersListScreenCoordinatorParameters(mediaProvider: userSession.mediaProvider,
|
||||||
roomProxy: roomProxy,
|
roomProxy: roomProxy,
|
||||||
userIndicatorController: userIndicatorController,
|
userIndicatorController: userIndicatorController,
|
||||||
@@ -618,10 +533,6 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func presentRoomDetailsEditScreen() {
|
private func presentRoomDetailsEditScreen() {
|
||||||
guard let roomProxy else {
|
|
||||||
fatalError()
|
|
||||||
}
|
|
||||||
|
|
||||||
let stackCoordinator = NavigationStackCoordinator()
|
let stackCoordinator = NavigationStackCoordinator()
|
||||||
|
|
||||||
let roomDetailsEditParameters = RoomDetailsEditScreenCoordinatorParameters(roomProxy: roomProxy,
|
let roomDetailsEditParameters = RoomDetailsEditScreenCoordinatorParameters(roomProxy: roomProxy,
|
||||||
@@ -647,7 +558,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func presentReportContent(for itemID: TimelineItemIdentifier, from senderID: String) {
|
private func presentReportContent(for itemID: TimelineItemIdentifier, from senderID: String) {
|
||||||
guard let roomProxy, let eventID = itemID.eventID else {
|
guard let eventID = itemID.eventID else {
|
||||||
fatalError()
|
fatalError()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -707,10 +618,6 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func presentMediaUploadPreviewScreen(for url: URL) {
|
private func presentMediaUploadPreviewScreen(for url: URL) {
|
||||||
guard let roomProxy else {
|
|
||||||
fatalError()
|
|
||||||
}
|
|
||||||
|
|
||||||
let stackCoordinator = NavigationStackCoordinator()
|
let stackCoordinator = NavigationStackCoordinator()
|
||||||
|
|
||||||
let parameters = MediaUploadPreviewScreenCoordinatorParameters(userIndicatorController: userIndicatorController,
|
let parameters = MediaUploadPreviewScreenCoordinatorParameters(userIndicatorController: userIndicatorController,
|
||||||
@@ -776,12 +683,12 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
switch action {
|
switch action {
|
||||||
case .selectedLocation(let geoURI, let isUserLocation):
|
case .selectedLocation(let geoURI, let isUserLocation):
|
||||||
Task {
|
Task {
|
||||||
_ = await self.roomProxy?.timeline.sendLocation(body: geoURI.bodyMessage,
|
_ = await self.roomProxy.timeline.sendLocation(body: geoURI.bodyMessage,
|
||||||
geoURI: geoURI,
|
geoURI: geoURI,
|
||||||
description: nil,
|
description: nil,
|
||||||
zoomLevel: 15,
|
zoomLevel: 15,
|
||||||
assetType: isUserLocation ? .sender : .pin)
|
assetType: isUserLocation ? .sender : .pin)
|
||||||
self.navigationSplitCoordinator.setSheetCoordinator(nil)
|
self.navigationStackCoordinator.setSheetCoordinator(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.analytics.trackComposer(inThread: false,
|
self.analytics.trackComposer(inThread: false,
|
||||||
@@ -790,7 +697,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
messageType: isUserLocation ? .LocationUser : .LocationPin,
|
messageType: isUserLocation ? .LocationUser : .LocationPin,
|
||||||
startsThread: nil)
|
startsThread: nil)
|
||||||
case .close:
|
case .close:
|
||||||
self.navigationSplitCoordinator.setSheetCoordinator(nil)
|
self.navigationStackCoordinator.setSheetCoordinator(nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
@@ -813,7 +720,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
self.navigationSplitCoordinator.setSheetCoordinator(nil)
|
self.navigationStackCoordinator.setSheetCoordinator(nil)
|
||||||
|
|
||||||
switch action {
|
switch action {
|
||||||
case .cancel:
|
case .cancel:
|
||||||
@@ -831,18 +738,13 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
navigationSplitCoordinator.setSheetCoordinator(stackCoordinator) { [weak self] in
|
navigationStackCoordinator.setSheetCoordinator(stackCoordinator) { [weak self] in
|
||||||
self?.stateMachine.tryEvent(.dismissPollForm)
|
self?.stateMachine.tryEvent(.dismissPollForm)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func createPoll(question: String, options: [String], pollKind: Poll.Kind) {
|
private func createPoll(question: String, options: [String], pollKind: Poll.Kind) {
|
||||||
Task {
|
Task {
|
||||||
guard let roomProxy = self.roomProxy else {
|
|
||||||
self.userIndicatorController.submitIndicator(UserIndicator(title: L10n.errorUnknown))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let result = await roomProxy.timeline.createPoll(question: question, answers: options, pollKind: pollKind)
|
let result = await roomProxy.timeline.createPoll(question: question, answers: options, pollKind: pollKind)
|
||||||
|
|
||||||
self.analytics.trackComposer(inThread: false,
|
self.analytics.trackComposer(inThread: false,
|
||||||
@@ -864,11 +766,6 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
|
|
||||||
private func editPoll(pollStartID: String, question: String, options: [String], pollKind: Poll.Kind) {
|
private func editPoll(pollStartID: String, question: String, options: [String], pollKind: Poll.Kind) {
|
||||||
Task {
|
Task {
|
||||||
guard let roomProxy = self.roomProxy else {
|
|
||||||
self.userIndicatorController.submitIndicator(UserIndicator(title: L10n.errorUnknown))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let result = await roomProxy.timeline.editPoll(original: pollStartID, question: question, answers: options, pollKind: pollKind)
|
let result = await roomProxy.timeline.editPoll(original: pollStartID, question: question, answers: options, pollKind: pollKind)
|
||||||
|
|
||||||
switch result {
|
switch result {
|
||||||
@@ -882,7 +779,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
|
|
||||||
private func deletePoll(mode: PollFormMode) {
|
private func deletePoll(mode: PollFormMode) {
|
||||||
Task {
|
Task {
|
||||||
guard case .edit(let pollStartID, _) = mode, let roomProxy = self.roomProxy else {
|
guard case .edit(let pollStartID, _) = mode else {
|
||||||
self.userIndicatorController.submitIndicator(UserIndicator(title: L10n.errorUnknown))
|
self.userIndicatorController.submitIndicator(UserIndicator(title: L10n.errorUnknown))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -905,10 +802,6 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func asyncPresentRoomPollsHistory() async {
|
private func asyncPresentRoomPollsHistory() async {
|
||||||
guard let roomProxy else {
|
|
||||||
fatalError()
|
|
||||||
}
|
|
||||||
|
|
||||||
let userID = userSession.clientProxy.userID
|
let userID = userSession.clientProxy.userID
|
||||||
|
|
||||||
let timelineItemFactory = RoomTimelineItemFactory(userID: userID,
|
let timelineItemFactory = RoomTimelineItemFactory(userID: userID,
|
||||||
@@ -940,10 +833,6 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func presentRoomMemberDetails(userID: String) {
|
private func presentRoomMemberDetails(userID: String) {
|
||||||
guard let roomProxy else {
|
|
||||||
fatalError()
|
|
||||||
}
|
|
||||||
|
|
||||||
let params = RoomMemberDetailsScreenCoordinatorParameters(userID: userID,
|
let params = RoomMemberDetailsScreenCoordinatorParameters(userID: userID,
|
||||||
roomProxy: roomProxy,
|
roomProxy: roomProxy,
|
||||||
clientProxy: userSession.clientProxy,
|
clientProxy: userSession.clientProxy,
|
||||||
@@ -968,12 +857,12 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
let currentDirectRoom = await userSession.clientProxy.directRoomForUserID(userID)
|
let currentDirectRoom = await userSession.clientProxy.directRoomForUserID(userID)
|
||||||
switch currentDirectRoom {
|
switch currentDirectRoom {
|
||||||
case .success(.some(let roomID)):
|
case .success(.some(let roomID)):
|
||||||
stateMachine.tryEvent(.presentRoom(roomID: roomID))
|
actionsSubject.send(.presentRoom(roomID: roomID))
|
||||||
case .success(nil):
|
case .success(nil):
|
||||||
switch await userSession.clientProxy.createDirectRoom(with: userID, expectedRoomName: displayName) {
|
switch await userSession.clientProxy.createDirectRoom(with: userID, expectedRoomName: displayName) {
|
||||||
case .success(let roomID):
|
case .success(let roomID):
|
||||||
analytics.trackCreatedRoom(isDM: true)
|
analytics.trackCreatedRoom(isDM: true)
|
||||||
stateMachine.tryEvent(.presentRoom(roomID: roomID))
|
actionsSubject.send(.presentRoom(roomID: roomID))
|
||||||
case .failure:
|
case .failure:
|
||||||
userIndicatorController.alertInfo = .init(id: UUID())
|
userIndicatorController.alertInfo = .init(id: UUID())
|
||||||
}
|
}
|
||||||
@@ -993,7 +882,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func presentMessageForwarding(for itemID: TimelineItemIdentifier) {
|
private func presentMessageForwarding(for itemID: TimelineItemIdentifier) {
|
||||||
guard let roomProxy, let roomSummaryProvider = userSession.clientProxy.alternateRoomSummaryProvider, let eventID = itemID.eventID else {
|
guard let roomSummaryProvider = userSession.clientProxy.alternateRoomSummaryProvider, let eventID = itemID.eventID else {
|
||||||
fatalError()
|
fatalError()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1028,12 +917,6 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func forward(eventID: String, toRoomID roomID: String) async {
|
private func forward(eventID: String, toRoomID roomID: String) async {
|
||||||
guard let roomProxy else {
|
|
||||||
MXLog.error("Failed retrieving current room with id: \(roomID)")
|
|
||||||
userIndicatorController.submitIndicator(UserIndicator(title: L10n.errorUnknown))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let messageEventContent = roomProxy.timeline.messageEventContent(for: eventID) else {
|
guard let messageEventContent = roomProxy.timeline.messageEventContent(for: eventID) else {
|
||||||
MXLog.error("Failed retrieving forwarded message event content for eventID: \(eventID)")
|
MXLog.error("Failed retrieving forwarded message event content for eventID: \(eventID)")
|
||||||
userIndicatorController.submitIndicator(UserIndicator(title: L10n.errorUnknown))
|
userIndicatorController.submitIndicator(UserIndicator(title: L10n.errorUnknown))
|
||||||
@@ -1052,14 +935,12 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
stateMachine.tryEvent(.presentRoom(roomID: roomID), userInfo: EventUserInfo(animated: true, destinationRoomProxy: targetRoomProxy))
|
// This will become a child flow and we can pass the proxy down afterwards.
|
||||||
|
|
||||||
|
actionsSubject.send(.presentRoom(roomID: roomID))
|
||||||
}
|
}
|
||||||
|
|
||||||
private func presentNotificationSettingsScreen() {
|
private func presentNotificationSettingsScreen() {
|
||||||
guard let roomProxy else {
|
|
||||||
fatalError()
|
|
||||||
}
|
|
||||||
|
|
||||||
let parameters = RoomNotificationSettingsScreenCoordinatorParameters(notificationSettingsProxy: userSession.clientProxy.notificationSettings,
|
let parameters = RoomNotificationSettingsScreenCoordinatorParameters(notificationSettingsProxy: userSession.clientProxy.notificationSettings,
|
||||||
roomProxy: roomProxy,
|
roomProxy: roomProxy,
|
||||||
displayAsUserDefinedRoomSettings: false)
|
displayAsUserDefinedRoomSettings: false)
|
||||||
@@ -1101,10 +982,6 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func presentInviteUsersScreen() {
|
private func presentInviteUsersScreen() {
|
||||||
guard let roomProxy else {
|
|
||||||
fatalError()
|
|
||||||
}
|
|
||||||
|
|
||||||
let selectedUsersSubject: CurrentValueSubject<[UserProfileProxy], Never> = .init([])
|
let selectedUsersSubject: CurrentValueSubject<[UserProfileProxy], Never> = .init([])
|
||||||
|
|
||||||
let stackCoordinator = NavigationStackCoordinator()
|
let stackCoordinator = NavigationStackCoordinator()
|
||||||
@@ -1173,8 +1050,6 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func presentRolesAndPermissionsScreen() {
|
private func presentRolesAndPermissionsScreen() {
|
||||||
guard let roomProxy else { fatalError() }
|
|
||||||
|
|
||||||
let parameters = RoomRolesAndPermissionsFlowCoordinatorParameters(roomProxy: roomProxy,
|
let parameters = RoomRolesAndPermissionsFlowCoordinatorParameters(roomProxy: roomProxy,
|
||||||
navigationStackCoordinator: navigationStackCoordinator,
|
navigationStackCoordinator: navigationStackCoordinator,
|
||||||
userIndicatorController: userIndicatorController,
|
userIndicatorController: userIndicatorController,
|
||||||
@@ -1208,39 +1083,41 @@ private extension RoomFlowCoordinator {
|
|||||||
|
|
||||||
enum State: StateType {
|
enum State: StateType {
|
||||||
case initial
|
case initial
|
||||||
case room(roomID: String)
|
case room
|
||||||
case roomDetails(roomID: String, isRoot: Bool)
|
case roomDetails(isRoot: Bool)
|
||||||
case roomDetailsEditScreen(roomID: String)
|
case roomDetailsEditScreen
|
||||||
case notificationSettings(roomID: String)
|
case notificationSettings
|
||||||
case globalNotificationSettings(roomID: String)
|
case globalNotificationSettings
|
||||||
case roomMembersList(roomID: String)
|
case roomMembersList
|
||||||
case roomMemberDetails(roomID: String, userID: String, fromRoomMembersList: Bool)
|
case roomMemberDetails(userID: String, fromRoomMembersList: Bool)
|
||||||
case inviteUsersScreen(roomID: String, fromRoomMembersList: Bool)
|
case inviteUsersScreen(fromRoomMembersList: Bool)
|
||||||
case mediaUploadPicker(roomID: String, source: MediaPickerScreenSource)
|
case mediaUploadPicker(source: MediaPickerScreenSource)
|
||||||
case mediaUploadPreview(roomID: String, fileURL: URL)
|
case mediaUploadPreview(fileURL: URL)
|
||||||
case emojiPicker(roomID: String, itemID: TimelineItemIdentifier, selectedEmojis: Set<String>)
|
case emojiPicker(itemID: TimelineItemIdentifier, selectedEmojis: Set<String>)
|
||||||
case mapNavigator(roomID: String)
|
case mapNavigator
|
||||||
case messageForwarding(roomID: String, itemID: TimelineItemIdentifier)
|
case messageForwarding(itemID: TimelineItemIdentifier)
|
||||||
case reportContent(roomID: String, itemID: TimelineItemIdentifier, senderID: String)
|
case reportContent(itemID: TimelineItemIdentifier, senderID: String)
|
||||||
case pollForm(roomID: String)
|
case pollForm
|
||||||
case pollsHistory(roomID: String)
|
case pollsHistory
|
||||||
case pollsHistoryForm(roomID: String)
|
case pollsHistoryForm
|
||||||
case rolesAndPermissions(roomID: String)
|
case rolesAndPermissions
|
||||||
|
|
||||||
|
/// The flow is complete and is handing control of the stack back to its parent.
|
||||||
|
case complete
|
||||||
}
|
}
|
||||||
|
|
||||||
struct EventUserInfo {
|
struct EventUserInfo {
|
||||||
let animated: Bool
|
let animated: Bool
|
||||||
var destinationRoomProxy: RoomProxyProtocol?
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Event: EventType {
|
enum Event: EventType {
|
||||||
case presentRoom(roomID: String)
|
case presentRoom
|
||||||
case dismissRoom
|
case dismissRoom
|
||||||
|
|
||||||
case presentReportContent(itemID: TimelineItemIdentifier, senderID: String)
|
case presentReportContent(itemID: TimelineItemIdentifier, senderID: String)
|
||||||
case dismissReportContent
|
case dismissReportContent
|
||||||
|
|
||||||
case presentRoomDetails(roomID: String)
|
case presentRoomDetails
|
||||||
case dismissRoomDetails
|
case dismissRoomDetails
|
||||||
|
|
||||||
case presentRoomDetailsEditScreen
|
case presentRoomDetailsEditScreen
|
||||||
|
|||||||
@@ -31,10 +31,13 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
private let windowManager: WindowManagerProtocol
|
private let windowManager: WindowManagerProtocol
|
||||||
private let bugReportService: BugReportServiceProtocol
|
private let bugReportService: BugReportServiceProtocol
|
||||||
private let appSettings: AppSettings
|
private let appSettings: AppSettings
|
||||||
|
private let analytics: AnalyticsService
|
||||||
|
|
||||||
private let stateMachine: UserSessionFlowCoordinatorStateMachine
|
private let stateMachine: UserSessionFlowCoordinatorStateMachine
|
||||||
|
|
||||||
private let roomFlowCoordinator: RoomFlowCoordinator
|
// periphery:ignore - retaining purpose
|
||||||
|
private var roomFlowCoordinator: RoomFlowCoordinator?
|
||||||
|
private let roomTimelineControllerFactory: RoomTimelineControllerFactoryProtocol
|
||||||
|
|
||||||
private let settingsFlowCoordinator: SettingsFlowCoordinator
|
private let settingsFlowCoordinator: SettingsFlowCoordinator
|
||||||
|
|
||||||
@@ -58,6 +61,9 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
actionsSubject.eraseToAnyPublisher()
|
actionsSubject.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// For testing purposes.
|
||||||
|
var statePublisher: AnyPublisher<UserSessionFlowCoordinatorStateMachine.State, Never> { stateMachine.statePublisher }
|
||||||
|
|
||||||
init(userSession: UserSessionProtocol,
|
init(userSession: UserSessionProtocol,
|
||||||
navigationRootCoordinator: NavigationRootCoordinator,
|
navigationRootCoordinator: NavigationRootCoordinator,
|
||||||
windowManager: WindowManagerProtocol,
|
windowManager: WindowManagerProtocol,
|
||||||
@@ -73,7 +79,9 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
self.navigationRootCoordinator = navigationRootCoordinator
|
self.navigationRootCoordinator = navigationRootCoordinator
|
||||||
self.windowManager = windowManager
|
self.windowManager = windowManager
|
||||||
self.bugReportService = bugReportService
|
self.bugReportService = bugReportService
|
||||||
|
self.roomTimelineControllerFactory = roomTimelineControllerFactory
|
||||||
self.appSettings = appSettings
|
self.appSettings = appSettings
|
||||||
|
self.analytics = analytics
|
||||||
|
|
||||||
navigationSplitCoordinator = NavigationSplitCoordinator(placeholderCoordinator: PlaceholderScreenCoordinator())
|
navigationSplitCoordinator = NavigationSplitCoordinator(placeholderCoordinator: PlaceholderScreenCoordinator())
|
||||||
|
|
||||||
@@ -82,16 +90,6 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
|
|
||||||
navigationSplitCoordinator.setSidebarCoordinator(sidebarNavigationStackCoordinator)
|
navigationSplitCoordinator.setSidebarCoordinator(sidebarNavigationStackCoordinator)
|
||||||
|
|
||||||
roomFlowCoordinator = RoomFlowCoordinator(userSession: userSession,
|
|
||||||
roomTimelineControllerFactory: roomTimelineControllerFactory,
|
|
||||||
navigationStackCoordinator: detailNavigationStackCoordinator,
|
|
||||||
navigationSplitCoordinator: navigationSplitCoordinator,
|
|
||||||
emojiProvider: EmojiProvider(),
|
|
||||||
appSettings: appSettings,
|
|
||||||
analytics: analytics,
|
|
||||||
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
|
||||||
orientationManager: windowManager)
|
|
||||||
|
|
||||||
settingsFlowCoordinator = SettingsFlowCoordinator(parameters: .init(userSession: userSession,
|
settingsFlowCoordinator = SettingsFlowCoordinator(parameters: .init(userSession: userSession,
|
||||||
windowManager: windowManager,
|
windowManager: windowManager,
|
||||||
appLockService: appLockService,
|
appLockService: appLockService,
|
||||||
@@ -125,28 +123,6 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
roomFlowCoordinator.actions.sink { [weak self] action in
|
|
||||||
guard let self else { return }
|
|
||||||
|
|
||||||
switch action {
|
|
||||||
case .presentedRoom(let roomID):
|
|
||||||
analytics.signpost.beginRoomFlow(roomID)
|
|
||||||
|
|
||||||
let availableInvitesCount = userSession.clientProxy.inviteSummaryProvider?.roomListPublisher.value.count ?? 0
|
|
||||||
if case .invitesScreen = stateMachine.state, availableInvitesCount == 1 {
|
|
||||||
dismissInvitesList(animated: true)
|
|
||||||
}
|
|
||||||
|
|
||||||
stateMachine.processEvent(.selectRoom(roomID: roomID))
|
|
||||||
case .dismissedRoom:
|
|
||||||
stateMachine.processEvent(.deselectRoom)
|
|
||||||
analytics.signpost.endRoomFlow()
|
|
||||||
case .presentCallScreen(let roomProxy):
|
|
||||||
presentCallScreen(roomProxy: roomProxy)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.store(in: &cancellables)
|
|
||||||
|
|
||||||
settingsFlowCoordinator.actions.sink { [weak self] action in
|
settingsFlowCoordinator.actions.sink { [weak self] action in
|
||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
|
|
||||||
@@ -202,11 +178,15 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
|
|
||||||
switch appRoute {
|
switch appRoute {
|
||||||
case .room(let roomID):
|
case .room(let roomID):
|
||||||
Task {
|
Task { await self.handleRoomRoute(roomID: roomID, animated: animated) }
|
||||||
await self.handleRoomRoute(roomID: roomID, animated: animated)
|
case .roomDetails(let roomID):
|
||||||
|
if stateMachine.state.selectedRoomID == roomID {
|
||||||
|
roomFlowCoordinator?.handleAppRoute(appRoute, animated: animated)
|
||||||
|
} else {
|
||||||
|
stateMachine.processEvent(.selectRoom(roomID: roomID, showingRoomDetails: true), userInfo: .init(animated: animated))
|
||||||
}
|
}
|
||||||
case .roomDetails, .roomList, .roomMemberDetails:
|
case .roomList, .roomMemberDetails:
|
||||||
self.roomFlowCoordinator.handleAppRoute(appRoute, animated: animated)
|
self.roomFlowCoordinator?.handleAppRoute(appRoute, animated: animated)
|
||||||
case .genericCallLink(let url):
|
case .genericCallLink(let url):
|
||||||
self.navigationSplitCoordinator.setSheetCoordinator(GenericCallLinkCoordinator(parameters: .init(url: url)), animated: animated)
|
self.navigationSplitCoordinator.setSheetCoordinator(GenericCallLinkCoordinator(parameters: .init(url: url)), animated: animated)
|
||||||
case .oidcCallback:
|
case .oidcCallback:
|
||||||
@@ -221,11 +201,11 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
switch await userSession.clientProxy.roomForIdentifier(roomID)?.membership {
|
switch await userSession.clientProxy.roomForIdentifier(roomID)?.membership {
|
||||||
case .invited:
|
case .invited:
|
||||||
if UIDevice.current.isPhone {
|
if UIDevice.current.isPhone {
|
||||||
roomFlowCoordinator.clearRoute(animated: animated)
|
roomFlowCoordinator?.clearRoute(animated: animated)
|
||||||
}
|
}
|
||||||
stateMachine.processEvent(.showInvitesScreen, userInfo: .init(animated: animated))
|
stateMachine.processEvent(.showInvitesScreen, userInfo: .init(animated: animated))
|
||||||
case .joined:
|
case .joined:
|
||||||
roomFlowCoordinator.handleAppRoute(.room(roomID: roomID), animated: animated)
|
stateMachine.processEvent(.selectRoom(roomID: roomID, showingRoomDetails: false), userInfo: .init(animated: animated))
|
||||||
case .left, .none:
|
case .left, .none:
|
||||||
// Do nothing but maybe we should ask design to have some kind of error state
|
// Do nothing but maybe we should ask design to have some kind of error state
|
||||||
break
|
break
|
||||||
@@ -233,7 +213,7 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func clearRoute(animated: Bool) {
|
func clearRoute(animated: Bool) {
|
||||||
roomFlowCoordinator.clearRoute(animated: animated)
|
roomFlowCoordinator?.handleAppRoute(.roomList, animated: animated)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
@@ -268,15 +248,15 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
presentHomeScreen()
|
presentHomeScreen()
|
||||||
attemptStartingOnboarding()
|
attemptStartingOnboarding()
|
||||||
|
|
||||||
case(.roomList, .selectRoom, .roomList):
|
case(.roomList(let currentRoomID), .selectRoom(let roomID, let showingRoomDetails), .roomList):
|
||||||
break
|
Task { await self.presentRoomFlow(roomID: roomID, showingRoomDetails: showingRoomDetails, animated: animated) }
|
||||||
case(.roomList, .deselectRoom, .roomList):
|
case(.roomList, .deselectRoom, .roomList):
|
||||||
break
|
tearDownRoomFlow(animated: animated)
|
||||||
|
|
||||||
case (.invitesScreen, .selectRoom, .invitesScreen):
|
case (.invitesScreen, .selectRoom, .invitesScreen):
|
||||||
break
|
break
|
||||||
case (.invitesScreen, .deselectRoom, .invitesScreen):
|
case (.invitesScreen, .deselectRoom, .invitesScreen):
|
||||||
break
|
tearDownRoomFlow(animated: animated)
|
||||||
|
|
||||||
case (.roomList, .showSettingsScreen, .settingsScreen):
|
case (.roomList, .showSettingsScreen, .settingsScreen):
|
||||||
break
|
break
|
||||||
@@ -353,13 +333,13 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
|
|
||||||
switch action {
|
switch action {
|
||||||
case .presentRoom(let roomID):
|
case .presentRoom(let roomID):
|
||||||
roomFlowCoordinator.handleAppRoute(.room(roomID: roomID), animated: true)
|
stateMachine.processEvent(.selectRoom(roomID: roomID, showingRoomDetails: false))
|
||||||
case .presentRoomDetails(let roomID):
|
case .presentRoomDetails(let roomID):
|
||||||
roomFlowCoordinator.handleAppRoute(.roomDetails(roomID: roomID), animated: true)
|
stateMachine.processEvent(.selectRoom(roomID: roomID, showingRoomDetails: true))
|
||||||
case .roomLeft(let roomID):
|
case .roomLeft(let roomID):
|
||||||
if case .roomList(selectedRoomID: let selectedRoomID) = stateMachine.state,
|
if case .roomList(selectedRoomID: let selectedRoomID) = stateMachine.state,
|
||||||
selectedRoomID == roomID {
|
selectedRoomID == roomID {
|
||||||
roomFlowCoordinator.handleAppRoute(.roomList, animated: true)
|
clearRoute(animated: true)
|
||||||
}
|
}
|
||||||
case .presentSettingsScreen:
|
case .presentSettingsScreen:
|
||||||
settingsFlowCoordinator.handleAppRoute(.settings, animated: true)
|
settingsFlowCoordinator.handleAppRoute(.settings, animated: true)
|
||||||
@@ -431,6 +411,62 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
presentSecureBackupLogoutConfirmationScreen()
|
presentSecureBackupLogoutConfirmationScreen()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: Room Flow
|
||||||
|
|
||||||
|
private func presentRoomFlow(roomID: String, showingRoomDetails: Bool, animated: Bool) async {
|
||||||
|
guard let roomProxy = await userSession.clientProxy.roomForIdentifier(roomID) else {
|
||||||
|
MXLog.error("Invalid room ID: \(roomID)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let coordinator = await RoomFlowCoordinator(roomProxy: roomProxy,
|
||||||
|
userSession: userSession,
|
||||||
|
roomTimelineControllerFactory: roomTimelineControllerFactory,
|
||||||
|
navigationStackCoordinator: detailNavigationStackCoordinator,
|
||||||
|
emojiProvider: EmojiProvider(),
|
||||||
|
appSettings: appSettings,
|
||||||
|
analytics: analytics,
|
||||||
|
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||||
|
orientationManager: windowManager)
|
||||||
|
|
||||||
|
coordinator.actions.sink { [weak self] action in
|
||||||
|
guard let self else { return }
|
||||||
|
|
||||||
|
switch action {
|
||||||
|
case .finished:
|
||||||
|
stateMachine.processEvent(.deselectRoom)
|
||||||
|
case .presentRoom(let roomID):
|
||||||
|
stateMachine.processEvent(.selectRoom(roomID: roomID, showingRoomDetails: false))
|
||||||
|
case .presentCallScreen(let roomProxy):
|
||||||
|
presentCallScreen(roomProxy: roomProxy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
|
||||||
|
roomFlowCoordinator = coordinator
|
||||||
|
|
||||||
|
if navigationSplitCoordinator.detailCoordinator !== detailNavigationStackCoordinator {
|
||||||
|
navigationSplitCoordinator.setDetailCoordinator(detailNavigationStackCoordinator, animated: animated)
|
||||||
|
}
|
||||||
|
|
||||||
|
if showingRoomDetails {
|
||||||
|
coordinator.handleAppRoute(.roomDetails(roomID: roomID), animated: animated)
|
||||||
|
} else {
|
||||||
|
coordinator.handleAppRoute(.room(roomID: roomID), animated: animated)
|
||||||
|
}
|
||||||
|
|
||||||
|
let availableInvitesCount = userSession.clientProxy.inviteSummaryProvider?.roomListPublisher.value.count ?? 0
|
||||||
|
if case .invitesScreen = stateMachine.state, availableInvitesCount == 1 {
|
||||||
|
dismissInvitesList(animated: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func tearDownRoomFlow(animated: Bool) {
|
||||||
|
// THIS MUST BE CALLED *AFTER* THE FLOW HAS TIDIED UP THE STACK OR IT CAN CAUSE A CRASH.
|
||||||
|
navigationSplitCoordinator.setDetailCoordinator(nil, animated: animated)
|
||||||
|
roomFlowCoordinator = nil
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: Start Chat
|
// MARK: Start Chat
|
||||||
|
|
||||||
private func presentStartChat(animated: Bool) {
|
private func presentStartChat(animated: Bool) {
|
||||||
@@ -451,7 +487,7 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
self.navigationSplitCoordinator.setSheetCoordinator(nil)
|
self.navigationSplitCoordinator.setSheetCoordinator(nil)
|
||||||
case .openRoom(let roomID):
|
case .openRoom(let roomID):
|
||||||
self.navigationSplitCoordinator.setSheetCoordinator(nil)
|
self.navigationSplitCoordinator.setSheetCoordinator(nil)
|
||||||
self.roomFlowCoordinator.handleAppRoute(.room(roomID: roomID), animated: true)
|
self.stateMachine.processEvent(.selectRoom(roomID: roomID, showingRoomDetails: false))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
@@ -473,7 +509,7 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
.sink { [weak self] action in
|
.sink { [weak self] action in
|
||||||
switch action {
|
switch action {
|
||||||
case .openRoom(let roomID):
|
case .openRoom(let roomID):
|
||||||
self?.roomFlowCoordinator.handleAppRoute(.room(roomID: roomID), animated: true)
|
self?.stateMachine.processEvent(.selectRoom(roomID: roomID, showingRoomDetails: false))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import Combine
|
||||||
import Foundation
|
import Foundation
|
||||||
import SwiftState
|
import SwiftState
|
||||||
|
|
||||||
@@ -38,11 +39,27 @@ class UserSessionFlowCoordinatorStateMachine {
|
|||||||
/// Showing invites list screen
|
/// Showing invites list screen
|
||||||
case invitesScreen(selectedRoomID: String?)
|
case invitesScreen(selectedRoomID: String?)
|
||||||
|
|
||||||
// Showing the logout flows
|
/// Showing the logout flows
|
||||||
case logoutConfirmationScreen(selectedRoomID: String?)
|
case logoutConfirmationScreen(selectedRoomID: String?)
|
||||||
|
|
||||||
// Showing Room Directory Search screen
|
/// Showing Room Directory Search screen
|
||||||
case roomDirectorySearchScreen(selectedRoomID: String?)
|
case roomDirectorySearchScreen(selectedRoomID: String?)
|
||||||
|
|
||||||
|
/// The selected room ID from the state if available.
|
||||||
|
var selectedRoomID: String? {
|
||||||
|
switch self {
|
||||||
|
case .initial:
|
||||||
|
nil
|
||||||
|
case .roomList(let selectedRoomID),
|
||||||
|
.feedbackScreen(let selectedRoomID),
|
||||||
|
.settingsScreen(let selectedRoomID),
|
||||||
|
.startChatScreen(let selectedRoomID),
|
||||||
|
.invitesScreen(let selectedRoomID),
|
||||||
|
.logoutConfirmationScreen(let selectedRoomID),
|
||||||
|
.roomDirectorySearchScreen(let selectedRoomID):
|
||||||
|
selectedRoomID
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct EventUserInfo {
|
struct EventUserInfo {
|
||||||
@@ -56,7 +73,7 @@ class UserSessionFlowCoordinatorStateMachine {
|
|||||||
|
|
||||||
/// Request presentation for a particular room
|
/// Request presentation for a particular room
|
||||||
/// - Parameter roomID:the room identifier
|
/// - Parameter roomID:the room identifier
|
||||||
case selectRoom(roomID: String)
|
case selectRoom(roomID: String, showingRoomDetails: Bool)
|
||||||
/// The room screen has been dismissed
|
/// The room screen has been dismissed
|
||||||
case deselectRoom
|
case deselectRoom
|
||||||
|
|
||||||
@@ -96,6 +113,11 @@ class UserSessionFlowCoordinatorStateMachine {
|
|||||||
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()
|
||||||
@@ -106,9 +128,9 @@ class UserSessionFlowCoordinatorStateMachine {
|
|||||||
|
|
||||||
stateMachine.addRouteMapping { event, fromState, _ in
|
stateMachine.addRouteMapping { event, fromState, _ in
|
||||||
switch (fromState, event) {
|
switch (fromState, event) {
|
||||||
case (.roomList, .selectRoom(let roomID)):
|
case (.roomList, .selectRoom(let roomID, _)):
|
||||||
return .roomList(selectedRoomID: roomID)
|
return .roomList(selectedRoomID: roomID)
|
||||||
case (.invitesScreen, .selectRoom(let roomID)):
|
case (.invitesScreen, .selectRoom(let roomID, _)):
|
||||||
return .invitesScreen(selectedRoomID: roomID)
|
return .invitesScreen(selectedRoomID: roomID)
|
||||||
case (.roomList, .deselectRoom):
|
case (.roomList, .deselectRoom):
|
||||||
return .roomList(selectedRoomID: nil)
|
return .roomList(selectedRoomID: nil)
|
||||||
@@ -160,6 +182,10 @@ class UserSessionFlowCoordinatorStateMachine {
|
|||||||
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
|
||||||
|
|||||||
@@ -85,11 +85,13 @@ extension ClientProxyMock {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let roomID = room.id ?? UUID().uuidString
|
||||||
|
|
||||||
switch room {
|
switch room {
|
||||||
case .empty:
|
case .empty:
|
||||||
return await RoomProxyMock(with: .init(name: "Empty room"))
|
return await RoomProxyMock(with: .init(name: "Empty room"))
|
||||||
case .filled(let details), .invalidated(let details):
|
case .filled(let details), .invalidated(let details):
|
||||||
return await RoomProxyMock(with: .init(name: details.name))
|
return await RoomProxyMock(with: .init(id: roomID, name: details.name))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4351,4 +4351,109 @@ class VoiceMessageRecorderMock: VoiceMessageRecorderProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
class WindowManagerMock: WindowManagerProtocol {
|
||||||
|
weak var delegate: WindowManagerDelegate?
|
||||||
|
var mainWindow: UIWindow!
|
||||||
|
var overlayWindow: UIWindow!
|
||||||
|
var globalSearchWindow: UIWindow!
|
||||||
|
var alternateWindow: UIWindow!
|
||||||
|
var windows: [UIWindow] = []
|
||||||
|
|
||||||
|
//MARK: - configure
|
||||||
|
|
||||||
|
var configureWithCallsCount = 0
|
||||||
|
var configureWithCalled: Bool {
|
||||||
|
return configureWithCallsCount > 0
|
||||||
|
}
|
||||||
|
var configureWithReceivedWindowScene: UIWindowScene?
|
||||||
|
var configureWithReceivedInvocations: [UIWindowScene] = []
|
||||||
|
var configureWithClosure: ((UIWindowScene) -> Void)?
|
||||||
|
|
||||||
|
func configure(with windowScene: UIWindowScene) {
|
||||||
|
configureWithCallsCount += 1
|
||||||
|
configureWithReceivedWindowScene = windowScene
|
||||||
|
configureWithReceivedInvocations.append(windowScene)
|
||||||
|
configureWithClosure?(windowScene)
|
||||||
|
}
|
||||||
|
//MARK: - switchToMain
|
||||||
|
|
||||||
|
var switchToMainCallsCount = 0
|
||||||
|
var switchToMainCalled: Bool {
|
||||||
|
return switchToMainCallsCount > 0
|
||||||
|
}
|
||||||
|
var switchToMainClosure: (() -> Void)?
|
||||||
|
|
||||||
|
func switchToMain() {
|
||||||
|
switchToMainCallsCount += 1
|
||||||
|
switchToMainClosure?()
|
||||||
|
}
|
||||||
|
//MARK: - switchToAlternate
|
||||||
|
|
||||||
|
var switchToAlternateCallsCount = 0
|
||||||
|
var switchToAlternateCalled: Bool {
|
||||||
|
return switchToAlternateCallsCount > 0
|
||||||
|
}
|
||||||
|
var switchToAlternateClosure: (() -> Void)?
|
||||||
|
|
||||||
|
func switchToAlternate() {
|
||||||
|
switchToAlternateCallsCount += 1
|
||||||
|
switchToAlternateClosure?()
|
||||||
|
}
|
||||||
|
//MARK: - showGlobalSearch
|
||||||
|
|
||||||
|
var showGlobalSearchCallsCount = 0
|
||||||
|
var showGlobalSearchCalled: Bool {
|
||||||
|
return showGlobalSearchCallsCount > 0
|
||||||
|
}
|
||||||
|
var showGlobalSearchClosure: (() -> Void)?
|
||||||
|
|
||||||
|
func showGlobalSearch() {
|
||||||
|
showGlobalSearchCallsCount += 1
|
||||||
|
showGlobalSearchClosure?()
|
||||||
|
}
|
||||||
|
//MARK: - hideGlobalSearch
|
||||||
|
|
||||||
|
var hideGlobalSearchCallsCount = 0
|
||||||
|
var hideGlobalSearchCalled: Bool {
|
||||||
|
return hideGlobalSearchCallsCount > 0
|
||||||
|
}
|
||||||
|
var hideGlobalSearchClosure: (() -> Void)?
|
||||||
|
|
||||||
|
func hideGlobalSearch() {
|
||||||
|
hideGlobalSearchCallsCount += 1
|
||||||
|
hideGlobalSearchClosure?()
|
||||||
|
}
|
||||||
|
//MARK: - setOrientation
|
||||||
|
|
||||||
|
var setOrientationCallsCount = 0
|
||||||
|
var setOrientationCalled: Bool {
|
||||||
|
return setOrientationCallsCount > 0
|
||||||
|
}
|
||||||
|
var setOrientationReceivedOrientation: UIInterfaceOrientationMask?
|
||||||
|
var setOrientationReceivedInvocations: [UIInterfaceOrientationMask] = []
|
||||||
|
var setOrientationClosure: ((UIInterfaceOrientationMask) -> Void)?
|
||||||
|
|
||||||
|
func setOrientation(_ orientation: UIInterfaceOrientationMask) {
|
||||||
|
setOrientationCallsCount += 1
|
||||||
|
setOrientationReceivedOrientation = orientation
|
||||||
|
setOrientationReceivedInvocations.append(orientation)
|
||||||
|
setOrientationClosure?(orientation)
|
||||||
|
}
|
||||||
|
//MARK: - lockOrientation
|
||||||
|
|
||||||
|
var lockOrientationCallsCount = 0
|
||||||
|
var lockOrientationCalled: Bool {
|
||||||
|
return lockOrientationCallsCount > 0
|
||||||
|
}
|
||||||
|
var lockOrientationReceivedOrientation: UIInterfaceOrientationMask?
|
||||||
|
var lockOrientationReceivedInvocations: [UIInterfaceOrientationMask] = []
|
||||||
|
var lockOrientationClosure: ((UIInterfaceOrientationMask) -> Void)?
|
||||||
|
|
||||||
|
func lockOrientation(_ orientation: UIInterfaceOrientationMask) {
|
||||||
|
lockOrientationCallsCount += 1
|
||||||
|
lockOrientationReceivedOrientation = orientation
|
||||||
|
lockOrientationReceivedInvocations.append(orientation)
|
||||||
|
lockOrientationClosure?(orientation)
|
||||||
|
}
|
||||||
|
}
|
||||||
// swiftlint:enable all
|
// swiftlint:enable all
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ extension RoomProxyMock {
|
|||||||
timeline = configuration.timeline
|
timeline = configuration.timeline
|
||||||
|
|
||||||
ownUserID = configuration.ownUserID
|
ownUserID = configuration.ownUserID
|
||||||
|
membership = .joined
|
||||||
|
|
||||||
membersPublisher = CurrentValueSubject(configuration.members).asCurrentValuePublisher()
|
membersPublisher = CurrentValueSubject(configuration.members).asCurrentValuePublisher()
|
||||||
typingMembersPublisher = CurrentValueSubject([]).asCurrentValuePublisher()
|
typingMembersPublisher = CurrentValueSubject([]).asCurrentValuePublisher()
|
||||||
|
|||||||
@@ -83,6 +83,34 @@ extension XCTestCase {
|
|||||||
return deferred
|
return deferred
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// XCTest utility that assists in subscribing to a publisher and deferring the failure for a particular value until some other actions have been performed.
|
||||||
|
/// - Parameters:
|
||||||
|
/// - publisher: The publisher to wait on.
|
||||||
|
/// - timeout: A timeout after which we give up.
|
||||||
|
/// - message: An optional custom expectation message
|
||||||
|
/// - until: callback that evaluates outputs until some condition is reached
|
||||||
|
/// - Returns: The deferred fulfilment to be executed after some actions. The publisher's result is not returned from this fulfilment.
|
||||||
|
func deferFailure<P: Publisher>(_ publisher: P,
|
||||||
|
timeout: TimeInterval,
|
||||||
|
message: String? = nil,
|
||||||
|
until condition: @escaping (P.Output) -> Bool) -> DeferredFulfillment<Void> where P.Failure == Never {
|
||||||
|
let expectation = expectation(description: message ?? "Awaiting publisher")
|
||||||
|
expectation.isInverted = true
|
||||||
|
var hasFulfilled = false
|
||||||
|
let cancellable = publisher
|
||||||
|
.sink { value in
|
||||||
|
if condition(value), !hasFulfilled {
|
||||||
|
expectation.fulfill()
|
||||||
|
hasFulfilled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return DeferredFulfillment<Void> {
|
||||||
|
await self.fulfillment(of: [expectation], timeout: timeout)
|
||||||
|
cancellable.cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct DeferredFulfillment<T> {
|
struct DeferredFulfillment<T> {
|
||||||
let closure: () async throws -> T
|
let closure: () async throws -> T
|
||||||
@discardableResult func fulfill() async throws -> T {
|
@discardableResult func fulfill() async throws -> T {
|
||||||
|
|||||||
@@ -38,86 +38,63 @@ class RoomFlowCoordinatorTests: XCTestCase {
|
|||||||
navigationStackCoordinator = NavigationStackCoordinator()
|
navigationStackCoordinator = NavigationStackCoordinator()
|
||||||
navigationSplitCoordinator.setDetailCoordinator(navigationStackCoordinator)
|
navigationSplitCoordinator.setDetailCoordinator(navigationStackCoordinator)
|
||||||
|
|
||||||
roomFlowCoordinator = RoomFlowCoordinator(userSession: userSession,
|
roomFlowCoordinator = await RoomFlowCoordinator(roomProxy: RoomProxyMock(with: .init(id: "1")),
|
||||||
roomTimelineControllerFactory: MockRoomTimelineControllerFactory(),
|
userSession: userSession,
|
||||||
navigationStackCoordinator: navigationStackCoordinator,
|
roomTimelineControllerFactory: MockRoomTimelineControllerFactory(),
|
||||||
navigationSplitCoordinator: navigationSplitCoordinator,
|
navigationStackCoordinator: navigationStackCoordinator,
|
||||||
emojiProvider: EmojiProvider(),
|
emojiProvider: EmojiProvider(),
|
||||||
appSettings: ServiceLocator.shared.settings,
|
appSettings: ServiceLocator.shared.settings,
|
||||||
analytics: ServiceLocator.shared.analytics,
|
analytics: ServiceLocator.shared.analytics,
|
||||||
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||||
orientationManager: OrientationManagerMock())
|
orientationManager: OrientationManagerMock())
|
||||||
}
|
}
|
||||||
|
|
||||||
func testRoomPresentation() async throws {
|
func testRoomPresentation() async throws {
|
||||||
try await process(route: .room(roomID: "1"), expectedAction: .presentedRoom("1"))
|
try await process(route: .room(roomID: "1"))
|
||||||
XCTAssert(navigationStackCoordinator.rootCoordinator is RoomScreenCoordinator)
|
XCTAssert(navigationStackCoordinator.rootCoordinator is RoomScreenCoordinator)
|
||||||
|
|
||||||
try await process(route: .roomList, expectedAction: .dismissedRoom)
|
try await process(route: .roomList, expectedAction: .finished)
|
||||||
XCTAssertNil(navigationStackCoordinator.rootCoordinator)
|
|
||||||
|
|
||||||
try await process(route: .room(roomID: "1"), expectedAction: .presentedRoom("1"))
|
|
||||||
XCTAssert(navigationStackCoordinator.rootCoordinator is RoomScreenCoordinator)
|
|
||||||
|
|
||||||
try await process(route: .room(roomID: "2"), expectedAction: .presentedRoom("2"))
|
|
||||||
XCTAssert(navigationStackCoordinator.rootCoordinator is RoomScreenCoordinator)
|
|
||||||
|
|
||||||
try await process(route: .roomList, expectedAction: .dismissedRoom)
|
|
||||||
XCTAssertNil(navigationStackCoordinator.rootCoordinator)
|
XCTAssertNil(navigationStackCoordinator.rootCoordinator)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testRoomDetailsPresentation() async throws {
|
func testRoomDetailsPresentation() async throws {
|
||||||
try await process(route: .roomDetails(roomID: "1"), expectedAction: .presentedRoom("1"))
|
try await process(route: .roomDetails(roomID: "1"))
|
||||||
XCTAssert(navigationStackCoordinator.rootCoordinator is RoomDetailsScreenCoordinator)
|
XCTAssert(navigationStackCoordinator.rootCoordinator is RoomDetailsScreenCoordinator)
|
||||||
|
|
||||||
try await process(route: .roomList, expectedAction: .dismissedRoom)
|
try await process(route: .roomList, expectedAction: .finished)
|
||||||
XCTAssertNil(navigationStackCoordinator.rootCoordinator)
|
XCTAssertNil(navigationStackCoordinator.rootCoordinator)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testStackUnwinding() async throws {
|
|
||||||
try await process(route: .roomDetails(roomID: "1"), expectedAction: .presentedRoom("1"))
|
|
||||||
XCTAssert(navigationStackCoordinator.rootCoordinator is RoomDetailsScreenCoordinator)
|
|
||||||
|
|
||||||
try await process(route: .room(roomID: "2"), expectedAction: .presentedRoom("2"))
|
|
||||||
XCTAssert(navigationStackCoordinator.rootCoordinator is RoomScreenCoordinator)
|
|
||||||
}
|
|
||||||
|
|
||||||
func testNoOp() async throws {
|
func testNoOp() async throws {
|
||||||
try await process(route: .roomDetails(roomID: "1"), expectedAction: .presentedRoom("1"))
|
try await process(route: .roomDetails(roomID: "1"))
|
||||||
XCTAssert(navigationStackCoordinator.rootCoordinator is RoomDetailsScreenCoordinator)
|
XCTAssert(navigationStackCoordinator.rootCoordinator is RoomDetailsScreenCoordinator)
|
||||||
|
let detailsCoordinator = navigationStackCoordinator.rootCoordinator
|
||||||
|
|
||||||
roomFlowCoordinator.handleAppRoute(.roomDetails(roomID: "1"), animated: true)
|
roomFlowCoordinator.handleAppRoute(.roomDetails(roomID: "1"), animated: true)
|
||||||
await Task.yield()
|
await Task.yield()
|
||||||
XCTAssert(navigationStackCoordinator.rootCoordinator is RoomDetailsScreenCoordinator)
|
|
||||||
}
|
|
||||||
|
|
||||||
func testSwitchToDifferentDetails() async throws {
|
|
||||||
try await process(route: .roomDetails(roomID: "1"), expectedAction: .presentedRoom("1"))
|
|
||||||
XCTAssert(navigationStackCoordinator.rootCoordinator is RoomDetailsScreenCoordinator)
|
|
||||||
|
|
||||||
try await process(route: .roomDetails(roomID: "2"), expectedAction: .presentedRoom("2"))
|
|
||||||
XCTAssert(navigationStackCoordinator.rootCoordinator is RoomDetailsScreenCoordinator)
|
XCTAssert(navigationStackCoordinator.rootCoordinator is RoomDetailsScreenCoordinator)
|
||||||
|
XCTAssert(navigationStackCoordinator.rootCoordinator === detailsCoordinator)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testPushDetails() async throws {
|
func testPushDetails() async throws {
|
||||||
try await process(route: .room(roomID: "1"), expectedAction: .presentedRoom("1"))
|
try await process(route: .room(roomID: "1"))
|
||||||
XCTAssert(navigationStackCoordinator.rootCoordinator is RoomScreenCoordinator)
|
XCTAssert(navigationStackCoordinator.rootCoordinator is RoomScreenCoordinator)
|
||||||
|
XCTAssertEqual(navigationStackCoordinator.stackCoordinators.count, 0)
|
||||||
|
|
||||||
try await process(route: .roomDetails(roomID: "1"), expectedAction: .presentedRoom("1"))
|
try await process(route: .roomDetails(roomID: "1"))
|
||||||
XCTAssert(navigationStackCoordinator.rootCoordinator is RoomScreenCoordinator)
|
XCTAssert(navigationStackCoordinator.rootCoordinator is RoomScreenCoordinator)
|
||||||
XCTAssertEqual(navigationStackCoordinator.stackCoordinators.count, 1)
|
XCTAssertEqual(navigationStackCoordinator.stackCoordinators.count, 1)
|
||||||
XCTAssert(navigationStackCoordinator.stackCoordinators.first is RoomDetailsScreenCoordinator)
|
XCTAssert(navigationStackCoordinator.stackCoordinators.first is RoomDetailsScreenCoordinator)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testReplaceDetailsWithTimeline() async throws {
|
|
||||||
try await process(route: .roomDetails(roomID: "1"), expectedAction: .presentedRoom("1"))
|
|
||||||
XCTAssert(navigationStackCoordinator.rootCoordinator is RoomDetailsScreenCoordinator)
|
|
||||||
|
|
||||||
try await process(route: .room(roomID: "1"), expectedActions: [.dismissedRoom, .presentedRoom("1")])
|
|
||||||
XCTAssert(navigationStackCoordinator.rootCoordinator is RoomScreenCoordinator)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
|
|
||||||
|
private func process(route: AppRoute) async throws {
|
||||||
|
roomFlowCoordinator.handleAppRoute(route, animated: true)
|
||||||
|
await Task.yield()
|
||||||
|
}
|
||||||
|
|
||||||
private func process(route: AppRoute, expectedAction: RoomFlowCoordinatorAction) async throws {
|
private func process(route: AppRoute, expectedAction: RoomFlowCoordinatorAction) async throws {
|
||||||
try await process(route: route, expectedActions: [expectedAction])
|
try await process(route: route, expectedActions: [expectedAction])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,164 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2023 New Vector Ltd
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
//
|
||||||
|
|
||||||
|
import XCTest
|
||||||
|
|
||||||
|
import Combine
|
||||||
|
@testable import ElementX
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
class UserSessionFlowCoordinatorTests: XCTestCase {
|
||||||
|
var userSessionFlowCoordinator: UserSessionFlowCoordinator!
|
||||||
|
var navigationRootCoordinator: NavigationRootCoordinator!
|
||||||
|
var cancellables = Set<AnyCancellable>()
|
||||||
|
|
||||||
|
var detailCoordinator: CoordinatorProtocol? {
|
||||||
|
let navigationSplitCoordinator = navigationRootCoordinator.rootCoordinator as? NavigationSplitCoordinator
|
||||||
|
return navigationSplitCoordinator?.detailCoordinator
|
||||||
|
}
|
||||||
|
|
||||||
|
var detailNavigationStack: NavigationStackCoordinator? {
|
||||||
|
detailCoordinator as? NavigationStackCoordinator
|
||||||
|
}
|
||||||
|
|
||||||
|
override func setUp() async throws {
|
||||||
|
cancellables.removeAll()
|
||||||
|
let clientProxy = ClientProxyMock(.init(userID: "hi@bob", roomSummaryProvider: RoomSummaryProviderMock(.init(state: .loaded(.mockRooms)))))
|
||||||
|
let mediaProvider = MockMediaProvider()
|
||||||
|
let voiceMessageMediaManager = VoiceMessageMediaManagerMock()
|
||||||
|
let userSession = MockUserSession(clientProxy: clientProxy,
|
||||||
|
mediaProvider: mediaProvider,
|
||||||
|
voiceMessageMediaManager: voiceMessageMediaManager)
|
||||||
|
|
||||||
|
navigationRootCoordinator = NavigationRootCoordinator()
|
||||||
|
|
||||||
|
userSessionFlowCoordinator = UserSessionFlowCoordinator(userSession: userSession,
|
||||||
|
navigationRootCoordinator: navigationRootCoordinator,
|
||||||
|
windowManager: WindowManagerMock(),
|
||||||
|
appLockService: AppLockServiceMock(),
|
||||||
|
bugReportService: BugReportServiceMock(),
|
||||||
|
roomTimelineControllerFactory: MockRoomTimelineControllerFactory(),
|
||||||
|
appSettings: ServiceLocator.shared.settings,
|
||||||
|
analytics: ServiceLocator.shared.analytics,
|
||||||
|
notificationManager: NotificationManagerMock(),
|
||||||
|
isNewLogin: false)
|
||||||
|
|
||||||
|
let deferred = deferFulfillment(userSessionFlowCoordinator.statePublisher) { $0 == .roomList(selectedRoomID: nil) }
|
||||||
|
userSessionFlowCoordinator.start()
|
||||||
|
try await deferred.fulfill()
|
||||||
|
}
|
||||||
|
|
||||||
|
func testRoomPresentation() async throws {
|
||||||
|
try await process(route: .room(roomID: "1"), expectedState: .roomList(selectedRoomID: "1"))
|
||||||
|
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
|
||||||
|
XCTAssertNotNil(detailCoordinator)
|
||||||
|
|
||||||
|
try await process(route: .roomList, expectedState: .roomList(selectedRoomID: nil))
|
||||||
|
XCTAssertNil(detailNavigationStack?.rootCoordinator)
|
||||||
|
XCTAssertNil(detailCoordinator)
|
||||||
|
|
||||||
|
try await process(route: .room(roomID: "1"), expectedState: .roomList(selectedRoomID: "1"))
|
||||||
|
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
|
||||||
|
XCTAssertNotNil(detailCoordinator)
|
||||||
|
|
||||||
|
try await process(route: .room(roomID: "2"), expectedState: .roomList(selectedRoomID: "2"))
|
||||||
|
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
|
||||||
|
XCTAssertNotNil(detailCoordinator)
|
||||||
|
|
||||||
|
try await process(route: .roomList, expectedState: .roomList(selectedRoomID: nil))
|
||||||
|
XCTAssertNil(detailNavigationStack?.rootCoordinator)
|
||||||
|
XCTAssertNil(detailCoordinator)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testRoomDetailsPresentation() async throws {
|
||||||
|
try await process(route: .roomDetails(roomID: "1"), expectedState: .roomList(selectedRoomID: "1"))
|
||||||
|
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomDetailsScreenCoordinator)
|
||||||
|
XCTAssertNotNil(detailCoordinator)
|
||||||
|
|
||||||
|
try await process(route: .roomList, expectedState: .roomList(selectedRoomID: nil))
|
||||||
|
XCTAssertNil(detailNavigationStack?.rootCoordinator)
|
||||||
|
XCTAssertNil(detailCoordinator)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testStackUnwinding() async throws {
|
||||||
|
try await process(route: .roomDetails(roomID: "1"), expectedState: .roomList(selectedRoomID: "1"))
|
||||||
|
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomDetailsScreenCoordinator)
|
||||||
|
XCTAssertNotNil(detailCoordinator)
|
||||||
|
|
||||||
|
try await process(route: .room(roomID: "2"), expectedState: .roomList(selectedRoomID: "2"))
|
||||||
|
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
|
||||||
|
XCTAssertNotNil(detailCoordinator)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testNoOp() async throws {
|
||||||
|
try await process(route: .roomDetails(roomID: "1"), expectedState: .roomList(selectedRoomID: "1"))
|
||||||
|
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomDetailsScreenCoordinator)
|
||||||
|
XCTAssertNotNil(detailCoordinator)
|
||||||
|
|
||||||
|
let unexpectedFulfillment = deferFailure(userSessionFlowCoordinator.statePublisher, timeout: 1) { _ in true }
|
||||||
|
userSessionFlowCoordinator.handleAppRoute(.roomDetails(roomID: "1"), animated: true)
|
||||||
|
try await unexpectedFulfillment.fulfill()
|
||||||
|
|
||||||
|
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomDetailsScreenCoordinator)
|
||||||
|
XCTAssertNotNil(detailCoordinator)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSwitchToDifferentDetails() async throws {
|
||||||
|
try await process(route: .roomDetails(roomID: "1"), expectedState: .roomList(selectedRoomID: "1"))
|
||||||
|
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomDetailsScreenCoordinator)
|
||||||
|
XCTAssertNotNil(detailCoordinator)
|
||||||
|
|
||||||
|
try await process(route: .roomDetails(roomID: "2"), expectedState: .roomList(selectedRoomID: "2"))
|
||||||
|
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomDetailsScreenCoordinator)
|
||||||
|
XCTAssertNotNil(detailCoordinator)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testPushDetails() async throws {
|
||||||
|
try await process(route: .room(roomID: "1"), expectedState: .roomList(selectedRoomID: "1"))
|
||||||
|
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
|
||||||
|
XCTAssertNotNil(detailCoordinator)
|
||||||
|
|
||||||
|
let unexpectedFulfillment = deferFailure(userSessionFlowCoordinator.statePublisher, timeout: 1) { _ in true }
|
||||||
|
userSessionFlowCoordinator.handleAppRoute(.roomDetails(roomID: "1"), animated: true)
|
||||||
|
try await unexpectedFulfillment.fulfill()
|
||||||
|
|
||||||
|
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
|
||||||
|
XCTAssertEqual(detailNavigationStack?.stackCoordinators.count, 1)
|
||||||
|
XCTAssertTrue(detailNavigationStack?.stackCoordinators.first is RoomDetailsScreenCoordinator)
|
||||||
|
XCTAssertNotNil(detailCoordinator)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testReplaceDetailsWithTimeline() async throws {
|
||||||
|
try await process(route: .roomDetails(roomID: "1"), expectedState: .roomList(selectedRoomID: "1"))
|
||||||
|
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomDetailsScreenCoordinator)
|
||||||
|
XCTAssertNotNil(detailCoordinator)
|
||||||
|
|
||||||
|
try await process(route: .room(roomID: "1"), expectedState: .roomList(selectedRoomID: "1"))
|
||||||
|
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
|
||||||
|
XCTAssertNotNil(detailCoordinator)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Private
|
||||||
|
|
||||||
|
private func process(route: AppRoute, expectedState: UserSessionFlowCoordinatorStateMachine.State) async throws {
|
||||||
|
// Sometimes the state machine's state changes before the coordinators have updated the stack.
|
||||||
|
let delayedPublisher = userSessionFlowCoordinator.statePublisher.delay(for: .milliseconds(10), scheduler: DispatchQueue.main)
|
||||||
|
|
||||||
|
let deferred = deferFulfillment(delayedPublisher) { $0 == expectedState }
|
||||||
|
userSessionFlowCoordinator.handleAppRoute(route, animated: true)
|
||||||
|
try await deferred.fulfill()
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user