Hook up the RoomThreadList to the RoomFlowCoordinator state machine and presenting threads.

This commit is contained in:
Stefan Ceriu
2026-04-01 09:27:39 +03:00
committed by Stefan Ceriu
parent 212cd42161
commit a358cacf31
9 changed files with 67 additions and 17 deletions
@@ -394,6 +394,11 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
case (.room, .presentPinnedEventsTimeline, .pinnedEventsTimeline): case (.room, .presentPinnedEventsTimeline, .pinnedEventsTimeline):
startPinnedEventsTimelineFlow() startPinnedEventsTimelineFlow()
// Thread List
case (.room, .presentThreadList, .threadList):
Task { await self.presentThreadList(animated: animated) }
// Thread // Thread
case (_, .presentThread(let threadRootEventID, let focusEventID), .thread): case (_, .presentThread(let threadRootEventID, let focusEventID), .thread):
@@ -721,9 +726,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
case .presentKnockRequestsList: case .presentKnockRequestsList:
stateMachine.tryEvent(.presentKnockRequestsListScreen) stateMachine.tryEvent(.presentKnockRequestsListScreen)
case .presentThreadList: case .presentThreadList:
Task { stateMachine.tryEvent(.presentThreadList, userInfo: EventUserInfo(animated: animated))
await self.presentThreadList(animated: true)
}
case .presentThread(let threadRootEventID, let focussedEventID): case .presentThread(let threadRootEventID, let focussedEventID):
stateMachine.tryEvent(.presentThread(threadRootEventID: threadRootEventID, focusEventID: focussedEventID)) stateMachine.tryEvent(.presentThread(threadRootEventID: threadRootEventID, focusEventID: focussedEventID))
case .presentRoom(let roomID, let via): case .presentRoom(let roomID, let via):
@@ -738,12 +741,21 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
} }
private func presentThreadList(animated: Bool) async { private func presentThreadList(animated: Bool) async {
let coordinator = await RoomThreadListScreenCoordinator(parameters: .init(threadListServiceProxy: roomProxy.threadListService(), let coordinator = RoomThreadListScreenCoordinator(parameters: .init(threadListServiceProxy: roomProxy.threadListService(),
mediaProvider: userSession.mediaProvider)) 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 { private func presentThread(threadRootEventID: String, focusEventID: String?, animated: Bool) async {
@@ -59,6 +59,7 @@ extension RoomFlowCoordinator {
case initial case initial
case joinRoomScreen case joinRoomScreen
case room case room
case threadList
case thread(threadRootEventID: String, previousState: State) case thread(threadRootEventID: String, previousState: State)
case roomDetails(isRoot: Bool) case roomDetails(isRoot: Bool)
case roomDetailsEditScreen case roomDetailsEditScreen
@@ -113,6 +114,9 @@ extension RoomFlowCoordinator {
case presentThread(threadRootEventID: String, focusEventID: String?) case presentThread(threadRootEventID: String, focusEventID: String?)
case dismissThread case dismissThread
case presentThreadList
case dismissThreadList
case startSpaceFlow case startSpaceFlow
case finishedSpaceFlow case finishedSpaceFlow
@@ -236,6 +240,14 @@ extension RoomFlowCoordinator {
case (.pinnedEventsTimeline(let previousState), .dismissPinnedEventsTimeline): case (.pinnedEventsTimeline(let previousState), .dismissPinnedEventsTimeline):
return previousState 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 // Thread
case (.room, .presentThread(let threadRootEventID, _)): case (.room, .presentThread(let threadRootEventID, _)):
return .thread(threadRootEventID: threadRootEventID, previousState: fromState) return .thread(threadRootEventID: threadRootEventID, previousState: fromState)
@@ -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 threadListServiceCallsCount += 1
if let threadListServiceClosure = threadListServiceClosure { if let threadListServiceClosure = threadListServiceClosure {
return await threadListServiceClosure() return threadListServiceClosure()
} else { } else {
return threadListServiceReturnValue return threadListServiceReturnValue
} }
@@ -13,7 +13,9 @@ struct RoomThreadListScreenCoordinatorParameters {
let mediaProvider: MediaProviderProtocol let mediaProvider: MediaProviderProtocol
} }
enum RoomThreadListScreenCoordinatorAction { } enum RoomThreadListScreenCoordinatorAction {
case presentThread(threadRootEventID: String)
}
final class RoomThreadListScreenCoordinator: CoordinatorProtocol { final class RoomThreadListScreenCoordinator: CoordinatorProtocol {
private let parameters: RoomThreadListScreenCoordinatorParameters private let parameters: RoomThreadListScreenCoordinatorParameters
@@ -35,7 +37,13 @@ final class RoomThreadListScreenCoordinator: CoordinatorProtocol {
func start() { func start() {
viewModel.actionsPublisher.sink { [weak self] action in viewModel.actionsPublisher.sink { [weak self] action in
guard let self else { return }
MXLog.info("Coordinator: received view model action: \(action)") MXLog.info("Coordinator: received view model action: \(action)")
switch action {
case .presentThread(let threadRootEventID):
actionsSubject.send(.presentThread(threadRootEventID: threadRootEventID))
}
} }
.store(in: &cancellables) .store(in: &cancellables)
} }
@@ -7,7 +7,9 @@
import Foundation import Foundation
enum RoomThreadListScreenViewModelAction { } enum RoomThreadListScreenViewModelAction {
case presentThread(threadRootEventID: String)
}
struct RoomThreadListScreenViewState: BindableState { struct RoomThreadListScreenViewState: BindableState {
var items = [RoomThreadListItem]() var items = [RoomThreadListItem]()
@@ -22,4 +24,5 @@ struct RoomThreadListScreenViewStateBindings { }
enum RoomThreadListScreenViewAction { enum RoomThreadListScreenViewAction {
case oldestItemDidAppear case oldestItemDidAppear
case oldestItemDidDisappear case oldestItemDidDisappear
case tappedThread(threadRootEventID: String)
} }
@@ -58,6 +58,8 @@ class RoomThreadListScreenViewModel: RoomThreadListScreenViewModelType, RoomThre
} }
case .oldestItemDidDisappear: case .oldestItemDidDisappear:
isOldestItemVisible = false isOldestItemVisible = false
case .tappedThread(let threadRootEventID):
actionsSubject.send(.presentThread(threadRootEventID: threadRootEventID))
} }
} }
@@ -15,9 +15,14 @@ struct RoomThreadListScreen: View {
ScrollView { ScrollView {
LazyVStack(spacing: 0) { LazyVStack(spacing: 0) {
ForEach(context.viewState.items) { item in ForEach(context.viewState.items) { item in
RoomThreadListCell(item: item, mediaProvider: context.mediaProvider) Button {
.padding(.horizontal, 16) context.send(viewAction: .tappedThread(threadRootEventID: item.id))
.padding(.vertical, 8) } label: {
RoomThreadListCell(item: item, mediaProvider: context.mediaProvider)
.padding(.horizontal, 16)
.padding(.vertical, 8)
}
.buttonStyle(ThreadCellButtonStyle())
} }
footer 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 // MARK: - Previews
struct RoomThreadListScreen_Previews: PreviewProvider, TestablePreview { struct RoomThreadListScreen_Previews: PreviewProvider, TestablePreview {
@@ -203,7 +203,7 @@ class JoinedRoomProxy: JoinedRoomProxyProtocol {
} }
} }
func threadListService() async -> RoomThreadListServiceProxyProtocol { func threadListService() -> RoomThreadListServiceProxyProtocol {
RoomThreadListProxy(threadListService: room.threadListService(), eventStringBuilder: eventStringBuilder) RoomThreadListProxy(threadListService: room.threadListService(), eventStringBuilder: eventStringBuilder)
} }
@@ -98,7 +98,7 @@ protocol JoinedRoomProxyProtocol: RoomProxyProtocol {
func threadTimeline(eventID: String) async -> Result<TimelineProxyProtocol, RoomProxyError> func threadTimeline(eventID: String) async -> Result<TimelineProxyProtocol, RoomProxyError>
func threadListService() async -> RoomThreadListServiceProxyProtocol func threadListService() -> RoomThreadListServiceProxyProtocol
func loadOrFetchEventDetails(for eventID: String) async -> Result<TimelineEvent, RoomProxyError> func loadOrFetchEventDetails(for eventID: String) async -> Result<TimelineEvent, RoomProxyError>