Quellcode durchsuchen

refactor: implement better handling of Autosave/RotateContent when the button is pressed

Sam Jaffe vor 1 Woche
Ursprung
Commit
1fab220e70

+ 60 - 0
Todos/Controller/RotateController.swift

@@ -0,0 +1,60 @@
+//
+//  RotateController.swift
+//  Todos
+//
+//  Created by Sam Jaffe on 3/13/26.
+//
+
+import Foundation
+
+final class RotateController {
+  static func ymd(_ date: Date) -> String { RotateController().ymd(date) }
+  func ymd(_ date: Date) -> String {
+    date.formatted(.iso8601.year().month().day())
+  }
+
+  func saveAndRotate(_ items: [Project], _ date: inout Date,
+                     _ tracking: inout RotateTracking) {
+    SaveController().save(items, onDate: ymd(date))
+    tracking.summary = summarize(items)
+    rotate(items, &tracking)
+    date = Date()
+  }
+  
+  func summarize(_ items: [Project]) -> String? {
+    var summary = ""
+    for item in items {
+      item.tasks.filter(isComplete).map(\.name).forEach { name in
+        summary += "- \(item.name) > \(name)\n"
+      }
+      for task in item.tasks {
+        task.subtasks.filter(isComplete).map(\.name).forEach { name in
+          summary += "- \(item.name) > \(task.name) > \(name)\n"
+        }
+      }
+    }
+    return summary.isEmpty ? nil : summary
+  }
+
+  private func rotate(_ items: [Project], _ tracking: inout RotateTracking) {
+    for item in items {
+      cleanStatuses(&item.tasks)
+      for task in item.tasks {
+        cleanStatuses(&task.subtasks)
+      }
+    }
+  }
+
+  private func isComplete<T : Filterable>(_ arg: T) -> Bool {
+    arg.status == .complete
+  }
+
+  private func cleanStatuses<T : Filterable>(_ items: inout [T]) {
+    items.removeAll(where: isComplete)
+    for var item in items {
+      if item.status == .inProgress {
+        item.status = .todo
+      }
+    }
+  }
+}

+ 3 - 3
Todos/TodosApp.swift

@@ -33,11 +33,11 @@ struct TodosApp: App {
     }
   }()
 
