diff --git a/App/Sources/Views/TaskList/TaskListContent.swift b/App/Sources/Views/TaskList/TaskListContent.swift index 5857a5d..f918ad2 100644 --- a/App/Sources/Views/TaskList/TaskListContent.swift +++ b/App/Sources/Views/TaskList/TaskListContent.swift @@ -5,42 +5,82 @@ extension TaskListView { /// Internal content view that manages the dynamic SwiftData query for tasks. /// - /// This view is responsible for querying and displaying the list of tasks based on - /// the specified group and completion status filter. It exists as a separate view - /// to enable dynamic query updates when filter parameters change. + /// This view is responsible for querying and displaying the list of tasks based on the specified + /// group and completion status filter. It exists as a separate view to enable dynamic query + /// updates when filter parameters change. + /// + /// Task reordering is enabled based on the ``TodoGroup/canReorder`` property. + /// Groups that support reordering (``TodoGroup/today``, ``TodoGroup/upcoming``, + /// ``TodoGroup/overdue``) allow users to drag and drop tasks to change their order. + /// The ``TodoGroup/completed`` group does not support reordering as tasks are + /// displayed in chronological order by completion date. struct Content: View { + // MARK: Type aliases + + /// Closure type for handling task reorder operations. + /// + /// - Parameters: + /// - tasks: The current array of tasks. + /// - source: The index set of items being moved. + /// - destination: The destination index for the moved items. + typealias ReorderClosure = ([Todo], IndexSet, Int) -> Void + // MARK: Properties /// Query to fetch todos filtered by group and completion status. /// - /// This query is initialized with a `FetchDescriptor` that applies predicates + /// This query is initialized with a todo fetch descriptor that applies predicates /// based on the group type and completion filter. SwiftData automatically /// monitors changes and updates the view when the underlying data changes. + /// + /// Tasks are sorted by their `order` property first, then by date-based criteria + /// specific to each group, enabling both manual and automatic ordering. @Query private var tasks: [Todo] + /// Indicates whether tasks in this view can be reordered through drag and drop. + /// + /// This value is derived from the ``TodoGroup/canReorder`` property and + /// determines whether the `.onMove` modifier is applied to the task list. + private let canReorder: Bool + + /// Closure called when tasks are reordered through drag and drop. + /// + /// This closure is invoked by the `.onMove` modifier and is responsible for + /// updating the `order` property of affected tasks to reflect their new positions. + private let onReorder: ReorderClosure + // MARK: Initializers - /// Creates a content view with a configured task query. + /// Creates a content view with a configured task query and reorder handler. /// - /// This initializer sets up the `@Query` with a fetch descriptor that filters + /// This initializer sets up a query with a fetch descriptor that filters /// tasks based on the specified group and completion status. When these parameters /// change, SwiftUI recreates the view with new values, causing the query to update. /// + /// The reordering capability is automatically determined based on the + /// ``TodoGroup/canReorder`` property. For groups that support reordering, + /// the provided closure will be called when users drag and drop tasks to new positions. + /// /// - Parameters: /// - group: The todo group category to filter by (today, upcoming, overdue, or completed). /// - showCompleted: Whether to include completed tasks in the results. Only applies to groups other than `.completed`. + /// - onReorder: A closure called when tasks are reordered. init( group: TodoGroup, - showCompleted: Bool + showCompleted: Bool, + onReorder: @escaping ReorderClosure ) { - _tasks = .init( + self._tasks = .init( FetchDescriptor( group: group, showCompleted: showCompleted ) ) + + self.canReorder = group.canReorder + self.onReorder = onReorder } // MARK: Body @@ -49,6 +89,12 @@ extension TaskListView { /// /// Displays either a content unavailable state or a list of tasks /// based on whether any tasks match the current filters. + /// + /// When tasks are present, displays them in a grouped list. The list supports + /// drag and drop reordering for groups where `canReorder` is true, allowing + /// users to manually prioritize tasks within the group. For groups where + /// reordering is disabled (such as completed tasks), the list is displayed + /// in read-only mode with chronological sorting. var body: some View { Group { if tasks.isEmpty { @@ -59,8 +105,18 @@ extension TaskListView { } else { List { - ForEach(tasks) { todo in - TaskListView.Item(todo) + if canReorder { + ForEach(tasks) { + TaskListView.Item($0) + } + .onMove { source, destination in + onReorder(tasks, source, destination) + } + } + else { + ForEach(tasks) { + TaskListView.Item($0) + } } } .listStyle(.grouped) @@ -80,7 +136,9 @@ extension TaskListView { TaskListView.Content( group: .today, showCompleted: .random() - ) + ) { _, _, _ in + // On reorder closure + } } #Preview( @@ -90,7 +148,9 @@ extension TaskListView { TaskListView.Content( group: .today, showCompleted: false - ) + ) { _, _, _ in + // On reorder closure + } } #Preview( @@ -100,5 +160,19 @@ extension TaskListView { TaskListView.Content( group: .today, showCompleted: true - ) + ) { _, _, _ in + // On reorder closure + } +} + +#Preview( + "Task list content when only completed tasks shown", + traits: .sampleTasks +) { + TaskListView.Content( + group: .completed, + showCompleted: .random() + ) { _, _, _ in + // On reorder closure + } }