Skip to content

Commit

Permalink
Volatile draft to restore the composer after an edit. (#2996)
Browse files Browse the repository at this point in the history
  • Loading branch information
Velin92 authored Jul 2, 2024
1 parent 457bd31 commit 94f9834
Show file tree
Hide file tree
Showing 7 changed files with 280 additions and 23 deletions.
140 changes: 140 additions & 0 deletions ElementX/Sources/Mocks/Generated/GeneratedMocks.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4480,6 +4480,47 @@ class ComposerDraftServiceMock: ComposerDraftServiceProtocol {
return saveDraftReturnValue
}
}
//MARK: - saveVolatileDraft

var saveVolatileDraftUnderlyingCallsCount = 0
var saveVolatileDraftCallsCount: Int {
get {
if Thread.isMainThread {
return saveVolatileDraftUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = saveVolatileDraftUnderlyingCallsCount
}

return returnValue!
}
}
set {
if Thread.isMainThread {
saveVolatileDraftUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
saveVolatileDraftUnderlyingCallsCount = newValue
}
}
}
}
var saveVolatileDraftCalled: Bool {
return saveVolatileDraftCallsCount > 0
}
var saveVolatileDraftReceivedDraft: ComposerDraftProxy?
var saveVolatileDraftReceivedInvocations: [ComposerDraftProxy] = []
var saveVolatileDraftClosure: ((ComposerDraftProxy) -> Void)?

func saveVolatileDraft(_ draft: ComposerDraftProxy) {
saveVolatileDraftCallsCount += 1
saveVolatileDraftReceivedDraft = draft
DispatchQueue.main.async {
self.saveVolatileDraftReceivedInvocations.append(draft)
}
saveVolatileDraftClosure?(draft)
}
//MARK: - loadDraft

var loadDraftUnderlyingCallsCount = 0
Expand Down Expand Up @@ -4544,6 +4585,70 @@ class ComposerDraftServiceMock: ComposerDraftServiceProtocol {
return loadDraftReturnValue
}
}
//MARK: - loadVolatileDraft

var loadVolatileDraftUnderlyingCallsCount = 0
var loadVolatileDraftCallsCount: Int {
get {
if Thread.isMainThread {
return loadVolatileDraftUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = loadVolatileDraftUnderlyingCallsCount
}

return returnValue!
}
}
set {
if Thread.isMainThread {
loadVolatileDraftUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
loadVolatileDraftUnderlyingCallsCount = newValue
}
}
}
}
var loadVolatileDraftCalled: Bool {
return loadVolatileDraftCallsCount > 0
}

var loadVolatileDraftUnderlyingReturnValue: ComposerDraftProxy?
var loadVolatileDraftReturnValue: ComposerDraftProxy? {
get {
if Thread.isMainThread {
return loadVolatileDraftUnderlyingReturnValue
} else {
var returnValue: ComposerDraftProxy?? = nil
DispatchQueue.main.sync {
returnValue = loadVolatileDraftUnderlyingReturnValue
}

return returnValue!
}
}
set {
if Thread.isMainThread {
loadVolatileDraftUnderlyingReturnValue = newValue
} else {
DispatchQueue.main.sync {
loadVolatileDraftUnderlyingReturnValue = newValue
}
}
}
}
var loadVolatileDraftClosure: (() -> ComposerDraftProxy?)?

func loadVolatileDraft() -> ComposerDraftProxy? {
loadVolatileDraftCallsCount += 1
if let loadVolatileDraftClosure = loadVolatileDraftClosure {
return loadVolatileDraftClosure()
} else {
return loadVolatileDraftReturnValue
}
}
//MARK: - clearDraft

var clearDraftUnderlyingCallsCount = 0
Expand Down Expand Up @@ -4608,6 +4713,41 @@ class ComposerDraftServiceMock: ComposerDraftServiceProtocol {
return clearDraftReturnValue
}
}
//MARK: - clearVolatileDraft

var clearVolatileDraftUnderlyingCallsCount = 0
var clearVolatileDraftCallsCount: Int {
get {
if Thread.isMainThread {
return clearVolatileDraftUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = clearVolatileDraftUnderlyingCallsCount
}

return returnValue!
}
}
set {
if Thread.isMainThread {
clearVolatileDraftUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
clearVolatileDraftUnderlyingCallsCount = newValue
}
}
}
}
var clearVolatileDraftCalled: Bool {
return clearVolatileDraftCallsCount > 0
}
var clearVolatileDraftClosure: (() -> Void)?

func clearVolatileDraft() {
clearVolatileDraftCallsCount += 1
clearVolatileDraftClosure?()
}
//MARK: - getReply