-  @State private var hasAutosave: Bool = false
+  @State private var rotate = RotateTracking()
 
   var body: some Scene {
     WindowGroup {
-      ContentView(hasAutosave: $hasAutosave).onAppear {
+      ContentView(rotate: $rotate).onAppear {
         // Disable the tab bar options
         NSWindow.allowsAutomaticWindowTabbing = false
       }
@@ -57,7 +57,7 @@ struct TodosApp: App {
           .modelContainer(sharedModelContainer)
         ImportMenu()
           .modelContainer(sharedModelContainer)
-        AutosaveMenu(hasAutosave: $hasAutosave)
+        RotateContentMenu(rotate: $rotate)
           .modelContainer(sharedModelContainer)
       }
     }

+ 23 - 5
Todos/View/ContentView.swift

@@ -10,10 +10,11 @@ import SwiftData
 
 struct ContentView: View {
   @Environment(\.modelContext) private var modelContext
+  @AppStorage(UserDefaultsKeys.WeekStart) private var weekStart = Date()
 
   @Query(sort: \Project.sortOrder) private var items: [Project]
   @State private var selection: Project?
-  @Binding var hasAutosave: Bool
+  @Binding var rotate: RotateTracking
 
   var body: some View {
     NavigationSplitView {
@@ -45,12 +46,29 @@ struct ContentView: View {
         ContentUnavailableView(header, systemImage: "doc.text.image.fill")
       }
     }
-    .alert("Autosave", isPresented: $hasAutosave) {
+    .confirmationDialog("Manually run \"Save and Cleanup\"?",
+                        isPresented: $rotate.manuallyInvoked) {
+      Button("Yes") {
+        RotateController().saveAndRotate(items, &weekStart, &rotate)
+        rotate.summary = nil
+      }
+      Button("Cancel", role: .cancel) {}
+    } message: {
+      if let summary = rotate.summary {
+        Text(summary)
+      }
+    }
+    .alert("Autosave", isPresented: $rotate.hasTriggeredAutosave) {
+      Text("Cleaned up the following Tasks:\n" + (rotate.summary ?? ""))
       Button("OK") {
-        hasAutosave = false
+        rotate.hasTriggeredAutosave = false
+        rotate.summary = nil
       }
     } message: {
       Text("All completed tasks/subtasks have been deleted")
+      if let summary = rotate.summary {
+        Text(summary)
+      }
     }
   }
 
@@ -82,7 +100,7 @@ struct ContentView: View {
 }
 
 #Preview {
-  @Previewable @State var hasAutosave = false
-  ContentView(hasAutosave: $hasAutosave)
+  @Previewable @State var rotate = RotateTracking()
+  ContentView(rotate: $rotate)
     .modelContainer(for: Project.self, inMemory: true)
 }

+ 9 - 29
Todos/View/Menu/AutosaveMenu.swift

@@ -9,17 +9,18 @@ import SwiftUI
 import SwiftData
 internal import Combine
 
-struct AutosaveMenu: View {
+struct RotateContentMenu: View {
   @Environment(\.modelContext) private var modelContext
   @AppStorage(UserDefaultsKeys.WeekStart) private var weekStart = Date()
   let inPreview = ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1"
 
   @Query private var items: [Project]
-  @Binding var hasAutosave: Bool
+  @Binding var rotate: RotateTracking
 
   var body: some View {
     Button("Save and Cleanup", systemImage: "arrow.3.trianglepath") {
-      tryAutosave()
+      rotate.summary = RotateController().summarize(items)
+      rotate.manuallyInvoked = true
     }
     .keyboardShortcut("R", modifiers: [.command, .shift])
 //    .disabled(!shouldAutosave)
@@ -29,9 +30,8 @@ struct AutosaveMenu: View {
 
   func tryAutosave() {
     if shouldAutosave && !inPreview {
-      saveAndCleanup(weekStart)
-      weekStart = Date()
-      hasAutosave = true
+      RotateController().saveAndRotate(items, &weekStart, &rotate)
+      rotate.hasTriggeredAutosave = true
     }
   }
 
@@ -41,34 +41,14 @@ struct AutosaveMenu: View {
                               matchingPolicy: .nextTime)!
   }
 
-  func ymd(_ date: Date) -> String {
-    date.formatted(.iso8601.year().month().day())
-  }
+  func ymd(_ date: Date) -> String { RotateController.ymd(date) }
 
   var shouldAutosave: Bool {
     ymd(Date()) > ymd(nextSunday(weekStart))
   }
-
-  func saveAndCleanup(_ date: Date) {
-    SaveController().save(items, onDate: ymd(date))
-
-    for item in items {
-      item.tasks.removeAll(where: { $0.status == .complete })
-      for task in item.tasks {
-        if task.status == .inProgress {
-          task.status = .todo
-        }
-
-        task.subtasks.removeAll(where: { $0.status == .complete })
-        for subtask in task.subtasks where subtask.status == .inProgress {
-          subtask.status = .todo
-        }
-      }
-    }
-  }
 }
 
 #Preview {
-  @Previewable @State var hasAutosave = true
-  AutosaveMenu(hasAutosave: $hasAutosave)
+  @Previewable @State var rotate = RotateTracking()
+  RotateContentMenu(rotate: $rotate)
 }

+ 1 - 1
Todos/ViewModel/Filterable.swift

@@ -9,7 +9,7 @@ import Foundation
 
 protocol Filterable {
   var name: String { get }
-  var status: Status { get }
+  var status: Status { get set }
   func containsText(_ text: String) -> Bool
 }
 

+ 19 - 0
Todos/ViewModel/RotateTracking.swift

@@ -0,0 +1,19 @@
+//
+//  RotateTracking.swift
+//  Todos
+//
+//  Created by Sam Jaffe on 3/13/26.
+//
+
+import Foundation
+import SwiftData
+import SwiftUI
+
+@Model
+final class RotateTracking {
+  var hasTriggeredAutosave: Bool = false
+  var manuallyInvoked: Bool = false
+  var summary: String?
+  
+  init() {}
+}