ContentView.swift 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  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. @Binding var isMoveMode: Bool
  14. @Query private var items: [Project]
  15. @State private var selection: Project?
  16. var body: some View {
  17. NavigationSplitView {
  18. List(selection: $selection) {
  19. ForEach(items, id: \.self) { item in
  20. NavigationLink(value: item) {
  21. ProjectSidebarView(name: Bindable(item).name)
  22. } .contextMenu {
  23. Button(action: { deleteItem(item: item) }) {
  24. Label("Delete", systemImage: "trash")
  25. }
  26. }
  27. }
  28. .onDelete(perform: deleteItems)
  29. }
  30. .navigationSplitViewColumnWidth(min: 180, ideal: 200)
  31. .toolbar {
  32. ToolbarItem {
  33. Button(action: addItem) {
  34. Label("New Project", systemImage: "plus")
  35. }
  36. }
  37. }
  38. } detail: {
  39. if let selection = selection {
  40. ProjectPanelView(item: selection, isMoveMode: $isMoveMode)
  41. } else {
  42. let header = items.isEmpty ? "Create a New Project" : "Select a project from the sidebar"
  43. ContentUnavailableView(header, systemImage: "doc.text.image.fill")
  44. }
  45. }
  46. .onAppear(perform: autosave)
  47. }
  48. private func addItem() {
  49. withAnimation {
  50. let newItem = Project(timestamp: Date())
  51. modelContext.insert(newItem)
  52. }
  53. }
  54. private func autosave() {
  55. if inPreview {
  56. // This isn't great, but we shouldn't be running this in a preview
  57. // environment in the first place, so w/e.
  58. return
  59. }
  60. let now = Date()
  61. let sunday = Calendar.current.nextDate(after: weekStart,
  62. matching: DateComponents(weekday: 1),
  63. matchingPolicy: .nextTime)
  64. if now <= sunday! {
  65. return
  66. }
  67. let ymd = weekStart.formatted(.iso8601.year().month().day())
  68. SaveController.save(items, to: SaveController.filename(date: ymd))
  69. weekStart = now
  70. cleanup()
  71. }
  72. private func cleanup() {
  73. for item in items {
  74. item.tasks.removeAll(where: { $0.status == .Complete })
  75. for task in item.tasks {
  76. if task.status == .InProgress {
  77. task.status = .Todo
  78. }
  79. task.subtasks.removeAll(where: { $0.status == .Complete })
  80. for subtask in task.subtasks {
  81. if subtask.status == .InProgress {
  82. subtask.status = .Todo
  83. }
  84. }
  85. }
  86. }
  87. }
  88. private func deleteItem(item: Project) {
  89. if let selection = selection, selection == item {
  90. self.selection = nil
  91. }
  92. withAnimation {
  93. modelContext.delete(item)
  94. }
  95. }
  96. private func deleteItems(offsets: IndexSet) {
  97. if let selection = selection, offsets.contains(where: { items[$0] == selection }) {
  98. self.selection = nil
  99. }
  100. withAnimation {
  101. for index in offsets {
  102. modelContext.delete(items[index])
  103. }
  104. }
  105. }
  106. }
  107. #Preview {
  108. @Previewable @State var isMoveMode = false
  109. ContentView(isMoveMode: $isMoveMode)
  110. .modelContainer(for: Project.self, inMemory: true)
  111. }