From a358cacf31f2755246db24c37d7292c8c1acdccf Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Wed, 1 Apr 2026 09:27:39 +0300 Subject: [PATCH] Hook up the RoomThreadList to the RoomFlowCoordinator state machine and presenting threads. --- .../RoomFlowCoordinator.swift | 26 ++++++++++++++----- .../RoomFlowCoordinatorStateMachine.swift | 12 +++++++++ .../Mocks/Generated/GeneratedMocks.swift | 6 ++--- .../RoomThreadListScreenCoordinator.swift | 10 ++++++- .../RoomThreadListScreenModels.swift | 5 +++- .../RoomThreadListScreenViewModel.swift | 2 ++ .../View/RoomThreadListScreen.swift | 19 +++++++++++--- .../Services/Room/JoinedRoomProxy.swift | 2 +- .../Services/Room/RoomProxyProtocol.swift | 2 +- 9 files changed, 67 insertions(+), 17 deletions(-) diff --git a/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift index dff39c0e2..9d26060c8 100644 --- a/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift @@ -394,6 +394,11 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol { case (.room, .presentPinnedEventsTimeline, .pinnedEventsTimeline): startPinnedEventsTimelineFlow() + // Thread List + + case (.room, .presentThreadList, .threadList): + Task { await self.presentThreadList(animated: animated) } + // Thread case (_, .presentThread(let threadRootEventID, let focusEventID), .thread): @@ -721,9 +726,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol { case .presentKnockRequestsList: stateMachine.tryEvent(.presentKnockRequestsListScreen) case .presentThreadList: - Task { - await self.presentThreadList(animated: true) - } + stateMachine.tryEvent(.presentThreadList, userInfo: EventUserInfo(animated: animated)) case .presentThread(let threadRootEventID, let focussedEventID): stateMachine.tryEvent(.presentThread(threadRootEventID: threadRootEventID, focusEventID: focussedEventID)) case .presentRoom(let roomID, let via): @@ -738,12 +741,21 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol { } private func presentThreadList(animated: Bool) async { - let coordinator = await RoomThreadListScreenCoordinator(parameters: .init(threadListServiceProxy: roomProxy.threadListService(), - mediaProvider: userSession.mediaProvider)) + let coordinator = RoomThreadListScreenCoordinator(parameters: .init(threadListServiceProxy: roomProxy.threadListService(), + mediaProvider: userSession.mediaProvider)) - coordinator.actionsPublisher.sink { [weak self] _ in }.store(in: &cancellables) + coordinator.actionsPublisher.sink { [weak self] action in + guard let self else { return } + switch action { + case .presentThread(let threadRootEventID): + stateMachine.tryEvent(.presentThread(threadRootEventID: threadRootEventID, focusEventID: nil)) + } + }.store(in: &cancellables) - navigationStackCoordinator.push(coordinator, animated: animated) { [weak self] in } + navigationStackCoordinator.push(coordinator, animated: animated) { [weak self] in + guard let self else { return } + stateMachine.tryEvent(.dismissThreadList) + } } private func presentThread(threadRootEventID: String, focusEventID: String?, animated: Bool) async { diff --git a/ElementX/Sources/FlowCoordinators/RoomFlowCoordinatorStateMachine.swift b/ElementX/Sources/FlowCoordinators/RoomFlowCoordinatorStateMachine.swift index 2a03f1ae7..d20133c63 100644 --- a/ElementX/Sources/FlowCoordinators/RoomFlowCoordinatorStateMachine.swift +++ b/ElementX/Sources/FlowCoordinators/RoomFlowCoordinatorStateMachine.swift @@ -59,6 +59,7 @@ extension RoomFlowCoordinator { case initial case joinRoomScreen case room + case threadList case thread(threadRootEventID: String, previousState: State) case roomDetails(isRoot: Bool) case roomDetailsEditScreen @@ -113,6 +114,9 @@ extension RoomFlowCoordinator { case presentThread(threadRootEventID: String, focusEventID: String?) case dismissThread + + case presentThreadList + case dismissThreadList case startSpaceFlow case finishedSpaceFlow @@ -236,6 +240,14 @@ extension RoomFlowCoordinator { case (.pinnedEventsTimeline(let previousState), .dismissPinnedEventsTimeline): return previousState + // Thread List + case (.room, .presentThreadList): + return .threadList + case (.threadList, .dismissThreadList): + return .room + case (.threadList, .presentThread(let threadRootEventID, _)): + return .thread(threadRootEventID: threadRootEventID, previousState: fromState) + // Thread case (.room, .presentThread(let threadRootEventID, _)): return .thread(threadRootEventID: threadRootEventID, previousState: fromState) diff --git a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift index 75664dd0d..9cbb227b7 100644 --- a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift +++ b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift @@ -6984,12 +6984,12 @@ class JoinedRoomProxyMock: JoinedRoomProxyProtocol, @unchecked Sendable { } } } - var threadListServiceClosure: (() async -> RoomThreadListServiceProxyProtocol)? + var threadListServiceClosure: (() -> RoomThreadListServiceProxyProtocol)? - func threadListService() async -> RoomThreadListServiceProxyProtocol { + func threadListService() -> RoomThreadListServiceProxyProtocol { threadListServiceCallsCount += 1 if let threadListServiceClosure = threadListServiceClosure { - return await threadListServiceClosure() + return threadListServiceClosure() } else { return threadListServiceReturnValue } diff --git a/ElementX/Sources/Screens/RoomThreadListScreen/RoomThreadListScreenCoordinator.swift b/ElementX/Sources/Screens/RoomThreadListScreen/RoomThreadListScreenCoordinator.swift index 054814157..c99f69f6f 100644 --- a/ElementX/Sources/Screens/RoomThreadListScreen/RoomThreadListScreenCoordinator.swift +++ b/ElementX/Sources/Screens/RoomThreadListScreen/RoomThreadListScreenCoordinator.swift @@ -13,7 +13,9 @@ struct RoomThreadListScreenCoordinatorParameters { let mediaProvider: MediaProviderProtocol } -enum RoomThreadListScreenCoordinatorAction { } +enum RoomThreadListScreenCoordinatorAction { + case presentThread(threadRootEventID: String) +} final class RoomThreadListScreenCoordinator: CoordinatorProtocol { private let parameters: RoomThreadListScreenCoordinatorParameters @@ -35,7 +37,13 @@ final class RoomThreadListScreenCoordinator: CoordinatorProtocol { func start() { viewModel.actionsPublisher.sink { [weak self] action in + guard let self else { return } MXLog.info("Coordinator: received view model action: \(action)") + + switch action { + case .presentThread(let threadRootEventID): + actionsSubject.send(.presentThread(threadRootEventID: threadRootEventID)) + } } .store(in: &cancellables) } diff --git a/ElementX/Sources/Screens/RoomThreadListScreen/RoomThreadListScreenModels.swift b/ElementX/Sources/Screens/RoomThreadListScreen/RoomThreadListScreenModels.swift index 1d0999d5b..3a4a47a3a 100644 --- a/ElementX/Sources/Screens/RoomThreadListScreen/RoomThreadListScreenModels.swift +++ b/ElementX/Sources/Screens/RoomThreadListScreen/RoomThreadListScreenModels.swift @@ -7,7 +7,9 @@ import Foundation -enum RoomThreadListScreenViewModelAction { } +enum RoomThreadListScreenViewModelAction { + case presentThread(threadRootEventID: String) +} struct RoomThreadListScreenViewState: BindableState { var items = [RoomThreadListItem]() @@ -22,4 +24,5 @@ struct RoomThreadListScreenViewStateBindings { } enum RoomThreadListScreenViewAction { case oldestItemDidAppear case oldestItemDidDisappear + case tappedThread(threadRootEventID: String) } diff --git a/ElementX/Sources/Screens/RoomThreadListScreen/RoomThreadListScreenViewModel.swift b/ElementX/Sources/Screens/RoomThreadListScreen/RoomThreadListScreenViewModel.swift index 502bb88c0..64a7556ad 100644 --- a/ElementX/Sources/Screens/RoomThreadListScreen/RoomThreadListScreenViewModel.swift +++ b/ElementX/Sources/Screens/RoomThreadListScreen/RoomThreadListScreenViewModel.swift @@ -58,6 +58,8 @@ class RoomThreadListScreenViewModel: RoomThreadListScreenViewModelType, RoomThre } case .oldestItemDidDisappear: isOldestItemVisible = false + case .tappedThread(let threadRootEventID): + actionsSubject.send(.presentThread(threadRootEventID: threadRootEventID)) } } diff --git a/ElementX/Sources/Screens/RoomThreadListScreen/View/RoomThreadListScreen.swift b/ElementX/Sources/Screens/RoomThreadListScreen/View/RoomThreadListScreen.swift index f3d43e68d..b24593fed 100644 --- a/ElementX/Sources/Screens/RoomThreadListScreen/View/RoomThreadListScreen.swift +++ b/ElementX/Sources/Screens/RoomThreadListScreen/View/RoomThreadListScreen.swift @@ -15,9 +15,14 @@ struct RoomThreadListScreen: View { ScrollView { LazyVStack(spacing: 0) { ForEach(context.viewState.items) { item in - RoomThreadListCell(item: item, mediaProvider: context.mediaProvider) - .padding(.horizontal, 16) - .padding(.vertical, 8) + Button { + context.send(viewAction: .tappedThread(threadRootEventID: item.id)) + } label: { + RoomThreadListCell(item: item, mediaProvider: context.mediaProvider) + .padding(.horizontal, 16) + .padding(.vertical, 8) + } + .buttonStyle(ThreadCellButtonStyle()) } footer @@ -137,6 +142,14 @@ private struct RoomThreadListCell: View { } } +private struct ThreadCellButtonStyle: ButtonStyle { + func makeBody(configuration: Configuration) -> some View { + configuration.label + .background(configuration.isPressed ? Color.compound.bgSubtleSecondary : Color.compound.bgCanvasDefault) + .contentShape(Rectangle()) + } +} + // MARK: - Previews struct RoomThreadListScreen_Previews: PreviewProvider, TestablePreview { diff --git a/ElementX/Sources/Services/Room/JoinedRoomProxy.swift b/ElementX/Sources/Services/Room/JoinedRoomProxy.swift index 68628a7ef..913060eb0 100644 --- a/ElementX/Sources/Services/Room/JoinedRoomProxy.swift +++ b/ElementX/Sources/Services/Room/JoinedRoomProxy.swift @@ -203,7 +203,7 @@ class JoinedRoomProxy: JoinedRoomProxyProtocol { } } - func threadListService() async -> RoomThreadListServiceProxyProtocol { + func threadListService() -> RoomThreadListServiceProxyProtocol { RoomThreadListProxy(threadListService: room.threadListService(), eventStringBuilder: eventStringBuilder) } diff --git a/ElementX/Sources/Services/Room/RoomProxyProtocol.swift b/ElementX/Sources/Services/Room/RoomProxyProtocol.swift index 3d78f34b6..68a1e08a1 100644 --- a/ElementX/Sources/Services/Room/RoomProxyProtocol.swift +++ b/ElementX/Sources/Services/Room/RoomProxyProtocol.swift @@ -98,7 +98,7 @@ protocol JoinedRoomProxyProtocol: RoomProxyProtocol { func threadTimeline(eventID: String) async -> Result - func threadListService() async -> RoomThreadListServiceProxyProtocol + func threadListService() -> RoomThreadListServiceProxyProtocol func loadOrFetchEventDetails(for eventID: String) async -> Result