Jelajahi Sumber

feat: add filtering on status, relocate Move toggle into popover menu

Sam Jaffe 2 minggu lalu
induk
melakukan
db415b3c2d

+ 40 - 0
Todos/View/Components/StatusChecklist.swift

@@ -0,0 +1,40 @@
+//
+//  StatusChecklist.swift
+//  Todos
+//
+//  Created by Sam Jaffe on 3/7/26.
+//
+
+import SwiftUI
+
+struct StatusChecklist: View {
+  @Binding var statuses : StatusList
+
+  var body: some View {
+    VStack(alignment: .leading) {
+      Text("Filter by Status")
+        .font(.title3.bold())
+      ForEach(Status.allCases) { unit in
+        Toggle("Show \"\(unit.description)\"", systemImage: unit.label,
+               isOn: bindingFor(unit))
+        .padding(.leading, 15)
+      }
+    }
+  }
+  
+  private func bindingFor(_ index: Status) -> Binding<Bool> {
+    switch (index) {
+    case .todo: return $statuses.todo
+    case .complete: return $statuses.complete
+    case .inProgress: return $statuses.inProgess
+    case .hiatus: return $statuses.hiatus
+    case .waiting: return $statuses.waiting
+    case .unknown: return $statuses.unknown
+    }
+  }
+}
+
+#Preview {
+  @Previewable @State var statuses = StatusList()
+  StatusChecklist(statuses: $statuses)
+}

+ 29 - 4
Todos/View/ProjectPanelView.swift

@@ -14,7 +14,10 @@ struct ProjectPanelView: View {
 
   @Bindable var item: Project
   @State private var empty = Category()
+
+  @State private var showDialogue = false
   @State private var move = false
+  @State private var statuses = StatusList()
 
   var body: some View {
     HStack {
@@ -27,8 +30,24 @@ struct ProjectPanelView: View {
       }
       .help("New Task")
       .padding(.trailing, 10)
-      Toggle("Move Tasks", isOn: $move)
-        .padding(.trailing, 10)
+      Button {
+        showDialogue = !showDialogue
+      } label: {
+        Label("", systemImage: "ellipsis.circle")
+          .foregroundStyle(.gray)
+          .font(.title)
+      }
+      .buttonStyle(.borderless)
+      .popover(isPresented: $showDialogue) {
+        List{
+          HStack {
+            Label("", systemImage: "arrow.up.arrow.down")
+            Toggle("Move Tasks", isOn: $move)
+          }
+          StatusChecklist(statuses: $statuses)
+        }
+      }
+      Text("")
     }
     HStack(alignment: .top) {
       TextField("Project Notes", text: $item.notes, axis: .vertical)
@@ -44,7 +63,7 @@ struct ProjectPanelView: View {
       }.help("Default category for new Tasks")
     }
     List {
-      ForEach($item.tasks.sorted(by: Task.less), id: \.id) { task in
+      ForEach(selected($item.tasks), id: \.id) { task in
         TaskView(task: task)
           .swipeActions {
             Button("Delete", systemImage: "trash", role: .destructive) {
@@ -52,7 +71,7 @@ struct ProjectPanelView: View {
             }
           }
 
-        ForEach(task.subtasks.sorted(by: SubTask.less), id: \.id) { subtask in
+        ForEach(selected(task.subtasks), id: \.id) { subtask in
           SubTaskView(task: subtask)
             .swipeActions {
               Button("Delete", systemImage: "trash", role: .destructive) {
@@ -67,6 +86,12 @@ struct ProjectPanelView: View {
       .moveDisabled(!move)
     }
   }
+  
+  private func selected<T : Ordered & Filterable>(_ items: Binding<[T]>) -> [Binding<T>] {
+    return items.sorted(by: T.less).filter({
+      statuses.test($0.wrappedValue.status)
+    })
+  }
 
   private func addItem() {
     withAnimation {

+ 15 - 0
Todos/ViewModel/Filterable.swift

@@ -0,0 +1,15 @@
+//
+//  Filterable.swift
+//  Todos
+//
+//  Created by Sam Jaffe on 3/7/26.
+//
+
+import Foundation
+
+protocol Filterable {
+  var status: Status { get }
+}
+
+extension SubTask : Filterable {}
+extension Task : Filterable {}

+ 32 - 0
Todos/ViewModel/StatusList.swift

@@ -0,0 +1,32 @@
+//
+//  StatusList.swift
+//  Todos
+//
+//  Created by Sam Jaffe on 3/7/26.
+//
+
+import Foundation
+import SwiftData
+
+@Model // Must be @Model to watch changes to children
+final class StatusList {
+  var todo: Bool = true
+  var complete: Bool = true
+  var inProgess: Bool = true
+  var hiatus: Bool = true
+  var waiting: Bool = true
+  var unknown: Bool = true
+  
+  init() {}
+  
+  func test(_ index: Status) -> Bool {
+    switch (index) {
+    case .todo: return todo
+    case .complete: return complete
+    case .inProgress: return inProgess
+    case .hiatus: return hiatus
+    case .waiting: return waiting
+    case .unknown: return unknown
+    }
+  }
+}