diff --git a/freewrite/ContentView.swift b/freewrite/ContentView.swift index c4c08cd..1552ea7 100644 --- a/freewrite/ContentView.swift +++ b/freewrite/ContentView.swift @@ -128,6 +128,7 @@ struct ContentView: View { @State private var selectedVideoHasTranscript = false @State private var backspaceDisabled = false // Add state for backspace toggle @State private var isHoveringBackspaceToggle = false // Add state for backspace toggle hover + @State private var pendingSaveWorkItem: DispatchWorkItem? = nil // For debounced saves @State private var showingVideoRecording = false // Add state for video recording view @State private var isHoveringVideoButton = false // Add state for video button hover @State private var currentVideoURL: URL? = nil // Add state for current video being viewed @@ -154,9 +155,8 @@ struct ContentView: View { "Just say it" ] - // Add file manager and save timer + // Add file manager private let fileManager = FileManager.default - private let saveTimer = Timer.publish(every: 1.0, on: .main, in: .common).autoconnect() // Add cached documents directory private let documentsDirectory: URL = { @@ -1717,12 +1717,23 @@ struct ContentView: View { } } .onChange(of: text) { _ in + // Cancel any pending save + pendingSaveWorkItem?.cancel() + + // Schedule a new save after 500ms of no typing + let workItem = DispatchWorkItem { [self] in + if let currentId = selectedEntryId, + let currentEntry = entries.first(where: { $0.id == currentId }) { + saveEntry(entry: currentEntry) + } // Save current entry when text changes if let currentId = selectedEntryId, let currentEntry = entries.first(where: { $0.id == currentId }), currentEntry.entryType == .text { saveEntry(entry: currentEntry) } + pendingSaveWorkItem = workItem + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: workItem) } .onReceive(timer) { _ in if timerIsRunning && timeRemaining > 0 { @@ -1736,6 +1747,14 @@ struct ContentView: View { } } } + .onReceive(NotificationCenter.default.publisher(for: NSApplication.willResignActiveNotification)) { _ in + // Force save when app loses focus + pendingSaveWorkItem?.cancel() + if let currentId = selectedEntryId, + let currentEntry = entries.first(where: { $0.id == currentId }) { + saveEntry(entry: currentEntry) + } + } .onReceive(NotificationCenter.default.publisher(for: NSWindow.willEnterFullScreenNotification)) { _ in isFullscreen = true } @@ -1768,20 +1787,25 @@ struct ContentView: View { do { let content = try String(contentsOf: fileURL, encoding: .utf8) - let preview = content - .replacingOccurrences(of: "\n", with: " ") - .trimmingCharacters(in: .whitespacesAndNewlines) - let truncated = preview.isEmpty ? "" : (preview.count > 30 ? String(preview.prefix(30)) + "..." : preview) - - // Find and update the entry in the entries array - if let index = entries.firstIndex(where: { $0.id == entry.id }) { - entries[index].previewText = truncated - } + updatePreviewTextFromContent(content, for: entry) } catch { print("Error updating preview text: \(error)") } } + // New method that computes preview from content without disk I/O + private func updatePreviewTextFromContent(_ content: String, for entry: HumanEntry) { + let preview = content + .replacingOccurrences(of: "\n", with: " ") + .trimmingCharacters(in: .whitespacesAndNewlines) + let truncated = preview.isEmpty ? "" : (preview.count > 30 ? String(preview.prefix(30)) + "..." : preview) + + // Find and update the entry in the entries array + if let index = entries.firstIndex(where: { $0.id == entry.id }) { + entries[index].previewText = truncated + } + } + private func saveEntry(entry: HumanEntry) { guard entry.entryType == .text else { return @@ -1789,13 +1813,20 @@ struct ContentView: View { let documentsDirectory = getDocumentsDirectory() let fileURL = documentsDirectory.appendingPathComponent(entry.filename) + let textToSave = text // Capture the text value - do { - try text.write(to: fileURL, atomically: true, encoding: .utf8) - print("Successfully saved entry: \(entry.filename)") - updatePreviewText(for: entry) // Update preview after saving - } catch { - print("Error saving entry: \(error)") + DispatchQueue.global(qos: .utility).async { + do { + try textToSave.write(to: fileURL, atomically: true, encoding: .utf8) + print("Successfully saved entry: \(entry.filename)") + + // Update preview text on main thread + DispatchQueue.main.async { + self.updatePreviewTextFromContent(textToSave, for: entry) + } + } catch { + print("Error saving entry: \(error)") + } } }