From 6fc6df9375c13eea773ca041b8b2299ee08aef58 Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Fri, 13 Jun 2025 15:11:20 +0300 Subject: [PATCH] Refactor how drafts are handled on a coordinator level and add support for them on thread timelines. --- .../ComposerToolbar/ComposerToolbarModels.swift | 2 ++ .../ComposerToolbarViewModel.swift | 15 +++++++++++++++ .../ComposerToolbarViewModelProtocol.swift | 5 +++-- .../ComposerToolbar/View/ComposerToolbar.swift | 3 +++ .../RoomScreen/RoomScreenCoordinator.swift | 17 +++++------------ .../ThreadTimelineScreenCoordinator.swift | 7 +++++-- 6 files changed, 33 insertions(+), 16 deletions(-) diff --git a/ElementX/Sources/Screens/RoomScreen/ComposerToolbar/ComposerToolbarModels.swift b/ElementX/Sources/Screens/RoomScreen/ComposerToolbar/ComposerToolbarModels.swift index 9a522e0e9..235b7de83 100644 --- a/ElementX/Sources/Screens/RoomScreen/ComposerToolbar/ComposerToolbarModels.swift +++ b/ElementX/Sources/Screens/RoomScreen/ComposerToolbar/ComposerToolbarModels.swift @@ -39,6 +39,8 @@ enum ComposerToolbarViewModelAction { enum ComposerToolbarViewAction { case composerAppeared + case composerDisappeared + case sendMessage case editLastMessage case cancelReply diff --git a/ElementX/Sources/Screens/RoomScreen/ComposerToolbar/ComposerToolbarViewModel.swift b/ElementX/Sources/Screens/RoomScreen/ComposerToolbar/ComposerToolbarViewModel.swift index 384b96cdf..2a8d23660 100644 --- a/ElementX/Sources/Screens/RoomScreen/ComposerToolbar/ComposerToolbarViewModel.swift +++ b/ElementX/Sources/Screens/RoomScreen/ComposerToolbar/ComposerToolbarViewModel.swift @@ -161,9 +161,22 @@ final class ComposerToolbarViewModel: ComposerToolbarViewModelType, ComposerTool } } .store(in: &cancellables) + + NotificationCenter.default.publisher(for: UIApplication.willResignActiveNotification).sink { [weak self] _ in + self?.saveDraft() + } + .store(in: &cancellables) } // MARK: - Public + + func start() { + Task { await loadDraft() } + } + + func stop() { + saveDraft() + } override func process(viewAction: ComposerToolbarViewAction) { switch viewAction { @@ -172,6 +185,8 @@ final class ComposerToolbarViewModel: ComposerToolbarViewModelType, ComposerTool hasAppeard = true wysiwygViewModel.setup() } + case .composerDisappeared: + saveDraft() case .sendMessage: guard !state.sendButtonDisabled else { return } diff --git a/ElementX/Sources/Screens/RoomScreen/ComposerToolbar/ComposerToolbarViewModelProtocol.swift b/ElementX/Sources/Screens/RoomScreen/ComposerToolbar/ComposerToolbarViewModelProtocol.swift index 7afcd5349..4d9fc6be0 100644 --- a/ElementX/Sources/Screens/RoomScreen/ComposerToolbar/ComposerToolbarViewModelProtocol.swift +++ b/ElementX/Sources/Screens/RoomScreen/ComposerToolbar/ComposerToolbarViewModelProtocol.swift @@ -13,8 +13,9 @@ import WysiwygComposer protocol ComposerToolbarViewModelProtocol { var actions: AnyPublisher { get } var context: ComposerToolbarViewModelType.Context { get } + + func start() + func stop() func process(timelineAction: TimelineComposerAction) - func loadDraft() async - func saveDraft() } diff --git a/ElementX/Sources/Screens/RoomScreen/ComposerToolbar/View/ComposerToolbar.swift b/ElementX/Sources/Screens/RoomScreen/ComposerToolbar/View/ComposerToolbar.swift index 96c6e3027..8e34a779f 100644 --- a/ElementX/Sources/Screens/RoomScreen/ComposerToolbar/View/ComposerToolbar.swift +++ b/ElementX/Sources/Screens/RoomScreen/ComposerToolbar/View/ComposerToolbar.swift @@ -181,6 +181,9 @@ struct ComposerToolbar: View { } onAppearAction: { context.send(viewAction: .composerAppeared) } + .onDisappear { + context.send(viewAction: .composerDisappeared) + } .environmentObject(context) .focused($composerFocused) .padding(.leading, context.composerFormattingEnabled ? 7 : 0) diff --git a/ElementX/Sources/Screens/RoomScreen/RoomScreenCoordinator.swift b/ElementX/Sources/Screens/RoomScreen/RoomScreenCoordinator.swift index 7369e9a93..6ccd9156c 100644 --- a/ElementX/Sources/Screens/RoomScreen/RoomScreenCoordinator.swift +++ b/ElementX/Sources/Screens/RoomScreen/RoomScreenCoordinator.swift @@ -107,11 +107,6 @@ final class RoomScreenCoordinator: CoordinatorProtocol { analyticsService: ServiceLocator.shared.analytics, composerDraftService: parameters.composerDraftService) self.composerViewModel = composerViewModel - - NotificationCenter.default.publisher(for: UIApplication.willResignActiveNotification).sink { _ in - composerViewModel.saveDraft() - } - .store(in: &cancellables) } // MARK: - Public @@ -193,8 +188,9 @@ final class RoomScreenCoordinator: CoordinatorProtocol { } .store(in: &cancellables) - // Loading the draft requires the subscriptions to be set up first otherwise the room won't be be able to propagate the information to the composer. - Task { await composerViewModel.loadDraft() } + // Loading the draft requires the subscriptions to be set up first otherwise + // the room won't be be able to propagate the information to the composer. + composerViewModel.start() } func focusOnEvent(_ focussedEvent: FocusEvent) { @@ -212,7 +208,7 @@ final class RoomScreenCoordinator: CoordinatorProtocol { } func stop() { - composerViewModel.saveDraft() + composerViewModel.stop() roomViewModel.stop() } @@ -221,10 +217,7 @@ final class RoomScreenCoordinator: CoordinatorProtocol { return AnyView(RoomScreen(context: roomViewModel.context, timelineContext: timelineViewModel.context, - composerToolbar: composerToolbar) - .onDisappear { [weak self] in - self?.composerViewModel.saveDraft() - }) + composerToolbar: composerToolbar)) } } diff --git a/ElementX/Sources/Screens/ThreadTimelineScreen/ThreadTimelineScreenCoordinator.swift b/ElementX/Sources/Screens/ThreadTimelineScreen/ThreadTimelineScreenCoordinator.swift index 170c95014..25cea37bf 100644 --- a/ElementX/Sources/Screens/ThreadTimelineScreen/ThreadTimelineScreenCoordinator.swift +++ b/ElementX/Sources/Screens/ThreadTimelineScreen/ThreadTimelineScreenCoordinator.swift @@ -74,8 +74,6 @@ final class ThreadTimelineScreenCoordinator: CoordinatorProtocol { maxExpandedHeight: ComposerConstant.maxHeight, parserStyle: .elementX) - #warning("Drafts are not handled and they can't be without rust side changes") - composerViewModel = ComposerToolbarViewModel(initialText: nil, roomProxy: parameters.roomProxy, isInThread: true, @@ -144,9 +142,14 @@ final class ThreadTimelineScreenCoordinator: CoordinatorProtocol { timelineViewModel.process(composerAction: action) } .store(in: &cancellables) + + // Loading the draft requires the subscriptions to be set up first otherwise + // the room won't be be able to propagate the information to the composer. + composerViewModel.start() } func stop() { + composerViewModel.stop() viewModel.stop() }