Refactor how drafts are handled on a coordinator level and add support for them on thread timelines.

This commit is contained in:
Stefan Ceriu
2025-06-13 15:11:20 +03:00
committed by Stefan Ceriu
parent eaf14eaaac
commit 6fc6df9375
6 changed files with 33 additions and 16 deletions
@@ -39,6 +39,8 @@ enum ComposerToolbarViewModelAction {
enum ComposerToolbarViewAction { enum ComposerToolbarViewAction {
case composerAppeared case composerAppeared
case composerDisappeared
case sendMessage case sendMessage
case editLastMessage case editLastMessage
case cancelReply case cancelReply
@@ -161,9 +161,22 @@ final class ComposerToolbarViewModel: ComposerToolbarViewModelType, ComposerTool
} }
} }
.store(in: &cancellables) .store(in: &cancellables)
NotificationCenter.default.publisher(for: UIApplication.willResignActiveNotification).sink { [weak self] _ in
self?.saveDraft()
}
.store(in: &cancellables)
} }
// MARK: - Public // MARK: - Public
func start() {
Task { await loadDraft() }
}
func stop() {
saveDraft()
}
override func process(viewAction: ComposerToolbarViewAction) { override func process(viewAction: ComposerToolbarViewAction) {
switch viewAction { switch viewAction {
@@ -172,6 +185,8 @@ final class ComposerToolbarViewModel: ComposerToolbarViewModelType, ComposerTool
hasAppeard = true hasAppeard = true
wysiwygViewModel.setup() wysiwygViewModel.setup()
} }
case .composerDisappeared:
saveDraft()
case .sendMessage: case .sendMessage:
guard !state.sendButtonDisabled else { return } guard !state.sendButtonDisabled else { return }
@@ -13,8 +13,9 @@ import WysiwygComposer
protocol ComposerToolbarViewModelProtocol { protocol ComposerToolbarViewModelProtocol {
var actions: AnyPublisher<ComposerToolbarViewModelAction, Never> { get } var actions: AnyPublisher<ComposerToolbarViewModelAction, Never> { get }
var context: ComposerToolbarViewModelType.Context { get } var context: ComposerToolbarViewModelType.Context { get }
func start()
func stop()
func process(timelineAction: TimelineComposerAction) func process(timelineAction: TimelineComposerAction)
func loadDraft() async
func saveDraft()
} }
@@ -181,6 +181,9 @@ struct ComposerToolbar: View {
} onAppearAction: { } onAppearAction: {
context.send(viewAction: .composerAppeared) context.send(viewAction: .composerAppeared)
} }
.onDisappear {
context.send(viewAction: .composerDisappeared)
}
.environmentObject(context) .environmentObject(context)
.focused($composerFocused) .focused($composerFocused)
.padding(.leading, context.composerFormattingEnabled ? 7 : 0) .padding(.leading, context.composerFormattingEnabled ? 7 : 0)
@@ -107,11 +107,6 @@ final class RoomScreenCoordinator: CoordinatorProtocol {
analyticsService: ServiceLocator.shared.analytics, analyticsService: ServiceLocator.shared.analytics,
composerDraftService: parameters.composerDraftService) composerDraftService: parameters.composerDraftService)
self.composerViewModel = composerViewModel self.composerViewModel = composerViewModel
NotificationCenter.default.publisher(for: UIApplication.willResignActiveNotification).sink { _ in
composerViewModel.saveDraft()
}
.store(in: &cancellables)
} }
// MARK: - Public // MARK: - Public
@@ -193,8 +188,9 @@ final class RoomScreenCoordinator: CoordinatorProtocol {
} }
.store(in: &cancellables) .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. // Loading the draft requires the subscriptions to be set up first otherwise
Task { await composerViewModel.loadDraft() } // the room won't be be able to propagate the information to the composer.
composerViewModel.start()
} }
func focusOnEvent(_ focussedEvent: FocusEvent) { func focusOnEvent(_ focussedEvent: FocusEvent) {
@@ -212,7 +208,7 @@ final class RoomScreenCoordinator: CoordinatorProtocol {
} }
func stop() { func stop() {
composerViewModel.saveDraft() composerViewModel.stop()
roomViewModel.stop() roomViewModel.stop()
} }
@@ -221,10 +217,7 @@ final class RoomScreenCoordinator: CoordinatorProtocol {
return AnyView(RoomScreen(context: roomViewModel.context, return AnyView(RoomScreen(context: roomViewModel.context,
timelineContext: timelineViewModel.context, timelineContext: timelineViewModel.context,
composerToolbar: composerToolbar) composerToolbar: composerToolbar))
.onDisappear { [weak self] in
self?.composerViewModel.saveDraft()
})
} }
} }
@@ -74,8 +74,6 @@ final class ThreadTimelineScreenCoordinator: CoordinatorProtocol {
maxExpandedHeight: ComposerConstant.maxHeight, maxExpandedHeight: ComposerConstant.maxHeight,
parserStyle: .elementX) parserStyle: .elementX)
#warning("Drafts are not handled and they can't be without rust side changes")
composerViewModel = ComposerToolbarViewModel(initialText: nil, composerViewModel = ComposerToolbarViewModel(initialText: nil,
roomProxy: parameters.roomProxy, roomProxy: parameters.roomProxy,
isInThread: true, isInThread: true,
@@ -144,9 +142,14 @@ final class ThreadTimelineScreenCoordinator: CoordinatorProtocol {
timelineViewModel.process(composerAction: action) timelineViewModel.process(composerAction: action)
} }
.store(in: &cancellables) .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() { func stop() {
composerViewModel.stop()
viewModel.stop() viewModel.stop()
} }