var getReplyEventIDUnderlyingCallsCount = 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,13 @@ final class ComposerToolbarViewModel: ComposerToolbarViewModelType, ComposerTool
case .cancelReply:
set(mode: .default)
case .cancelEdit:
set(mode: .default)
set(text: "")
if let draft = draftService.loadVolatileDraft() {
handleLoadDraft(draft)
draftService.clearVolatileDraft()
} else {
set(text: "")
set(mode: .default)
}
case .attach(let attachment):
state.bindings.composerFocused = false
actionsSubject.send(.attach(attachment))
Expand Down Expand Up @@ -198,6 +203,9 @@ final class ComposerToolbarViewModel: ComposerToolbarViewModelType, ComposerTool
func process(roomAction: RoomScreenComposerAction) {
switch roomAction {
case .setMode(mode: let mode):
if state.composerMode.isComposingNewMessage, mode.isEdit {
handleSaveDraft(isVolatile: true)
}
set(mode: mode)
case .setText(let plainText, let htmlText):
if let htmlText, context.composerFormattingEnabled {
Expand All @@ -208,13 +216,22 @@ final class ComposerToolbarViewModel: ComposerToolbarViewModelType, ComposerTool
case .removeFocus:
state.bindings.composerFocused = false
case .clear:
set(mode: .default)
set(text: "")
if let draft = draftService.loadVolatileDraft() {
handleLoadDraft(draft)
draftService.clearVolatileDraft()
} else {
set(mode: .default)
set(text: "")
}
case .saveDraft:
handleSaveDraft()
handleSaveDraft(isVolatile: false)
case .loadDraft:
Task {
await handleLoadDraft()
guard case let .success(draft) = await draftService.loadDraft(),
let draft else {
return
}
handleLoadDraft(draft)
}
}
}
Expand All @@ -229,12 +246,7 @@ final class ComposerToolbarViewModel: ComposerToolbarViewModelType, ComposerTool

// MARK: - Private

private func handleLoadDraft() async {
guard case let .success(draft) = await draftService.loadDraft(),
let draft else {
return
}

private func handleLoadDraft(_ draft: ComposerDraftProxy) {
if let html = draft.htmlText {
context.composerFormattingEnabled = true
DispatchQueue.main.async {
Expand Down Expand Up @@ -269,24 +281,32 @@ final class ComposerToolbarViewModel: ComposerToolbarViewModelType, ComposerTool
}
}

private func handleSaveDraft() {
private func handleSaveDraft(isVolatile: Bool) {
let plainText: String
let htmlText: String?
let type: ComposerDraftProxy.ComposerDraftType

if context.composerFormattingEnabled {
if wysiwygViewModel.isContentEmpty, state.composerMode == .default {
Task {
await draftService.clearDraft()
if isVolatile {
draftService.clearVolatileDraft()
} else {
Task {
await draftService.clearDraft()
}
}
return
}
plainText = wysiwygViewModel.content.markdown
htmlText = wysiwygViewModel.content.html
} else {
if context.plainComposerText.string.isEmpty, state.composerMode == .default {
Task {
await draftService.clearDraft()
if isVolatile {
draftService.clearVolatileDraft()
} else {
Task {
await draftService.clearDraft()
}
}
return
}
Expand All @@ -310,15 +330,22 @@ final class ComposerToolbarViewModel: ComposerToolbarViewModelType, ComposerTool
}
type = .reply(eventID: eventID)
default:
// Do not save a draft for the other cases
Task {
await draftService.clearDraft()
if isVolatile {
draftService.clearVolatileDraft()
} else {
Task {
await draftService.clearDraft()
}
}
return
}

Task {
await draftService.saveDraft(.init(plainText: plainText, htmlText: htmlText, draftType: type))
if isVolatile {
draftService.saveVolatileDraft(.init(plainText: plainText, htmlText: htmlText, draftType: type))
} else {
Task {
await draftService.saveDraft(.init(plainText: plainText, htmlText: htmlText, draftType: type))
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -276,8 +276,9 @@ class RoomScreenInteractionHandler {
text = messageTimelineItem.body
}

actionsSubject.send(.composer(action: .setText(plainText: text, htmlText: htmlText)))
// Always update the mode first and then the text so that the composer has time to save the text draft
actionsSubject.send(.composer(action: .setMode(mode: .edit(originalItemId: messageTimelineItem.id))))
actionsSubject.send(.composer(action: .setText(plainText: text, htmlText: htmlText)))
}

// MARK: Polls
Expand Down
9 changes: 9 additions & 0 deletions ElementX/Sources/Screens/RoomScreen/RoomScreenModels.swift
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,15 @@ enum RoomScreenComposerMode: Equatable {
return nil
}
}

var isComposingNewMessage: Bool {
switch self {
case .default, .reply:
return true
default:
return false
}
}
}

enum RoomScreenViewPollAction {
Expand Down
13 changes: 13 additions & 0 deletions ElementX/Sources/Services/ComposerDraft/ComposerDraftService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import MatrixRustSDK
final class ComposerDraftService: ComposerDraftServiceProtocol {
private let roomProxy: RoomProxyProtocol
private let timelineItemfactory: RoomTimelineItemFactoryProtocol
private var volatileDraft: ComposerDraftProxy?

init(roomProxy: RoomProxyProtocol, timelineItemfactory: RoomTimelineItemFactoryProtocol) {
self.roomProxy = roomProxy
Expand Down Expand Up @@ -71,4 +72,16 @@ final class ComposerDraftService: ComposerDraftServiceProtocol {
return .failure(.failedToClearDraft)
}
}

func saveVolatileDraft(_ draft: ComposerDraftProxy) {
volatileDraft = draft
}

func loadVolatileDraft() -> ComposerDraftProxy? {
volatileDraft
}

func clearVolatileDraft() {
volatileDraft = nil
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,10 @@ enum ComposerDraftServiceError: Error {
// sourcery: AutoMockable
protocol ComposerDraftServiceProtocol {
func saveDraft(_ draft: ComposerDraftProxy) async -> Result<Void, ComposerDraftServiceError>
func saveVolatileDraft(_ draft: ComposerDraftProxy)
func loadDraft() async -> Result<ComposerDraftProxy?, ComposerDraftServiceError>
func loadVolatileDraft() -> ComposerDraftProxy?
func clearDraft() async -> Result<Void, ComposerDraftServiceError>
func clearVolatileDraft()
func getReply(eventID: String) async -> Result<TimelineItemReply, ComposerDraftServiceError>
}
Loading

0 comments on commit 94f9834

Please sign in to comment.