From 210a0dba1a138ded88a827a1aaeb1819b4dfcc95 Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Wed, 28 May 2025 15:13:00 +0300 Subject: [PATCH] Setup action forwarding for threaded timelines. --- .../RoomFlowCoordinator.swift | 28 +++++++ .../ThreadTimelineScreenCoordinator.swift | 78 ++++++++++++------- .../TimelineItemMenuActionProvider.swift | 2 +- 3 files changed, 81 insertions(+), 27 deletions(-) diff --git a/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift index 0caa40a36..6dbbfe1a8 100644 --- a/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift @@ -830,6 +830,34 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol { emojiProvider: emojiProvider, timelineControllerFactory: timelineControllerFactory, clientProxy: userSession.clientProxy)) + + coordinator.actions.sink { [weak self] action in + guard let self else { return } + + switch action { + case .presentReportContent(let itemID, let senderID): + stateMachine.tryEvent(.presentReportContent(itemID: itemID, senderID: senderID)) + case .presentMediaUploadPicker(let source): + stateMachine.tryEvent(.presentMediaUploadPicker(source: source)) + case .presentMediaUploadPreviewScreen(let url): + stateMachine.tryEvent(.presentMediaUploadPreview(fileURL: url)) + case .presentLocationPicker: + stateMachine.tryEvent(.presentMapNavigator(interactionMode: .picker)) + case .presentPollForm(let mode): + stateMachine.tryEvent(.presentPollForm(mode: mode)) + case .presentLocationViewer(_, let geoURI, let description): + stateMachine.tryEvent(.presentMapNavigator(interactionMode: .viewOnly(geoURI: geoURI, description: description))) + case .presentEmojiPicker(let itemID, let selectedEmojis): + stateMachine.tryEvent(.presentEmojiPicker(itemID: itemID, selectedEmojis: selectedEmojis)) + case .presentRoomMemberDetails(let userID): + stateMachine.tryEvent(.presentRoomMemberDetails(userID: userID)) + case .presentMessageForwarding(let forwardingItem): + stateMachine.tryEvent(.presentMessageForwarding(forwardingItem: forwardingItem)) + case .presentResolveSendFailure(let failure, let sendHandle): + stateMachine.tryEvent(.presentResolveSendFailure(failure: failure, sendHandle: sendHandle)) + } + } + .store(in: &cancellables) navigationStackCoordinator.push(coordinator) { [weak self] in self?.stateMachine.tryEvent(.dismissThread) diff --git a/ElementX/Sources/Screens/ThreadTimelineScreen/ThreadTimelineScreenCoordinator.swift b/ElementX/Sources/Screens/ThreadTimelineScreen/ThreadTimelineScreenCoordinator.swift index 7efaa00fe..705eea389 100644 --- a/ElementX/Sources/Screens/ThreadTimelineScreen/ThreadTimelineScreenCoordinator.swift +++ b/ElementX/Sources/Screens/ThreadTimelineScreen/ThreadTimelineScreenCoordinator.swift @@ -21,11 +21,16 @@ struct ThreadTimelineScreenCoordinatorParameters { } enum ThreadTimelineScreenCoordinatorAction { - case dismiss - case displayUser(userID: String) - case presentLocationViewer(geoURI: GeoURI, description: String?) - case displayMessageForwarding(forwardingItem: MessageForwardingItem) - case displayRoomScreenWithFocussedPin(eventID: String) + case presentReportContent(itemID: TimelineItemIdentifier, senderID: String) + case presentMediaUploadPicker(MediaPickerScreenSource) + case presentMediaUploadPreviewScreen(URL) + case presentLocationPicker + case presentPollForm(mode: PollFormMode) + case presentLocationViewer(body: String, geoURI: GeoURI, description: String?) + case presentEmojiPicker(itemID: TimelineItemIdentifier, selectedEmojis: Set) + case presentRoomMemberDetails(userID: String) + case presentMessageForwarding(forwardingItem: MessageForwardingItem) + case presentResolveSendFailure(failure: TimelineItemSendFailure.VerifiedUser, sendHandle: SendHandleProxy) } final class ThreadTimelineScreenCoordinator: CoordinatorProtocol { @@ -44,6 +49,7 @@ final class ThreadTimelineScreenCoordinator: CoordinatorProtocol { self.parameters = parameters viewModel = ThreadTimelineScreenViewModel() + timelineViewModel = TimelineViewModel(roomProxy: parameters.roomProxy, timelineController: parameters.timelineController, mediaProvider: parameters.mediaProvider, @@ -59,28 +65,48 @@ final class ThreadTimelineScreenCoordinator: CoordinatorProtocol { } func start() { - timelineViewModel.actions.sink { [weak self] action in - MXLog.info("Coordinator: received timeline view model action: \(action)") - guard let self else { return } - - switch action { - case .displaySenderDetails(let userID): - actionsSubject.send(.displayUser(userID: userID)) - case .displayMessageForwarding(let forwardingItem): - actionsSubject.send(.displayMessageForwarding(forwardingItem: forwardingItem)) - case .displayLocation(_, let geoURI, let description): - actionsSubject.send(.presentLocationViewer(geoURI: geoURI, description: description)) - case .viewInRoomTimeline(let eventID): - actionsSubject.send(.displayRoomScreenWithFocussedPin(eventID: eventID)) - // These other actions will not be handled in this view - case .displayEmojiPicker, .displayReportContent, .displayCameraPicker, .displayMediaPicker, - .displayDocumentPicker, .displayLocationPicker, .displayPollForm, .displayMediaPreview, - .displayMediaUploadPreviewScreen, .displayResolveSendFailure, .displayThread, .composer, .hasScrolled: - // These actions are not handled in this coordinator - break + timelineViewModel.actions + .sink { [weak self] action in + guard let self else { return } + + switch action { + case .displayEmojiPicker(let itemID, let selectedEmojis): + actionsSubject.send(.presentEmojiPicker(itemID: itemID, selectedEmojis: selectedEmojis)) + case .displayReportContent(let itemID, let senderID): + actionsSubject.send(.presentReportContent(itemID: itemID, senderID: senderID)) + case .displayCameraPicker: + actionsSubject.send(.presentMediaUploadPicker(.camera)) + case .displayMediaPicker: + actionsSubject.send(.presentMediaUploadPicker(.photoLibrary)) + case .displayDocumentPicker: + actionsSubject.send(.presentMediaUploadPicker(.documents)) + case .displayMediaPreview(let mediaPreviewViewModel): + break + case .displayLocationPicker: + actionsSubject.send(.presentLocationPicker) + case .displayPollForm(let mode): + actionsSubject.send(.presentPollForm(mode: mode)) + case .displayMediaUploadPreviewScreen(let url): + actionsSubject.send(.presentMediaUploadPreviewScreen(url)) + case .displaySenderDetails(userID: let userID): + actionsSubject.send(.presentRoomMemberDetails(userID: userID)) + case .displayMessageForwarding(let forwardingItem): + actionsSubject.send(.presentMessageForwarding(forwardingItem: forwardingItem)) + case .displayLocation(let body, let geoURI, let description): + actionsSubject.send(.presentLocationViewer(body: body, geoURI: geoURI, description: description)) + case .displayResolveSendFailure(let failure, let sendHandle): + actionsSubject.send(.presentResolveSendFailure(failure: failure, sendHandle: sendHandle)) + case .displayThread(let itemID): + break + case .composer(let action): + break + case .hasScrolled(direction: let direction): + break + case .viewInRoomTimeline: + fatalError("The action: \(action) should not be sent to this coordinator") + } } - } - .store(in: &cancellables) + .store(in: &cancellables) } func stop() { diff --git a/ElementX/Sources/Screens/Timeline/View/ItemMenu/TimelineItemMenuActionProvider.swift b/ElementX/Sources/Screens/Timeline/View/ItemMenu/TimelineItemMenuActionProvider.swift index a7f888b03..dab66d5dc 100644 --- a/ElementX/Sources/Screens/Timeline/View/ItemMenu/TimelineItemMenuActionProvider.swift +++ b/ElementX/Sources/Screens/Timeline/View/ItemMenu/TimelineItemMenuActionProvider.swift @@ -125,7 +125,7 @@ struct TimelineItemMenuActionProvider { secondaryActions = secondaryActions.filter(\.canAppearInRedacted) } - let isReactable = timelineKind == .live || timelineKind == .detached ? item.isReactable : false + let isReactable = timelineKind == .live || timelineKind == .detached || timelineKind == .thread ? item.isReactable : false return .init(isReactable: isReactable, actions: actions, secondaryActions: secondaryActions, emojiProvider: emojiProvider) }