|
|
@@ -0,0 +1,110 @@
|
|
|
+import Foundation
|
|
|
+import Combine
|
|
|
+
|
|
|
+@MainActor
|
|
|
+final class NotesStore: ObservableObject {
|
|
|
+ @Published private(set) var notes: [Note] = []
|
|
|
+ @Published var selectedNoteID: UUID?
|
|
|
+ @Published var searchQuery: String = ""
|
|
|
+
|
|
|
+ private let repository: FileNotesRepository
|
|
|
+ private var saveTask: Task<Void, Never>?
|
|
|
+
|
|
|
+ init(repository: FileNotesRepository = FileNotesRepository()) {
|
|
|
+ self.repository = repository
|
|
|
+
|
|
|
+ do {
|
|
|
+ let loadedNotes = try repository.loadNotes()
|
|
|
+ self.notes = loadedNotes.sorted(by: { $0.updatedAt > $1.updatedAt })
|
|
|
+ } catch {
|
|
|
+ self.notes = []
|
|
|
+ }
|
|
|
+
|
|
|
+ self.selectedNoteID = notes.first?.id
|
|
|
+ }
|
|
|
+
|
|
|
+ var filteredNotes: [Note] {
|
|
|
+ let query = searchQuery.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
|
+ guard !query.isEmpty else { return notes }
|
|
|
+
|
|
|
+ let q = query.lowercased()
|
|
|
+ return notes.filter { note in
|
|
|
+ note.title.lowercased().contains(q) || note.body.lowercased().contains(q)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ var selectedNote: Note? {
|
|
|
+ guard let id = selectedNoteID else { return nil }
|
|
|
+ return notes.first(where: { $0.id == id })
|
|
|
+ }
|
|
|
+
|
|
|
+ func noteByID(_ id: UUID) -> Note? {
|
|
|
+ notes.first(where: { $0.id == id })
|
|
|
+ }
|
|
|
+
|
|
|
+ func createNote() {
|
|
|
+ let now = Date()
|
|
|
+ let note = Note(
|
|
|
+ title: Note.placeholderTitle,
|
|
|
+ body: "",
|
|
|
+ updatedAt: now
|
|
|
+ )
|
|
|
+
|
|
|
+ notes.insert(note, at: 0)
|
|
|
+ selectedNoteID = note.id
|
|
|
+ sortNotesInPlace()
|
|
|
+ scheduleSave()
|
|
|
+ }
|
|
|
+
|
|
|
+ func updateNoteTitle(noteID: UUID, title: String) {
|
|
|
+ guard let index = notes.firstIndex(where: { $0.id == noteID }) else { return }
|
|
|
+ notes[index].title = title
|
|
|
+ notes[index].updatedAt = Date()
|
|
|
+ sortNotesInPlace()
|
|
|
+ scheduleSave()
|
|
|
+ }
|
|
|
+
|
|
|
+ func updateNoteBody(noteID: UUID, body: String) {
|
|
|
+ guard let index = notes.firstIndex(where: { $0.id == noteID }) else { return }
|
|
|
+ notes[index].body = body
|
|
|
+ notes[index].updatedAt = Date()
|
|
|
+ sortNotesInPlace()
|
|
|
+ scheduleSave()
|
|
|
+ }
|
|
|
+
|
|
|
+ func delete(noteID: UUID) {
|
|
|
+ guard let index = notes.firstIndex(where: { $0.id == noteID }) else { return }
|
|
|
+ notes.remove(at: index)
|
|
|
+
|
|
|
+ if selectedNoteID == noteID {
|
|
|
+ selectedNoteID = notes.first?.id
|
|
|
+ }
|
|
|
+
|
|
|
+ scheduleSave()
|
|
|
+ }
|
|
|
+
|
|
|
+ private func sortNotesInPlace() {
|
|
|
+ notes.sort(by: { $0.updatedAt > $1.updatedAt })
|
|
|
+ }
|
|
|
+
|
|
|
+ private func scheduleSave() {
|
|
|
+ saveTask?.cancel()
|
|
|
+
|
|
|
+ let snapshot = notes
|
|
|
+ let repository = self.repository
|
|
|
+
|
|
|
+ saveTask = Task { @MainActor in
|
|
|
+ try? await Task.sleep(nanoseconds: 500_000_000)
|
|
|
+ do {
|
|
|
+ try repository.saveNotes(snapshot)
|
|
|
+ } catch {
|
|
|
+ // Keep UI responsive; persistence failures will be visible via stderr in Debug.
|
|
|
+ // You can wire this into an error UI later if desired.
|
|
|
+ #if DEBUG
|
|
|
+ print("Failed saving notes: \(error)")
|
|
|
+ #endif
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|