Files
todos/App/Sources/Views/TaskList/TaskListContent.swift
T

220 lines
7.3 KiB
Swift
Raw Normal View History

2026-02-11 13:00:46 +00:00
import SwiftData
import SwiftUI
extension TaskListView {
/// Internal content view that manages the dynamic SwiftData query for tasks.
///
2026-02-11 15:28:22 +00:00
/// 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.
2026-02-11 13:00:46 +00:00
struct Content: View {
2026-02-11 15:28:22 +00:00
// MARK: Type aliases
2026-02-11 16:35:51 +00:00
/// Closure type for handling task deletion operations.
///
/// - Parameters:
/// - todos: The current array of todo tasks.
/// - offsets: The index set of tasks to delete.
typealias DeleteClosure = ([Todo], IndexSet) -> Void
2026-02-11 15:28:22 +00:00
/// Closure type for handling task reorder operations.
///
/// - Parameters:
2026-02-11 16:35:51 +00:00
/// - todos: The current array of todo tasks.
2026-02-11 15:28:22 +00:00
/// - source: The index set of items being moved.
/// - destination: The destination index for the moved items.
typealias ReorderClosure = ([Todo], IndexSet, Int) -> Void
2026-02-11 13:00:46 +00:00
// MARK: Properties
2026-02-11 16:35:51 +00:00
/// Query to fetch todo tasks filtered by group and completion status.
2026-02-11 13:00:46 +00:00
///
2026-02-11 15:28:22 +00:00
/// This query is initialized with a todo fetch descriptor that applies predicates
2026-02-11 13:00:46 +00:00
/// based on the group type and completion filter. SwiftData automatically
/// monitors changes and updates the view when the underlying data changes.
2026-02-11 15:28:22 +00:00
///
/// Tasks are sorted by their `order` property first, then by date-based criteria
/// specific to each group, enabling both manual and automatic ordering.
2026-02-11 13:00:46 +00:00
@Query
2026-02-11 16:35:51 +00:00
private var todos: [Todo]
2026-02-11 13:00:46 +00:00
2026-02-11 15:28:22 +00:00
/// 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
2026-02-11 16:35:51 +00:00
/// Closure called when tasks are deleted through swipe-to-delete.
///
/// This closure is invoked by the `.onDelete` modifier and is responsible for
/// removing the selected tasks from the model context.
private let onDelete: DeleteClosure
2026-02-11 15:28:22 +00:00
/// 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
2026-02-11 13:00:46 +00:00
// MARK: Initializers
2026-02-11 15:28:22 +00:00
/// Creates a content view with a configured task query and reorder handler.
2026-02-11 13:00:46 +00:00
///
2026-02-11 15:28:22 +00:00
/// This initializer sets up a query with a fetch descriptor that filters
2026-02-11 13:00:46 +00:00
/// 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.
///
2026-02-11 15:28:22 +00:00
/// 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.
///
2026-02-11 13:00:46 +00:00
/// - 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`.
2026-02-11 15:28:22 +00:00
/// - onReorder: A closure called when tasks are reordered.
2026-02-11 16:35:51 +00:00
/// - onDelete: A closure called when tasks are deleted.
2026-02-11 13:00:46 +00:00
init(
group: TodoGroup,
2026-02-11 15:28:22 +00:00
showCompleted: Bool,
2026-02-11 16:35:51 +00:00
onDelete: @escaping DeleteClosure,
2026-02-11 15:28:22 +00:00
onReorder: @escaping ReorderClosure
2026-02-11 13:00:46 +00:00
) {
2026-02-11 16:35:51 +00:00
self._todos = .init(
2026-02-11 13:00:46 +00:00
FetchDescriptor<Todo>(
group: group,
showCompleted: showCompleted
)
)
2026-02-11 15:28:22 +00:00
self.canReorder = group.canReorder
2026-02-11 16:35:51 +00:00
self.onDelete = onDelete
2026-02-11 15:28:22 +00:00
self.onReorder = onReorder
2026-02-11 13:00:46 +00:00
}
// MARK: Body
/// The content and behavior of the view.
///
/// Displays either a content unavailable state or a list of tasks
/// based on whether any tasks match the current filters.
2026-02-11 15:28:22 +00:00
///
/// 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.
2026-02-11 13:00:46 +00:00
var body: some View {
Group {
2026-02-11 16:35:51 +00:00
if todos.isEmpty {
2026-02-11 13:00:46 +00:00
ContentUnavailableView(
"view.task-list-content.content-unavailable.title",
systemImage: "checkmark.circle"
)
}
else {
List {
2026-02-11 16:35:51 +00:00
items
2026-02-11 13:00:46 +00:00
}
.listStyle(.grouped)
2026-02-12 01:21:19 +00:00
.onResignFocus()
2026-02-11 13:00:46 +00:00
}
}
}
}
}
2026-02-11 16:35:51 +00:00
// MARK: - Helpers
private extension TaskListView.Content {
// MARK: Computed
/// A view that renders the task items with appropriate modifiers.
@ViewBuilder
var items: some View {
let items = ForEach(todos) {
TaskListView.Item($0)
}
.onDelete { offsets in
onDelete(todos, offsets)
}
if canReorder {
items.onMove { source, destination in
onReorder(todos, source, destination)
}
}
else {
items
}
}
}
2026-02-11 13:00:46 +00:00
// MARK: - Previews
#Preview(
"Task list content when there are no tasks",
traits: .emptyContainer
) {
TaskListView.Content(
group: .today,
showCompleted: .random()
2026-02-11 16:35:51 +00:00
) { _, _ in
// On delete closure
} onReorder: { _, _, _ in
2026-02-11 15:28:22 +00:00
// On reorder closure
}
2026-02-11 13:00:46 +00:00
}
#Preview(
"Task list content when completed tasks not shown",
traits: .sampleTasks
) {
TaskListView.Content(
group: .today,
showCompleted: false
2026-02-11 16:35:51 +00:00
) { _, _ in
// On delete closure
} onReorder: { _, _, _ in
2026-02-11 15:28:22 +00:00
// On reorder closure
}
2026-02-11 13:00:46 +00:00
}
#Preview(
"Task list content when completed tasks shown",
traits: .sampleTasks
) {
TaskListView.Content(
group: .today,
showCompleted: true
2026-02-11 16:35:51 +00:00
) { _, _ in
// On delete closure
} onReorder: { _, _, _ in
2026-02-11 15:28:22 +00:00
// On reorder closure
}
}
#Preview(
"Task list content when only completed tasks shown",
traits: .sampleTasks
) {
TaskListView.Content(
group: .completed,
showCompleted: .random()
2026-02-11 16:35:51 +00:00
) { _, _ in
// On delete closure
} onReorder: { _, _, _ in
2026-02-11 15:28:22 +00:00
// On reorder closure
}
2026-02-11 13:00:46 +00:00
}