Task.swift 2.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788
  1. //
  2. // Task.swift
  3. // Todos
  4. //
  5. // Created by Sam Jaffe on 2/28/26.
  6. //
  7. import Foundation
  8. import SwiftData
  9. import SwiftUI
  10. @Model
  11. final class Task: Codable, Ordered, Aggregate {
  12. typealias Element = SubTask
  13. var sortOrder: Int = 0
  14. var name: String
  15. var project: Project?
  16. var category: String = ""
  17. @Relationship(deleteRule: .cascade, inverse: \Tag.task)
  18. var tags: [Tag] = []
  19. @Relationship(deleteRule: .cascade, inverse: \SubTask.task)
  20. var subtasks: [SubTask] = []
  21. var notes: String = ""
  22. var status: Status = Status.todo
  23. init(name: String, parent: Project? = nil) {
  24. self.name = name
  25. self.project = parent
  26. self.category = parent?.category ?? ""
  27. self.sortOrder = parent?.tasks.count ?? 0
  28. }
  29. func move(fromOffsets: IndexSet, toOffset: Int) {
  30. subtasks.move(fromOffsets: fromOffsets, toOffset: toOffset)
  31. reindex()
  32. }
  33. func remove(_ item: Element) {
  34. subtasks.removeAll(where: { $0.id == item.id })
  35. reindex()
  36. }
  37. func reindex() {
  38. for (index, item) in subtasks.enumerated() {
  39. item.sortOrder = index
  40. }
  41. }
  42. func yaml(_ indent: Int = 0) -> String {
  43. let hdr = String(repeating: " ", count: indent)
  44. let subhdr = hdr + " # "
  45. var rval = hdr + "[\(status.rawValue)] \(name) "
  46. rval += "(\(tags.map(\.id).joined(separator: " ")))\n"
  47. if !notes.isEmpty {
  48. rval += subhdr + notes.replacingOccurrences(of: "\n", with: "\n" + subhdr) + "\n"
  49. }
  50. rval += subtasks.map({ $0.yaml(indent + 1) }).joined()
  51. return rval
  52. }
  53. enum CodingKeys: CodingKey { case name, category, tags, subtasks, notes, status }
  54. required init(from decoder: any Decoder) throws {
  55. let container = try decoder.container(keyedBy: CodingKeys.self)
  56. name = try container.decode(String.self, forKey: .name)
  57. category = try container.decode(String.self, forKey: .category)
  58. tags = try container.decode([Tag].self, forKey: .tags)
  59. subtasks = try container.decode([SubTask].self, forKey: .subtasks)
  60. notes = try container.decode(String.self, forKey: .notes)
  61. status = try container.decode(Status.self, forKey: .status)
  62. tags.forEach({ $0.task = self })
  63. for (index, item) in subtasks.enumerated() {
  64. item.task = self
  65. item.sortOrder = index
  66. }
  67. }
  68. func encode(to encoder: any Encoder) throws {
  69. var container = encoder.container(keyedBy: CodingKeys.self)
  70. try container.encode(name, forKey: .name)
  71. try container.encode(category, forKey: .category)
  72. try container.encode(tags, forKey: .tags)
  73. try container.encode(subtasks, forKey: .subtasks)
  74. try container.encode(notes, forKey: .notes)
  75. try container.encode(status, forKey: .status)
  76. }
  77. }