Преглед изворни кода

refactor: shim Codable feature onto models

Sam Jaffe пре 2 недеља
родитељ
комит
fdcf63dc76

+ 19 - 1
Todos/Model/Project.swift

@@ -9,7 +9,7 @@ import Foundation
 import SwiftData
 
 @Model
-final class Project {
+final class Project : Codable {
   var timestamp: Date
   var category: String = ""
   var name: String = "New Project"
@@ -28,4 +28,22 @@ final class Project {
     rval += tasks.map({ $0.yaml(indent + 1) }).joined()
     return rval
   }
+  
+  enum CodingKeys : CodingKey { case timestamp, category, name, tasks }
+
+  required init(from decoder: any Decoder) throws {
+    let container = try decoder.container(keyedBy: CodingKeys.self)
+    timestamp = try container.decode(Date.self, forKey: .timestamp)
+    category = try container.decode(String.self, forKey: .category)
+    name = try container.decode(String.self, forKey: .name)
+    tasks = try container.decode([Task].self, forKey: .tasks)
+  }
+  
+  func encode(to encoder: any Encoder) throws {
+    var container = encoder.container(keyedBy: CodingKeys.self)
+    try container.encode(timestamp, forKey: .timestamp)
+    try container.encode(category, forKey: .category)
+    try container.encode(tasks, forKey: .tasks)
+    try container.encode(name, forKey: .name)
+  }
 }

+ 38 - 2
Todos/Model/Task.swift

@@ -32,7 +32,7 @@ enum Status : String, CaseIterable, Identifiable, Codable {
 }
 
 @Model
-final class SubTask {
+final class SubTask : Codable {
   var name: String
   var notes: String = ""
   var status: Status = Status.Todo
@@ -50,6 +50,22 @@ final class SubTask {
     }
     return rval
   }
+  
+  enum CodingKeys : CodingKey { case name, notes, status }
+
+  required init(from decoder: any Decoder) throws {
+    let container = try decoder.container(keyedBy: CodingKeys.self)
+    name = try container.decode(String.self, forKey: .name)
+    notes = try container.decode(String.self, forKey: .notes)
+    status = try container.decode(Status.self, forKey: .status)
+  }
+  
+  func encode(to encoder: any Encoder) throws {
+    var container = encoder.container(keyedBy: CodingKeys.self)
+    try container.encode(name, forKey: .name)
+    try container.encode(notes, forKey: .notes)
+    try container.encode(status, forKey: .status)
+  }
 }
 
 struct Tag : Codable, Identifiable {
@@ -61,7 +77,7 @@ struct Tag : Codable, Identifiable {
 }
 
 @Model
-final class Task {
+final class Task : Codable {
   var name: String
   var tags: [Tag] = []
   var subtasks: [SubTask] = []
@@ -83,4 +99,24 @@ final class Task {
     rval += subtasks.map({ $0.yaml(indent + 1) }).joined()
     return rval
   }
+  
+  enum CodingKeys : CodingKey { case name, tags, subtasks, notes, status }
+
+  required init(from decoder: any Decoder) throws {
+    let container = try decoder.container(keyedBy: CodingKeys.self)
+    name = try container.decode(String.self, forKey: .name)
+    tags = try container.decode([Tag].self, forKey: .tags)
+    subtasks = try container.decode([SubTask].self, forKey: .subtasks)
+    notes = try container.decode(String.self, forKey: .notes)
+    status = try container.decode(Status.self, forKey: .status)
+  }
+  
+  func encode(to encoder: any Encoder) throws {
+    var container = encoder.container(keyedBy: CodingKeys.self)
+    try container.encode(name, forKey: .name)
+    try container.encode(tags, forKey: .tags)
+    try container.encode(subtasks, forKey: .subtasks)
+    try container.encode(notes, forKey: .notes)
+    try container.encode(status, forKey: .status)
+  }
 }

+ 37 - 0
Todos/View/Menu/ExportMenu.swift

@@ -0,0 +1,37 @@
+//
+//  SaveAsMenu.swift
+//  Todos
+//
+//  Created by Sam Jaffe on 3/1/26.
+//
+
+import SwiftUI
+import SwiftData
+import UniformTypeIdentifiers
+
+struct SaveAsMenu: View {
+  @Environment(\.modelContext) private var modelContext
+  
+  @Query private var items: [Project]
+  @State private var showingExporter = false
+
+  var body: some View {
+    Button("Save As") {
+      showingExporter = true
+    }
+    .keyboardShortcut("S", modifiers: [.command, .shift])
+    .fileExporter(isPresented: $showingExporter, document: StubDocument(),
+                  contentType: .yaml) { result in
+      switch result {
+      case .success(let url):
+        SaveController.save(items, to: url)
+      case .failure(let error):
+        print(error.localizedDescription)
+      }
+    }
+  }
+}
+
+#Preview {
+  SaveAsMenu()
+}

+ 44 - 0
Todos/View/Menu/ImportMenu.swift

@@ -0,0 +1,44 @@
+//
+//  SaveAsMenu.swift
+//  Todos
+//
+//  Created by Sam Jaffe on 3/1/26.
+//
+
+import SwiftUI
+import SwiftData
+import UniformTypeIdentifiers
+
+struct ExportMenu: View {
+  @Environment(\.modelContext) private var modelContext
+  @AppStorage(UserDefaultsKeys.Category) var allGroups = CodableArray<Category>()
+  @AppStorage(UserDefaultsKeys.UrlHints) var allHints = CodableArray<URLHint>()
+
+  @Query private var items: [Project]
+  @State private var showingExporter = false
+
+  var body: some View {
+    Button("Export") {
+      showingExporter = true
+    }
+    .keyboardShortcut("E", modifiers: [.command])
+    .fileExporter(isPresented: $showingExporter, document: StubDocument(),
+                  contentType: .json) { result in
+      switch result {
+      case .success(let url):
+        let transfer = Transfer(projects: items, hints: allHints, categories: allGroups)
+        guard let data = try? JSONEncoder().encode(transfer)
+        else {
+          return
+        }
+        SaveController.save(data: data, to: url)
+      case .failure(let error):
+        print(error.localizedDescription)
+      }
+    }
+  }
+}
+
+#Preview {
+  SaveAsMenu()
+}

+ 8 - 0
Todos/ViewModel/Transfer.swift

@@ -0,0 +1,8 @@
+//
+//  Transfer.swift
+//  Todos
+//
+//  Created by Sam Jaffe on 3/2/26.
+//
+
+import Foundation