diff --git a/bitchat/ViewModels/ChatViewModel.swift b/bitchat/ViewModels/ChatViewModel.swift index 12adfda9..b7eab0bf 100644 --- a/bitchat/ViewModels/ChatViewModel.swift +++ b/bitchat/ViewModels/ChatViewModel.swift @@ -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() diff --git a/bitchat/Views/ContentView.swift b/bitchat/Views/ContentView.swift index 1fe95c75..fb77e698 100644 --- a/bitchat/Views/ContentView.swift +++ b/bitchat/Views/ContentView.swift @@ -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) + } }