Skip to content

Commit

Permalink
Hard code the room flow to a single room proxy. (#2599)
Browse files Browse the repository at this point in the history
* Don't give the room flow a split coordinator any more.
  • Loading branch information
pixlwave authored Mar 27, 2024
1 parent e424a02 commit 144f3a7
Show file tree
Hide file tree
Showing 11 changed files with 624 additions and 401 deletions.
4 changes: 4 additions & 0 deletions ElementX.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -596,6 +596,7 @@
9095B9E40DB5CF8BA26CE0D8 /* ReactionsSummaryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 153726EDCE1ACBB3D466A916 /* ReactionsSummaryView.swift */; };
90DF83A6A347F7EE7EDE89EE /* AttributedStringBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF25E364AE85090A70AE4644 /* AttributedStringBuilderTests.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 */; };
91ABC91758A6E4A5FAA2E9C4 /* ReadReceipt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314F1C79850BE46E8ABEAFCB /* ReadReceipt.swift */; };
91C6AC0E9D2B9C0C76CC6AD4 /* RoomDirectorySearchScreenScreenModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3984C93B8E9B10C92DADF9EE /* RoomDirectorySearchScreenScreenModelProtocol.swift */; };
Expand Down Expand Up @@ -1656,6 +1657,7 @@
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>"; };
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>"; };
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>"; };
Expand Down Expand Up @@ -3535,6 +3537,7 @@
7583EAC171059A86B767209F /* MediaProvider */,
7DBC911559934065993A5FF4 /* NotificationManager */,
1C62F5382CC9D9F7DCEC344A /* UserDiscoveryService */,
912B88352BADD6EA00CD00F6 /* UserSessionFlowCoordinatorTests.swift */,
);
path = Sources;
sourceTree = "<group>";
Expand Down Expand Up @@ -5624,6 +5627,7 @@
50381244BA280451771BE3ED /* PINTextFieldTests.swift in Sources */,
27E9263DA75E266690A37EB1 /* PermalinkBuilderTests.swift in Sources */,
3982E60F9C126437D5E488A3 /* PillContextTests.swift in Sources */,
912B88362BADD6EA00CD00F6 /* UserSessionFlowCoordinatorTests.swift in Sources */,
FF7E8ECC8E7E1D1851517536 /* PollFormScreenViewModelTests.swift in Sources */,
D415764645491F10344FC6AC /* Publisher.swift in Sources */,
D53B80EF02C1062E68659EDD /* ReportContentViewModelTests.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,6 @@ protocol WindowManagerProtocol: AnyObject, OrientationManagerProtocol {

func hideGlobalSearch()
}

// sourcery: AutoMockable
extension WindowManagerProtocol { }
473 changes: 175 additions & 298 deletions ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift

Large diffs are not rendered by default.

134 changes: 85 additions & 49 deletions ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,13 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
private let windowManager: WindowManagerProtocol
private let bugReportService: BugReportServiceProtocol
private let appSettings: AppSettings
private let analytics: AnalyticsService

private let stateMachine: UserSessionFlowCoordinatorStateMachine

private let roomFlowCoordinator: RoomFlowCoordinator
// periphery:ignore - retaining purpose
private var roomFlowCoordinator: RoomFlowCoordinator?
private let roomTimelineControllerFactory: RoomTimelineControllerFactoryProtocol

private let settingsFlowCoordinator: SettingsFlowCoordinator

Expand All @@ -58,6 +61,9 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
actionsSubject.eraseToAnyPublisher()
}

/// For testing purposes.
var statePublisher: AnyPublisher<UserSessionFlowCoordinatorStateMachine.State, Never> { stateMachine.statePublisher }

init(userSession: UserSessionProtocol,
navigationRootCoordinator: NavigationRootCoordinator,
windowManager: WindowManagerProtocol,
Expand All @@ -73,24 +79,16 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
self.navigationRootCoordinator = navigationRootCoordinator
self.windowManager = windowManager
self.bugReportService = bugReportService
self.roomTimelineControllerFactory = roomTimelineControllerFactory
self.appSettings = appSettings
self.analytics = analytics

navigationSplitCoordinator = NavigationSplitCoordinator(placeholderCoordinator: PlaceholderScreenCoordinator())

sidebarNavigationStackCoordinator = NavigationStackCoordinator(navigationSplitCoordinator: navigationSplitCoordinator)
detailNavigationStackCoordinator = NavigationStackCoordinator(navigationSplitCoordinator: navigationSplitCoordinator)

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,
windowManager: windowManager,
Expand Down Expand Up @@ -125,28 +123,6 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
}
.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
guard let self else { return }

Expand Down Expand Up @@ -202,11 +178,15 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {

switch appRoute {
case .room(let roomID):
Task {
await self.handleRoomRoute(roomID: roomID, animated: animated)
Task { 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:
self.roomFlowCoordinator.handleAppRoute(appRoute, animated: animated)
case .roomList, .roomMemberDetails:
self.roomFlowCoordinator?.handleAppRoute(appRoute, animated: animated)
case .genericCallLink(let url):
self.navigationSplitCoordinator.setSheetCoordinator(GenericCallLinkCoordinator(parameters: .init(url: url)), animated: animated)
case .oidcCallback:
Expand All @@ -221,19 +201,19 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
switch await userSession.clientProxy.roomForIdentifier(roomID)?.membership {
case .invited:
if UIDevice.current.isPhone {
roomFlowCoordinator.clearRoute(animated: animated)
roomFlowCoordinator?.clearRoute(animated: animated)
}
stateMachine.processEvent(.showInvitesScreen, userInfo: .init(animated: animated))
case .joined:
roomFlowCoordinator.handleAppRoute(.room(roomID: roomID), animated: animated)
stateMachine.processEvent(.selectRoom(roomID: roomID, showingRoomDetails: false), userInfo: .init(animated: animated))
case .left, .none:
// Do nothing but maybe we should ask design to have some kind of error state
break
}
}

func clearRoute(animated: Bool) {
roomFlowCoordinator.clearRoute(animated: animated)
roomFlowCoordinator?.handleAppRoute(.roomList, animated: animated)
}

// MARK: - Private
Expand Down Expand Up @@ -268,15 +248,15 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
presentHomeScreen()
attemptStartingOnboarding()

case(.roomList, .selectRoom, .roomList):
break
case(.roomList(let currentRoomID), .selectRoom(let roomID, let showingRoomDetails), .roomList):

Check warning on line 251 in ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift

View workflow job for this annotation

GitHub Actions / Tests

immutable value 'currentRoomID' was never used; consider replacing with '_' or removing it
Task { await self.presentRoomFlow(roomID: roomID, showingRoomDetails: showingRoomDetails, animated: animated) }
case(.roomList, .deselectRoom, .roomList):
break
tearDownRoomFlow(animated: animated)

case (.invitesScreen, .selectRoom, .invitesScreen):
break
case (.invitesScreen, .deselectRoom, .invitesScreen):
break
tearDownRoomFlow(animated: animated)

case (.roomList, .showSettingsScreen, .settingsScreen):
break
Expand Down Expand Up @@ -353,13 +333,13 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {

switch action {
case .presentRoom(let roomID):
roomFlowCoordinator.handleAppRoute(.room(roomID: roomID), animated: true)
stateMachine.processEvent(.selectRoom(roomID: roomID, showingRoomDetails: false))
case .presentRoomDetails(let roomID):
roomFlowCoordinator.handleAppRoute(.roomDetails(roomID: roomID), animated: true)
stateMachine.processEvent(.selectRoom(roomID: roomID, showingRoomDetails: true))
case .roomLeft(let roomID):
if case .roomList(selectedRoomID: let selectedRoomID) = stateMachine.state,
selectedRoomID == roomID {
roomFlowCoordinator.handleAppRoute(.roomList, animated: true)
clearRoute(animated: true)
}
case .presentSettingsScreen:
settingsFlowCoordinator.handleAppRoute(.settings, animated: true)
Expand Down Expand Up @@ -431,6 +411,62 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
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

private func presentStartChat(animated: Bool) {
Expand All @@ -451,7 +487,7 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
self.navigationSplitCoordinator.setSheetCoordinator(nil)
case .openRoom(let roomID):
self.navigationSplitCoordinator.setSheetCoordinator(nil)
self.roomFlowCoordinator.handleAppRoute(.room(roomID: roomID), animated: true)
self.stateMachine.processEvent(.selectRoom(roomID: roomID, showingRoomDetails: false))
}
}
.store(in: &cancellables)
Expand All @@ -473,7 +509,7 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
.sink { [weak self] action in
switch action {
case .openRoom(let roomID):
self?.roomFlowCoordinator.handleAppRoute(.room(roomID: roomID), animated: true)
self?.stateMachine.processEvent(.selectRoom(roomID: roomID, showingRoomDetails: false))
}
}
.store(in: &cancellables)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
// limitations under the License.
//

import Combine
import Foundation
import SwiftState

Expand All @@ -38,11 +39,27 @@ class UserSessionFlowCoordinatorStateMachine {
/// Showing invites list screen
case invitesScreen(selectedRoomID: String?)

// Showing the logout flows
/// Showing the logout flows
case logoutConfirmationScreen(selectedRoomID: String?)

// Showing Room Directory Search screen
/// Showing Room Directory Search screen
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 {
Expand All @@ -56,7 +73,7 @@ class UserSessionFlowCoordinatorStateMachine {

/// Request presentation for a particular room
/// - Parameter roomID:the room identifier
case selectRoom(roomID: String)
case selectRoom(roomID: String, showingRoomDetails: Bool)
/// The room screen has been dismissed
case deselectRoom

Expand Down Expand Up @@ -96,6 +113,11 @@ class UserSessionFlowCoordinatorStateMachine {
stateMachine.state
}

var stateSubject = PassthroughSubject<State, Never>()
var statePublisher: AnyPublisher<State, Never> {
stateSubject.eraseToAnyPublisher()
}

init() {
stateMachine = StateMachine(state: .initial)
configure()
Expand All @@ -106,9 +128,9 @@ class UserSessionFlowCoordinatorStateMachine {

stateMachine.addRouteMapping { event, fromState, _ in
switch (fromState, event) {
case (.roomList, .selectRoom(let roomID)):
case (.roomList, .selectRoom(let roomID, _)):
return .roomList(selectedRoomID: roomID)
case (.invitesScreen, .selectRoom(let roomID)):
case (.invitesScreen, .selectRoom(let roomID, _)):
return .invitesScreen(selectedRoomID: roomID)
case (.roomList, .deselectRoom):
return .roomList(selectedRoomID: nil)
Expand Down Expand Up @@ -160,6 +182,10 @@ class UserSessionFlowCoordinatorStateMachine {
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
Expand Down
4 changes: 3 additions & 1 deletion ElementX/Sources/Mocks/ClientProxyMock.swift
Original file line number Diff line number Diff line change
Expand Up @@ -84,12 +84,14 @@ extension ClientProxyMock {
guard let room = self?.roomSummaryProvider?.roomListPublisher.value.first(where: { $0.id == identifier }) else {
return nil
}

let roomID = room.id ?? UUID().uuidString

switch room {
case .empty:
return await RoomProxyMock(with: .init(name: "Empty room"))
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))
}
}
}
Expand Down
Loading

0 comments on commit 144f3a7

Please sign in to comment.