1
0

4 Commity 339a9326ad ... 17ed4f508a

Autor SHA1 Správa Dátum
  Sam Jaffe 17ed4f508a refactor: swiftlint 1 týždeň pred
  Sam Jaffe 1fab220e70 refactor: implement better handling of Autosave/RotateContent when the button is pressed 1 týždeň pred
  Sam Jaffe 8d71b7e8b4 refactor: change SaveController to not be static 1 týždeň pred
  Sam Jaffe 1bb7d46adb refactor: add menu icons 1 týždeň pred

+ 58 - 0
Todos/Controller/RotateController.swift

@@ -0,0 +1,58 @@
+//
+//  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 where item.status == .inProgress {
+      item.status = .todo
+    }
+  }
+}

+ 7 - 3
Todos/Controller/SaveController.swift

@@ -8,15 +8,19 @@
 import Foundation
 
 final class SaveController {
-  static func filename(date: String) -> URL {
+  private func filename(date: String) -> URL {
     URL.documentsDirectory.appending(path: "Weekly Tracker \(date).yml")
   }
 
-  static func save(_ items: [Project], toUrl: URL) {
+  func save(_ items: [Project], onDate: String) {
+    save(items, toUrl: filename(date: onDate))
+  }
+
+  func save(_ items: [Project], toUrl: URL) {
     save(data: Data(items.map({ $0.yaml() }).joined().utf8), toUrl: toUrl)
   }
 
-  static func save(data: Data, toUrl: URL) {
+  func save(data: Data, toUrl: URL) {
     do {
       try data.write(to: toUrl, options: [.atomic, .completeFileProtection])
       let input = try String(contentsOf: toUrl, encoding: .utf8)

+ 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)
 }

+ 0 - 75
Todos/View/Menu/AutosaveMenu.swift

@@ -1,75 +0,0 @@
-//
-//  SaveAndRefreshMenu.swift
-//  Todos
-//
-//  Created by Sam Jaffe on 3/9/26.
-//
-
-import SwiftUI
-import SwiftData
-internal import Combine
-
-struct AutosaveMenu: 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
-
-  var body: some View {
-    Button("Save and Cleanup") {
-      tryAutosave()
-    }
-    .keyboardShortcut("R", modifiers: [.command, .shift])
-//    .disabled(!shouldAutosave)
-
-    Text("     Next Autosave: after \(ymd(nextSunday(weekStart)))")
-      .onAppear(perform: tryAutosave)
-  }
-
-  func tryAutosave() {
-    if shouldAutosave && !inPreview {
-      saveAndCleanup(weekStart)
-      weekStart = Date()
-      hasAutosave = true
-    }
-  }
-
-  func nextSunday(_ date: Date) -> Date {
-    Calendar.current.nextDate(after: date,
-                              matching: DateComponents(weekday: 1),
-                              matchingPolicy: .nextTime)!
-  }
-
-  func ymd(_ date: Date) -> String {
-    date.formatted(.iso8601.year().month().day())
-  }
-
-  var shouldAutosave: Bool {
-    ymd(Date()) > ymd(nextSunday(weekStart))
-  }
-
-  func saveAndCleanup(_ date: Date) {
-    SaveController.save(items, toUrl: SaveController.filename(date: 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)
-}

+ 2 - 2
Todos/View/Menu/ExportMenu.swift

@@ -18,7 +18,7 @@ struct ExportMenu: View {
   @State private var showingExporter = false
 
   var body: some View {
-    Button("Export") {
+    Button("Export", systemImage: "square.and.arrow.up") {
       showingExporter = true
     }
     .keyboardShortcut("E", modifiers: [.command, .shift])
@@ -31,7 +31,7 @@ struct ExportMenu: View {
         else {
           return
         }
-        SaveController.save(data: data, toUrl: url)
+        SaveController().save(data: data, toUrl: url)
       case .failure(let error):
         print(error.localizedDescription)
       }

+ 1 - 1
Todos/View/Menu/ImportMenu.swift

@@ -17,7 +17,7 @@ struct ImportMenu: View {
   @State private var showingExporter = false
 
   var body: some View {
-    Button("Import") {
+    Button("Import", systemImage: "square.and.arrow.down.on.square") {
       showingExporter = true
     }
     .keyboardShortcut("I", modifiers: [.command, .shift])

+ 1 - 1
Todos/View/Menu/NewProjectMenu.swift

@@ -13,7 +13,7 @@ struct NewProjectMenu: View {
   @Query private var items: [Project]
 
   var body: some View {
-    Button("New Project", action: addItem)
+    Button("New Project", systemImage: "plus", action: addItem)
       .keyboardShortcut("N", modifiers: [.command])
   }
 

+ 54 - 0
Todos/View/Menu/RotateContentMenu.swift

@@ -0,0 +1,54 @@
+//
+//  SaveAndRefreshMenu.swift
+//  Todos
+//
+//  Created by Sam Jaffe on 3/9/26.
+//
+
+import SwiftUI
+import SwiftData
+internal import Combine
+
+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 rotate: RotateTracking
+
+  var body: some View {
+    Button("Save and Cleanup", systemImage: "arrow.3.trianglepath") {
+      rotate.summary = RotateController().summarize(items)
+      rotate.manuallyInvoked = true
+    }
+    .keyboardShortcut("R", modifiers: [.command, .shift])
+//    .disabled(!shouldAutosave)
+    .onAppear(perform: tryAutosave)
+    .help("Will save as \(ymd(weekStart)) after \(ymd(nextSunday(weekStart)))")
+  }
+
+  func tryAutosave() {
+    if shouldAutosave && !inPreview {
+      RotateController().saveAndRotate(items, &weekStart, &rotate)
+      rotate.hasTriggeredAutosave = true
+    }
+  }
+
+  func nextSunday(_ date: Date) -> Date {
+    Calendar.current.nextDate(after: date,
+                              matching: DateComponents(weekday: 1),
+                              matchingPolicy: .nextTime)!
+  }
+
+  func ymd(_ date: Date) -> String { RotateController.ymd(date) }
+
+  var shouldAutosave: Bool {
+    ymd(Date()) > ymd(nextSunday(weekStart))
+  }
+}
+
+#Preview {
+  @Previewable @State var rotate = RotateTracking()
+  RotateContentMenu(rotate: $rotate)
+}

+ 2 - 2
Todos/View/Menu/SaveAsMenu.swift

@@ -16,7 +16,7 @@ struct SaveAsMenu: View {
   @State private var showingExporter = false
 
   var body: some View {
-    Button("Save As") {
+    Button("Save As", systemImage: "plus.square.on.square") {
       showingExporter = true
     }
     .keyboardShortcut("S", modifiers: [.command, .shift])
@@ -24,7 +24,7 @@ struct SaveAsMenu: View {
                   contentType: .yaml) { result in
       switch result {
       case .success(let url):
-        SaveController.save(items, toUrl: url)
+        SaveController().save(items, toUrl: url)
       case .failure(let error):
         print(error.localizedDescription)
       }

+ 2 - 3
Todos/View/Menu/SaveSnapshotMenu.swift

@@ -14,9 +14,8 @@ struct SaveSnapshotMenu: View {
   @Query private var items: [Project]
 
   var body: some View {
-    Button("Save Snapshot") {
-      let snapshot = Date().formatted(.iso8601)
-      SaveController.save(items, toUrl: SaveController.filename(date: snapshot))
+    Button("Save Snapshot", systemImage: "square.and.arrow.down") {
+      SaveController().save(items, onDate: Date().formatted(.iso8601))
     }
     .keyboardShortcut("S", modifiers: .command)
   }

+ 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() {}
+}