feat: restore composer drafts across channel and DM switches

Save on leave and background, reload on enter, clear after send, and
wipe drafts during panic clear.
This commit is contained in:
Taksh 2026-07-31 11:43:57 +03:00
parent e62fa0a1c4
commit 371e77ac8a
2 changed files with 35 additions and 1 deletions

View File

@ -1637,6 +1637,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate, SynchronousMessage
MeshSightingsTracker.shared.clear()
MeshEchoSettings.reset()
NotificationPrivacySettings.reset()
ComposerDraftStore.reset()
// A hand-added relay names an operator someone chose to route through,
// which is the kind of trace a wipe should not leave behind.
NostrRelaySettings.reset()

View File

@ -98,6 +98,9 @@ struct ContentView: View {
@StateObject private var voiceRecordingVM = VoiceRecordingViewModel()
@State private var messageText = ""
/// Conversation the current `messageText` belongs to, so a channel switch
/// can save under the previous key before loading the next draft.
@State private var activeDraftKey: ComposerDraftStore.Key = .mesh
@FocusState private var isTextFieldFocused: Bool
@Environment(\.colorScheme) var colorScheme
@Environment(\.appTheme) private var appTheme
@ -241,6 +244,11 @@ struct ContentView: View {
}
#endif
sharedContentImportModel.updateDestination(sharedContentDestination)
activeDraftKey = ComposerDraftStore.Key.from(
peerID: selectedPrivatePeerID,
channel: locationChannelsModel.selectedChannel
)
messageText = ComposerDraftStore.load(activeDraftKey)
}
.onChange(of: colorScheme) { newValue in
conversationUIModel.setCurrentColorScheme(newValue)
@ -258,9 +266,26 @@ struct ContentView: View {
showSidebar = true
}
sharedContentImportModel.updateDestination(sharedContentDestination)
switchComposerDraft(to: ComposerDraftStore.Key.from(
peerID: newValue,
channel: locationChannelsModel.selectedChannel
))
}
.onChange(of: locationChannelsModel.selectedChannel) { _ in
.onChange(of: locationChannelsModel.selectedChannel) { newChannel in
sharedContentImportModel.updateDestination(sharedContentDestination)
// Private drafts are keyed by peer; only public channel switches
// need a draft swap while no DM is open.
if selectedPrivatePeerID == nil {
switchComposerDraft(to: ComposerDraftStore.Key.from(
peerID: nil,
channel: newChannel
))
}
}
.onChange(of: scenePhase) { phase in
if phase == .background || phase == .inactive {
ComposerDraftStore.save(messageText, for: activeDraftKey)
}
}
.sheet(
isPresented: Binding(
@ -541,9 +566,17 @@ struct ContentView: View {
guard let trimmed = messageText.trimmedOrNilIfEmpty else { return }
messageText = ""
ComposerDraftStore.save("", for: activeDraftKey)
DispatchQueue.main.async {
self.conversationUIModel.sendMessage(trimmed)
}
}
private func switchComposerDraft(to newKey: ComposerDraftStore.Key) {
guard newKey != activeDraftKey else { return }
ComposerDraftStore.save(messageText, for: activeDraftKey)
activeDraftKey = newKey
messageText = ComposerDraftStore.load(newKey)
}
}