diff --git a/WorterBuch/NotesListView.swift b/WorterBuch/NotesListView.swift index c1a8a6c..8c03f8a 100644 --- a/WorterBuch/NotesListView.swift +++ b/WorterBuch/NotesListView.swift @@ -18,12 +18,25 @@ struct NotesListView: View { private var notes: FetchedResults @State private var selectedNote: Note? + @State private var searchText = "" + + var filteredNotes: [Note] { + if searchText.isEmpty { + return Array(notes) + } else { + return notes.filter { note in + let titleMatch = note.title.localizedCaseInsensitiveContains(searchText) + let textMatch = (note.text ?? "").localizedCaseInsensitiveContains(searchText) + return titleMatch || textMatch + } + } + } var body: some View { NavigationView { // Left sidebar - list of notes List(selection: $selectedNote) { - ForEach(notes, id: \.id) { note in + ForEach(filteredNotes, id: \.id) { note in NoteRowView(note: note) .tag(note) .onTapGesture { @@ -47,6 +60,7 @@ struct NotesListView: View { } } } + .searchable(text: $searchText, prompt: "Search notes") // Right panel - note editor or placeholder if let note = selectedNote { @@ -108,28 +122,31 @@ struct NotesListView: View { } private func deleteNotes(offsets: IndexSet) { - withAnimation { - let notesToDelete = offsets.map { notes[$0] } + let notesToDelete = offsets.map { notes[$0] } - // Clear selection if we're deleting the selected note - if let selected = selectedNote, notesToDelete.contains(where: { $0.id == selected.id }) { - selectedNote = nil - } + // Clear selection if we're deleting the selected note + if let selected = selectedNote, notesToDelete.contains(where: { $0.id == selected.id }) { + selectedNote = nil + } - // Delete the notes - notesToDelete.forEach { note in - viewContext.delete(note) - } + // Small delay to ensure UI updates before deletion + DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) { + withAnimation { + // Delete the notes + notesToDelete.forEach { note in + viewContext.delete(note) + } - saveContext() + saveContext() - // After deletion, if no notes are left, ensure selection is nil - // Otherwise, select the first remaining note if nothing is selected - DispatchQueue.main.async { - if notes.isEmpty { - selectedNote = nil - } else if selectedNote == nil && !notes.isEmpty { - selectedNote = notes.first + // After deletion, if no notes are left, ensure selection is nil + // Otherwise, select the first remaining note if nothing is selected + DispatchQueue.main.async { + if notes.isEmpty { + selectedNote = nil + } else if selectedNote == nil && !notes.isEmpty { + selectedNote = notes.first + } } } } @@ -176,6 +193,7 @@ struct NoteEditorContentView: View { @Environment(\.managedObjectContext) private var viewContext let onAddNote: () -> Void @State private var showTranscription = false + @State private var forceTranscriptionTrigger = 0 var body: some View { VStack(spacing: 0) { @@ -197,7 +215,12 @@ struct NoteEditorContentView: View { Button(action: { withAnimation { + let wasHidden = !showTranscription showTranscription.toggle() + if wasHidden { + // Trigger immediate transcription when showing + forceTranscriptionTrigger += 1 + } } }) { Image(systemName: showTranscription ? "text.bubble.fill" : "text.bubble") @@ -232,7 +255,8 @@ struct NoteEditorContentView: View { NoteEditorContentOnly( drawing: bindingForDrawing(), text: bindingForText(), - showTranscription: $showTranscription + showTranscription: $showTranscription, + forceTranscriptionTrigger: forceTranscriptionTrigger ) } .background(Color(.systemGroupedBackground)) @@ -242,6 +266,8 @@ struct NoteEditorContentView: View { Binding( get: { note.pkDrawing }, set: { newValue in + // Guard against modifying deleted objects + guard note.managedObjectContext != nil else { return } note.pkDrawing = newValue saveContext() } @@ -252,6 +278,8 @@ struct NoteEditorContentView: View { Binding( get: { note.text ?? "" }, set: { newValue in + // Guard against modifying deleted objects + guard note.managedObjectContext != nil else { return } note.text = newValue saveContext() } @@ -272,6 +300,7 @@ struct NoteEditorContentOnly: View { @Binding var drawing: PKDrawing @Binding var text: String @Binding var showTranscription: Bool + let forceTranscriptionTrigger: Int @State private var isRecognizing = false @State private var viewAppeared = false @@ -348,6 +377,31 @@ struct NoteEditorContentOnly: View { .transition(.move(edge: .bottom).combined(with: .opacity)) } } + .onChange(of: forceTranscriptionTrigger) { _ in + forceRecognizeHandwriting() + } + } + + private func forceRecognizeHandwriting() { + guard !drawing.bounds.isEmpty else { + return + } + + // Cancel any pending recognition + recognitionWorkItem?.cancel() + + // Perform recognition immediately without debounce + Task { @MainActor in + isRecognizing = true + showTranscription = true // Auto-show transcription when forcing + + if let recognizedText = await HandwritingRecognizer.recognizeTextAsync(from: drawing) { + text = recognizedText + isRecognizing = false + } else { + isRecognizing = false + } + } } private func recognizeHandwriting(_ drawing: PKDrawing) {