ContentView.swift 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. //
  2. // ContentView.swift
  3. // Todos
  4. //
  5. // Created by Sam Jaffe on 2/28/26.
  6. //
  7. import SwiftUI
  8. import SwiftData
  9. struct ContentView: View {
  10. @Environment(\.modelContext) private var modelContext
  11. @AppStorage(UserDefaultsKeys.WeekStart) private var weekStart = Date()
  12. let inPreview = ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1"
  13. @Query private var items: [Category]
  14. @State private var showingPopup = false
  15. @State private var currentHint: URLHint = URLHint()
  16. var body: some View {
  17. NavigationSplitView {
  18. List {
  19. ForEach(items) { item in
  20. NavigationLink {
  21. CategoryPanelView(item: item)
  22. } label: {
  23. CategorySidebarView(name: Bindable(item).name)
  24. } .contextMenu {
  25. Button(action: { deleteItem(item: item) }) {
  26. Label("Delete", systemImage: "trash")
  27. }
  28. }
  29. }
  30. .onDelete(perform: deleteItems)
  31. }
  32. .navigationSplitViewColumnWidth(min: 180, ideal: 200)
  33. .toolbar {
  34. ToolbarItem {
  35. Button(action: addItem) {
  36. Label("New Category", systemImage: "plus")
  37. }
  38. }
  39. }
  40. } detail: {
  41. Text("Select an item")
  42. }
  43. .onAppear(perform: autosave)
  44. }
  45. private func addItem() {
  46. withAnimation {
  47. let newItem = Category(timestamp: Date())
  48. let count = items.count(where: { $0.name.starts(with: "New Category") })
  49. if (count > 0) {
  50. newItem.name += " (\(count))"
  51. }
  52. modelContext.insert(newItem)
  53. }
  54. }
  55. private func autosave() {
  56. if inPreview {
  57. // This isn't great, but we shouldn't be running this in a preview
  58. // environment in the first place, so w/e.
  59. return
  60. }
  61. let now = Date()
  62. let sunday = Calendar.current.nextDate(after: weekStart,
  63. matching: DateComponents(weekday: 1),
  64. matchingPolicy: .nextTime)
  65. if now <= sunday! {
  66. return
  67. }
  68. let ymd = weekStart.formatted(.iso8601.year().month().day())
  69. save(to: URL.documentsDirectory.appending(path: "Todo \(ymd).yaml"))
  70. weekStart = now
  71. cleanup()
  72. }
  73. private func save(to: URL) {
  74. let data = Data(items.map({ $0.yaml() }).joined().utf8)
  75. do {
  76. try data.write(to: to, options: [.atomic, .completeFileProtection])
  77. let input = try String(contentsOf: to, encoding: .utf8)
  78. print(input)
  79. } catch {
  80. print(error.localizedDescription)
  81. }
  82. }
  83. private func cleanup() {
  84. for item in items {
  85. item.tasks.removeAll(where: { $0.status == .Complete })
  86. for task in item.tasks {
  87. if task.status == .InProgress {
  88. task.status = .Todo
  89. }
  90. task.subtasks.removeAll(where: { $0.status == .Complete })
  91. for subtask in task.subtasks {
  92. if subtask.status == .InProgress {
  93. subtask.status = .Todo
  94. }
  95. }
  96. }
  97. }
  98. }
  99. private func deleteItem(item: Category) {
  100. withAnimation {
  101. modelContext.delete(item)
  102. }
  103. }
  104. private func deleteItems(offsets: IndexSet) {
  105. withAnimation {
  106. for index in offsets {
  107. modelContext.delete(items[index])
  108. }
  109. }
  110. }
  111. }
  112. #Preview {
  113. ContentView()
  114. .modelContainer(for: Category.self, inMemory: true)
  115. }