From 1385f0df344f220b324631ee3a216de7ec866f0e Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Tue, 10 Feb 2026 15:41:45 +0100 Subject: [PATCH] Implemented the TaskListItem view in the app target. --- App/Resources/Localizable.xcstrings | 33 ++++ App/Sources/Views/TaskList/TaskListItem.swift | 148 ++++++++++++++++++ 2 files changed, 181 insertions(+) create mode 100644 App/Sources/Views/TaskList/TaskListItem.swift diff --git a/App/Resources/Localizable.xcstrings b/App/Resources/Localizable.xcstrings index 46bd4cd..ca9900d 100644 --- a/App/Resources/Localizable.xcstrings +++ b/App/Resources/Localizable.xcstrings @@ -121,6 +121,39 @@ } } } + "view.task-list-item.placeholder.completed" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tap to complete or not your task" + } + } + } + }, + "view.task-list-item.placeholder.note" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Add extra notes to your task (if needed)" + } + } + } + }, + "view.task-list-item.placeholder.title" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Give a title to your task" + } + } + } + }, } }, "version" : "1.1" diff --git a/App/Sources/Views/TaskList/TaskListItem.swift b/App/Sources/Views/TaskList/TaskListItem.swift new file mode 100644 index 0000000..7a825ba --- /dev/null +++ b/App/Sources/Views/TaskList/TaskListItem.swift @@ -0,0 +1,148 @@ +import SwiftUI + +extension TaskListView { + + /// A view that displays a single editable todo task in a list. + /// + /// This view provides a comprehensive interface for viewing and editing task information, + /// including: + /// - A completion toggle using a circular checkbox design + /// - An editable title field with multi-line support + /// - An optional note field that appears when the task has notes + /// - A date picker for selecting the task's due date + /// + /// The view automatically adjusts its appearance based on completion state, dimming + /// completed tasks to provide visual feedback. All text fields support automatic + /// capitalization and keyboard correction for improved user experience. + struct Item: View { + + // MARK: Properties + + /// The todo task to display. + @Bindable + private var item: Todo + + // MARK: Initializers + + /// Creates a new task list item view for the specified todo. + /// + /// The item is wrapped in a `@Bindable` property wrapper to enable two-way + /// data binding for editing task properties directly within the view. + /// + /// - Parameter item: The todo task to display and edit. + init(_ item: Todo) { + _item = Bindable(item) + } + + // MARK: Body + + /// The content and behavior of the view. + /// + /// Renders an editable task list item with a completion toggle, title field, + /// optional note field, and date picker. The view's foreground color automatically + /// adjusts based on completion state, and changes animate smoothly. + var body: some View { + HStack( + alignment: .top, + spacing: 16 + ) { + Toggle( + "view.task-list-item.placeholder.completed", + isOn: isCompleted + ) + .toggleStyle(.completion) + + VStack( + alignment: .leading, + spacing: 8 + ) { + TextField( + "view.task-list-item.placeholder.title", + text: $item.title, + axis: .vertical + ) + .font(.headline) + + if showNote { + TextField( + "view.task-list-item.placeholder.note", + text: note, + axis: .vertical + ) + .font(.subheadline) + } + + DatePicker(selection: $item.due, displayedComponents: .date) { + Image(systemName: "calendar") + } + .frame(width: 160) + .disabled(isCompleted.wrappedValue) + } + .autocapitalization(.sentences) + .autocorrectionDisabled(false) + .foregroundStyle(foregroundColor) + .keyboardType(.asciiCapable) + } + .animation( + .easeInOut, + value: item.completed + ) + } + } + +} + +// MARK: - Helpers + +private extension TaskListView.Item { + + // MARK: Bindings + + var isCompleted: Binding { + .init { + item.completed != nil + } set: { + item.completed = $0 ? .now : nil + } + } + + var note: Binding { + .init { + item.note ?? "" + } set: { + item.note = $0.isEmpty ? nil : $0 + } + } + + // MARK: Computed + + var foregroundColor: Color { + item.completed == nil ? .primary : .secondary + } + + var showNote: Bool { + guard let note = item.note else { + return false + } + + return note.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == false + } + +} + +// MARK: - Previews + +#Preview("Task list item") { + @Previewable + var todo: Todo = .init( + title: "Complete project documentation", + note: "Add detailed comments and README", + due: .now, + completed: nil + ) + + List { + TaskListView.Item(todo) + } + .listStyle(.grouped) +}