Task edition improvements #9

Merged
javier merged 12 commits from app/todo-edit into main 2026-02-12 01:21:19 +00:00
Showing only changes of commit 81859cf841 - Show all commits
+17 -91
View File
@@ -20,7 +20,7 @@ extension TaskListView {
/// Tracks which text field currently has keyboard focus. /// Tracks which text field currently has keyboard focus.
@FocusState @FocusState
private var focusOnField: FieldFocus? private var inputFocus: InputFieldFocus?
/// The todo task to display. /// The todo task to display.
@Bindable @Bindable
@@ -35,7 +35,7 @@ extension TaskListView {
/// ///
/// - Parameter item: The todo task to display and edit. /// - Parameter item: The todo task to display and edit.
init(_ item: Todo) { init(_ item: Todo) {
_item = Bindable(item) self._item = Bindable(item)
} }
// MARK: Body // MARK: Body
@@ -65,14 +65,19 @@ extension TaskListView {
) { ) {
TextField( TextField(
"view.task-list-item.placeholder.title", "view.task-list-item.placeholder.title",
text: title, text: $item.title,
axis: .vertical axis: .vertical
) )
.font(.headline) .font(.headline)
.focused( .focused(
$focusOnField, $inputFocus,
equals: .title equals: .title
) )
.onNewlineFocus(
$item.title,
to: .note,
on: $inputFocus
)
if isNoteShown { if isNoteShown {
TextField( TextField(
@@ -82,9 +87,13 @@ extension TaskListView {
) )
.font(.subheadline) .font(.subheadline)
.focused( .focused(
$focusOnField, $inputFocus,
equals: .note equals: .note
) )
.onEmptyNewlineResignFocus(
text: note,
from: $inputFocus
)
} }
DatePicker( DatePicker(
@@ -99,7 +108,7 @@ extension TaskListView {
.autocapitalization(.sentences) .autocapitalization(.sentences)
.autocorrectionDisabled(false) .autocorrectionDisabled(false)
.foregroundStyle(foregroundStyleView) .foregroundStyle(foregroundStyleView)
.keyboardType(.asciiCapable) .keyboardType(.default)
} }
.animation( .animation(
.easeInOut, .easeInOut,
@@ -110,20 +119,6 @@ extension TaskListView {
} }
// MARK: - Enumerations
private extension TaskListView.Item {
/// Identifies which text field should receive keyboard focus.
enum FieldFocus {
/// The note text field has focus.
case note
/// The title text field has focus.
case title
}
}
// MARK: - Helpers // MARK: - Helpers
private extension TaskListView.Item { private extension TaskListView.Item {
@@ -151,19 +146,7 @@ private extension TaskListView.Item {
.init { .init {
item.note ?? "" item.note ?? ""
} set: { } set: {
setNote(with: $0) item.note = $0.isEmpty ? nil : $0
}
}
/// A binding to the title text of a todo task.
///
/// This computed binding provides access to the title of a todo task with special handling
/// for newline characters.
var title: Binding<String> {
.init {
item.title
} set: {
setTitle(with: $0)
} }
} }
@@ -184,7 +167,7 @@ private extension TaskListView.Item {
/// - Any text field has focus (allowing users to add a note even if one doesn't exist yet) /// - Any text field has focus (allowing users to add a note even if one doesn't exist yet)
/// - The todo task has a note with non-whitespace content /// - The todo task has a note with non-whitespace content
var isNoteShown: Bool { var isNoteShown: Bool {
guard focusOnField != nil else { guard inputFocus != nil else {
guard let note = item.note else { guard let note = item.note else {
return false return false
} }
@@ -195,57 +178,6 @@ private extension TaskListView.Item {
return true return true
} }
// MARK: Functions
/// Updates the note of a todo task with special handling for newlines and empty content.
///
/// This method provides intelligent note field behavior:
/// - Empty text immediately clears the note
/// - Newlines at the end of the text trigger special handling:
/// - If the text is only whitespace/newlines, the note is cleared and focus is removed
/// - Otherwise, the text (including newlines) is saved normally
/// - All other text is saved as-is
///
/// This allows users to press return on an empty note field to dismiss it,
/// while still supporting multi-line notes.
///
/// - Parameter text: The new note text to set.
func setNote(with text: String) {
guard text.isEmpty == false else {
item.note = text
return
}
if text.last == .newLine {
guard text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == true else {
item.note = text
return
}
item.note = nil
focusOnField = nil
}
else {
item.note = text
}
}
/// Updates the title of a todo task with special handling for newline characters.
///
/// When the user presses return in the title field (adding a newline), this function
/// prevents the newline from being added to the title and instead moves keyboard
/// focus to the note field. If no newline is present, the title is updated normally.
///
/// - Parameter text: The new title text to set.
func setTitle(with text: String) {
guard text.lastIndex(of: .newLine) == nil else {
focusOnField = .note
return
}
item.title = text
}
} }
// MARK: - Previews // MARK: - Previews
@@ -264,9 +196,3 @@ private extension TaskListView.Item {
} }
.listStyle(.grouped) .listStyle(.grouped)
} }
// MARK: - Character+Constants
private extension Character {
static let newLine: Self = "\n"
}