listless

A simple list app for Apple platforms
Log | Files | Refs | README | LICENSE

commit f7fd505561ba27161bed9b87b0021ec7ae1199c7
parent b6947ab9c1df71ddd081901fa2f227f32cbd615a
Author: Michael Camilleri <[email protected]>
Date:   Fri, 13 Mar 2026 08:20:22 +0900

Remove unnecessary shims

Diffstat:
MListless/Extensions/TaskListView+Logic.swift | 70+++++++++++++++++++++++++++++++++++-----------------------------------
MListless/Helpers/TaskListTypes.swift | 6++++++
MListless/Helpers/TaskListViewProtocol.swift | 3+--
MListlessMac/Extensions/TaskListView+Toolbar.swift | 4++--
MListlessMac/Views/TaskListView.swift | 75++++++++++++++++++++++-----------------------------------------------------
MListlessiOS/Extensions/TaskListView+Drag.swift | 8++++----
MListlessiOS/Extensions/TaskListView+NavigationHeader.swift | 2+-
MListlessiOS/Extensions/TaskListView+PullToClear.swift | 4++--
MListlessiOS/Extensions/TaskListView+Undo.swift | 6+++---
MListlessiOS/Views/TaskListView.swift | 111++++++++++++++++++++++---------------------------------------------------------
10 files changed, 106 insertions(+), 183 deletions(-)

diff --git a/Listless/Extensions/TaskListView+Logic.swift b/Listless/Extensions/TaskListView+Logic.swift @@ -98,17 +98,17 @@ extension TaskListViewProtocol { let taskID = draftTaskID(for: placement) draftTaskTitle = "" draftTaskPlacement = placement - pendingFocus = .task(taskID) + fState.pendingFocus = .task(taskID) focusedField = .task(taskID) - selectedTaskID = taskID + fState.selectedTaskID = taskID } func beginDraftTaskEditing(_ placement: DraftTaskPlacement) { guard draftTaskPlacement == placement else { return } let taskID = draftTaskID(for: placement) - selectedTaskID = taskID - if case .task(let id) = pendingFocus, id == taskID { - pendingFocus = nil + fState.selectedTaskID = taskID + if case .task(let id) = fState.pendingFocus, id == taskID { + fState.pendingFocus = nil } } @@ -117,17 +117,17 @@ extension TaskListViewProtocol { let taskID = draftTaskID(for: placement) let title = draftTaskTitle.trimmingCharacters(in: .whitespacesAndNewlines) - // Clear pendingFocus before clearDraftTaskUI so that the iOS + // Clear fState.pendingFocus before clearDraftTaskUI so that the iOS // onChange(of: focusedFieldBinding) nil-redirect doesn't re-focus - // the draft row via a stale pendingFocus value. - if case .task(let id) = pendingFocus, id == taskID { - pendingFocus = nil + // the draft row via a stale fState.pendingFocus value. + if case .task(let id) = fState.pendingFocus, id == taskID { + fState.pendingFocus = nil } clearDraftTaskUI(at: placement, hasTitle: !title.isEmpty) - if selectedTaskID == taskID { - selectedTaskID = nil + if fState.selectedTaskID == taskID { + fState.selectedTaskID = nil } guard !title.isEmpty else { return } @@ -141,7 +141,7 @@ extension TaskListViewProtocol { } try store.save() if placement == .append { - selectedTaskID = task.id + fState.selectedTaskID = task.id } } catch { presentStoreError(error) @@ -158,7 +158,7 @@ extension TaskListViewProtocol { let sortOrder = try sortOrderAfter(taskID: afterTaskID) let newTask = try store.createTask(title: title, sortOrder: sortOrder) try store.save() - selectedTaskID = newTask.id + fState.selectedTaskID = newTask.id focusedField = .scrollView } catch { presentStoreError(error) @@ -191,12 +191,12 @@ extension TaskListViewProtocol { func handleBackgroundTap() { let isTaskFocused = if case .task = focusedField { true } else { false } - if isTaskFocused || selectedTaskID != nil { - pendingFocus = nil + if isTaskFocused || fState.selectedTaskID != nil { + fState.pendingFocus = nil if draftTaskPlacement != nil { commitDraftTask() } - selectedTaskID = nil + fState.selectedTaskID = nil focusedField = nil } else { revealDraftTask(at: .append) @@ -224,7 +224,7 @@ extension TaskListViewProtocol { } private func deleteIfEmpty(taskID: UUID) { - if case .task(let pendingTaskID) = pendingFocus, pendingTaskID == taskID { + if case .task(let pendingTaskID) = fState.pendingFocus, pendingTaskID == taskID { return } @@ -272,15 +272,15 @@ extension TaskListViewProtocol { } func selectTask(_ taskID: UUID) { - selectedTaskID = taskID + fState.selectedTaskID = taskID } func deleteTask(_ task: TaskItem) { let taskID = task.id do { try store.delete(taskID: taskID) - if selectedTaskID == taskID { - selectedTaskID = nil + if fState.selectedTaskID == taskID { + fState.selectedTaskID = nil } } catch { presentStoreError(error) @@ -304,8 +304,8 @@ extension TaskListViewProtocol { return .ignored } - guard let currentID = selectedTaskID else { - selectedTaskID = activeTasks.last?.id + guard let currentID = fState.selectedTaskID else { + fState.selectedTaskID = activeTasks.last?.id return .handled } @@ -315,7 +315,7 @@ extension TaskListViewProtocol { } if currentIndex > 0 { - selectedTaskID = displayOrder[currentIndex - 1].id + fState.selectedTaskID = displayOrder[currentIndex - 1].id } return .handled } @@ -325,8 +325,8 @@ extension TaskListViewProtocol { return .ignored } - guard let currentID = selectedTaskID else { - selectedTaskID = activeTasks.first?.id ?? completedTasks.first?.id + guard let currentID = fState.selectedTaskID else { + fState.selectedTaskID = activeTasks.first?.id ?? completedTasks.first?.id return .handled } @@ -336,14 +336,14 @@ extension TaskListViewProtocol { } if currentIndex < displayOrder.count - 1 { - selectedTaskID = displayOrder[currentIndex + 1].id + fState.selectedTaskID = displayOrder[currentIndex + 1].id } return .handled } func toggleSelectedTask() -> KeyPress.Result { guard focusedField == .scrollView else { return .ignored } - guard let currentID = selectedTaskID else { return .handled } + guard let currentID = fState.selectedTaskID else { return .handled } guard let task = allTasksInDisplayOrder.first(where: { $0.id == currentID }) else { return .handled } @@ -353,7 +353,7 @@ extension TaskListViewProtocol { func focusSelectedTask() -> KeyPress.Result { guard focusedField == .scrollView else { return .ignored } - guard let currentID = selectedTaskID else { return .handled } + guard let currentID = fState.selectedTaskID else { return .handled } guard let task = allTasksInDisplayOrder.first(where: { $0.id == currentID }) else { return .handled } @@ -366,7 +366,7 @@ extension TaskListViewProtocol { guard focusedField == .scrollView else { return .ignored } - guard let currentID = selectedTaskID else { + guard let currentID = fState.selectedTaskID else { return .handled } guard let task = allTasksInDisplayOrder.first(where: { $0.id == currentID }) else { @@ -378,7 +378,7 @@ extension TaskListViewProtocol { func moveSelectedTaskUp() { guard focusedField == .scrollView else { return } - guard let currentID = selectedTaskID else { return } + guard let currentID = fState.selectedTaskID else { return } guard let currentIndex = activeTasks.firstIndex(where: { $0.id == currentID }) else { return } guard currentIndex > 0 else { return } @@ -391,7 +391,7 @@ extension TaskListViewProtocol { func moveSelectedTaskDown() { guard focusedField == .scrollView else { return } - guard let currentID = selectedTaskID else { return } + guard let currentID = fState.selectedTaskID else { return } guard let currentIndex = activeTasks.firstIndex(where: { $0.id == currentID }) else { return } guard currentIndex < activeTasks.count - 1 else { return } @@ -404,7 +404,7 @@ extension TaskListViewProtocol { func markSelectedTaskCompleted() { guard focusedField == .scrollView else { return } - guard let currentID = selectedTaskID else { return } + guard let currentID = fState.selectedTaskID else { return } guard let task = allTasksInDisplayOrder.first(where: { $0.id == currentID }) else { return } toggleCompletion(task) } @@ -416,9 +416,9 @@ extension TaskListViewProtocol { } func startEditing(_ taskID: UUID) { - selectedTaskID = taskID + fState.selectedTaskID = taskID focusedField = .task(taskID) - pendingFocus = nil + fState.pendingFocus = nil } func endEditing(_ taskID: UUID, shouldCreateNewTask: Bool) { @@ -437,7 +437,7 @@ extension TaskListViewProtocol { let willBeDeleted = shouldDeleteIfEmpty(taskID: taskID) if willBeDeleted { - selectedTaskID = nil + fState.selectedTaskID = nil deleteIfEmpty(taskID: taskID) } else if wasLastActiveTask && shouldCreateNewTask { revealDraftTask(at: .append) diff --git a/Listless/Helpers/TaskListTypes.swift b/Listless/Helpers/TaskListTypes.swift @@ -15,5 +15,11 @@ enum DraftTaskPlacement: Equatable { case append } +struct FocusStateData { + var focusedField: FocusField? + var selectedTaskID: UUID? + var pendingFocus: FocusField? +} + let draftPrependRowID = UUID() let draftAppendRowID = UUID() diff --git a/Listless/Helpers/TaskListViewProtocol.swift b/Listless/Helpers/TaskListViewProtocol.swift @@ -8,8 +8,7 @@ protocol TaskListViewProtocol { var syncMonitor: CloudKitSyncMonitor { get } var managedObjectContext: NSManagedObjectContext { get } var focusedField: FocusField? { get nonmutating set } - var selectedTaskID: UUID? { get nonmutating set } - var pendingFocus: FocusField? { get nonmutating set } + var fState: FocusStateData { get nonmutating set } var dragState: DragState { get nonmutating set } var draftTaskPlacement: DraftTaskPlacement? { get nonmutating set } var draftTaskTitle: String { get nonmutating set } diff --git a/ListlessMac/Extensions/TaskListView+Toolbar.swift b/ListlessMac/Extensions/TaskListView+Toolbar.swift @@ -33,7 +33,7 @@ extension TaskListView { .help("Create a new item") Button { - if let currentID = selectedTaskID, + if let currentID = fState.selectedTaskID, let task = allTasksInDisplayOrder.first(where: { $0.id == currentID }) { deleteTask(task) @@ -41,7 +41,7 @@ extension TaskListView { } label: { Label("Delete", systemImage: "trash") } - .disabled(selectedTaskID == nil || focusedField != .scrollView) + .disabled(fState.selectedTaskID == nil || focusedField != .scrollView) .help("Delete selected item") Divider() diff --git a/ListlessMac/Views/TaskListView.swift b/ListlessMac/Views/TaskListView.swift @@ -2,12 +2,6 @@ import SwiftUI import UniformTypeIdentifiers struct TaskListView: View, TaskListViewProtocol { - struct FocusStateData { - var focusedField: FocusField? - var selectedTaskID: UUID? - var pendingFocus: FocusField? - } - struct InteractionStateData { var dragState: DragState = .idle var liftedTaskID: UUID? @@ -15,10 +9,6 @@ struct TaskListView: View, TaskListViewProtocol { var draftTaskTitle: String = "" } - struct TaskStateData { - var refreshID = UUID() - } - @Environment(\.undoManager) var undoManager @Environment(\.managedObjectContext) var managedObjectContext @@ -31,9 +21,8 @@ struct TaskListView: View, TaskListViewProtocol { ) var tasks: FetchedResults<TaskItem> @FocusState private var focusedFieldBinding: FocusField? - @State private var fState = FocusStateData() + @State var fState = FocusStateData() @State private var iState = InteractionStateData() - @State private var tState = TaskStateData() var focusedField: FocusField? { get { fState.focusedField } @@ -43,26 +32,11 @@ struct TaskListView: View, TaskListViewProtocol { } } - var selectedTaskID: UUID? { - get { fState.selectedTaskID } - nonmutating set { fState.selectedTaskID = newValue } - } - - var pendingFocus: FocusField? { - get { fState.pendingFocus } - nonmutating set { fState.pendingFocus = newValue } - } - var dragState: DragState { get { iState.dragState } nonmutating set { iState.dragState = newValue } } - var liftedTaskID: UUID? { - get { iState.liftedTaskID } - nonmutating set { iState.liftedTaskID = newValue } - } - var draftTaskPlacement: DraftTaskPlacement? { get { iState.draftTaskPlacement } nonmutating set { iState.draftTaskPlacement = newValue } @@ -73,30 +47,25 @@ struct TaskListView: View, TaskListViewProtocol { nonmutating set { iState.draftTaskTitle = newValue } } - var refreshID: UUID { - get { tState.refreshID } - nonmutating set { tState.refreshID = newValue } - } - var vStackSpacing: CGFloat { 0 } var isCompletelyEmpty: Bool { activeTasks.isEmpty && completedTasks.isEmpty } var selectedIndex: Int? { - guard let currentID = selectedTaskID else { return nil } + guard let currentID = fState.selectedTaskID else { return nil } return activeTasks.firstIndex(where: { $0.id == currentID }) } var canDeleteSelectionFromList: Bool { - selectedTaskID != nil && focusedField == .scrollView + fState.selectedTaskID != nil && focusedField == .scrollView } var canMarkSelectionCompleted: Bool { guard focusedField == .scrollView else { return false } - guard let currentID = selectedTaskID else { return false } + guard let currentID = fState.selectedTaskID else { return false } return allTasksInDisplayOrder.contains(where: { $0.id == currentID }) } var markCompletedMenuTitle: String { - completedTasks.contains(where: { $0.id == selectedTaskID }) + completedTasks.contains(where: { $0.id == fState.selectedTaskID }) ? "Mark as Incomplete" : "Mark as Complete" } @@ -122,7 +91,7 @@ struct TaskListView: View, TaskListViewProtocol { var menuCoordinatorTrigger: MenuState { MenuState( - selectedTaskID: selectedTaskID, + selectedTaskID: fState.selectedTaskID, isScrollViewFocused: focusedField == .scrollView, activeTaskCount: activeTasks.count, completedTaskCount: completedTasks.count, @@ -134,14 +103,14 @@ struct TaskListView: View, TaskListViewProtocol { let coord = menuCoordinator coord.newTask = { createNewTask(); focusedField = nil } coord.copySelectedTask = { - guard let taskID = selectedTaskID, + guard let taskID = fState.selectedTaskID, let task = tasks.first(where: { $0.id == taskID }) else { return } let pasteboard = NSPasteboard.general pasteboard.clearContents() pasteboard.setString(task.title, forType: .string) } coord.cutSelectedTask = { - guard let taskID = selectedTaskID, + guard let taskID = fState.selectedTaskID, let task = tasks.first(where: { $0.id == taskID }) else { return } let pasteboard = NSPasteboard.general pasteboard.clearContents() @@ -149,7 +118,7 @@ struct TaskListView: View, TaskListViewProtocol { deleteTask(task) } coord.pasteAfterSelectedTask = { - guard let taskID = selectedTaskID, + guard let taskID = fState.selectedTaskID, let string = NSPasteboard.general.string(forType: .string) else { return } createTask(title: string, afterTaskID: taskID) } @@ -159,8 +128,8 @@ struct TaskListView: View, TaskListViewProtocol { coord.markSelectedTaskCompleted = { markSelectedTaskCompleted() } coord.clearCompletedTasks = { clearCompletedTasks() } let inNavMode = focusedField == .scrollView - coord.canCopySelectedTask = selectedTaskID != nil && inNavMode - coord.canCutSelectedTask = selectedTaskID != nil && inNavMode + coord.canCopySelectedTask = fState.selectedTaskID != nil && inNavMode + coord.canCutSelectedTask = fState.selectedTaskID != nil && inNavMode coord.canPasteAfterSelectedTask = selectedIndex != nil && inNavMode coord.canDeleteSelectedTask = canDeleteSelectionFromList coord.canMoveSelectedTaskUp = canMoveSelectionUp @@ -177,7 +146,7 @@ struct TaskListView: View, TaskListViewProtocol { } func isRowLifted(_ taskID: UUID) -> Bool { - liftedTaskID == taskID || draggedTaskID == taskID + iState.liftedTaskID == taskID || draggedTaskID == taskID } func clearDraftTaskUI(at placement: DraftTaskPlacement, hasTitle _: Bool) { @@ -185,8 +154,8 @@ struct TaskListView: View, TaskListViewProtocol { draftTaskPlacement = nil } draftTaskTitle = "" - if selectedTaskID == draftTaskID(for: placement) { - selectedTaskID = nil + if fState.selectedTaskID == draftTaskID(for: placement) { + fState.selectedTaskID = nil } focusedField = nil } @@ -203,7 +172,7 @@ struct TaskListView: View, TaskListViewProtocol { taskID: taskID, index: index, totalTasks: displayActiveTasks.count, - isSelected: selectedTaskID == taskID, + isSelected: fState.selectedTaskID == taskID, focusedField: $focusedFieldBinding, onToggle: { toggleCompletion($0) }, onTitleChange: { updateTitle($0, $1) }, @@ -217,11 +186,11 @@ struct TaskListView: View, TaskListViewProtocol { isActive: !task.isCompleted, taskID: task.id, onDragStart: { - liftedTaskID = nil + iState.liftedTaskID = nil startDrag(taskID: task.id) }, - onLift: { liftedTaskID = task.id }, - onLiftEnd: { if liftedTaskID == task.id { liftedTaskID = nil } } + onLift: { iState.liftedTaskID = task.id }, + onLiftEnd: { if iState.liftedTaskID == task.id { iState.liftedTaskID = nil } } ) .scaleEffect(isRowLifted(taskID) ? 1.03 : 1.0) .shadow( @@ -279,7 +248,7 @@ struct TaskListView: View, TaskListViewProtocol { let accentColor = cachedTaskColor( forIndex: index, total: total ) - let isSelected = selectedTaskID == draftAppendRowID + let isSelected = fState.selectedTaskID == draftAppendRowID HStack(alignment: .firstTextBaseline, spacing: 12) { Image(systemName: "circle") .foregroundStyle(.primary) @@ -341,7 +310,7 @@ struct TaskListView: View, TaskListViewProtocol { TaskRowView( task: task, taskID: taskID, - isSelected: selectedTaskID == taskID, + isSelected: fState.selectedTaskID == taskID, focusedField: $focusedFieldBinding, onToggle: { toggleCompletion($0) }, onTitleChange: { updateTitle($0, $1) }, @@ -399,10 +368,10 @@ struct TaskListView: View, TaskListViewProtocol { handleFocusChange(from: oldValue, to: newValue) if newValue == nil { - if let pending = pendingFocus { + if let pending = fState.pendingFocus { focusedFieldBinding = pending fState.focusedField = pending - pendingFocus = nil + fState.pendingFocus = nil } else { focusedFieldBinding = .scrollView fState.focusedField = .scrollView diff --git a/ListlessiOS/Extensions/TaskListView+Drag.swift b/ListlessiOS/Extensions/TaskListView+Drag.swift @@ -5,7 +5,7 @@ extension TaskListView { guard let draggedID = draggedTaskID, var order = visualOrder, let currentIndex = order.firstIndex(of: draggedID), - let draggedFrame = rowFrames[draggedID] else { return } + let draggedFrame = iState.rowFrames[draggedID] else { return } let threshold = draggedFrame.height * 0.2 @@ -32,17 +32,17 @@ extension TaskListView { let order = visualOrder, let finalIndex = order.firstIndex(of: draggedID) else { clearDragState() - isDragging = false + iState.isDragging = false return } do { try store.moveTask(taskID: draggedID, toIndex: finalIndex) clearDragState() - isDragging = false + iState.isDragging = false } catch { withAnimation(.spring(response: 0.3, dampingFraction: 0.8)) { clearDragState() - isDragging = false + iState.isDragging = false } presentStoreError(error) } diff --git a/ListlessiOS/Extensions/TaskListView+NavigationHeader.swift b/ListlessiOS/Extensions/TaskListView+NavigationHeader.swift @@ -33,7 +33,7 @@ extension TaskListView { .padding(.bottom, 8) .contentShape(Rectangle()) .onTapGesture { - selectedTaskID = nil + fState.selectedTaskID = nil focusedField = nil } } diff --git a/ListlessiOS/Extensions/TaskListView+PullToClear.swift b/ListlessiOS/Extensions/TaskListView+PullToClear.swift @@ -2,8 +2,8 @@ import SwiftUI extension TaskListView { @ViewBuilder var pullToClearIndicatorRow: some View { - if pullUpOffset > 0 && !completedTasks.isEmpty { - PullToClearIndicator(pullOffset: pullUpOffset) + if iState.pullUpOffset > 0 && !completedTasks.isEmpty { + PullToClearIndicator(pullOffset: iState.pullUpOffset) } } } diff --git a/ListlessiOS/Extensions/TaskListView+Undo.swift b/ListlessiOS/Extensions/TaskListView+Undo.swift @@ -11,7 +11,7 @@ extension TaskListView { guard focusedField == .scrollView else { return .ignored } - guard let currentID = selectedTaskID else { + guard let currentID = fState.selectedTaskID else { return .handled } guard let task = allTasksInDisplayOrder.first(where: { $0.id == currentID }) else { @@ -40,7 +40,7 @@ extension TaskListView { func showUndoToast(message: String) { withAnimation { - undoToast = UndoToastData(id: UUID(), message: message) + iState.undoToast = UndoToastData(id: UUID(), message: message) } } @@ -56,7 +56,7 @@ extension TaskListView { func dismissUndoToast() { withAnimation { - undoToast = nil + iState.undoToast = nil } } } diff --git a/ListlessiOS/Views/TaskListView.swift b/ListlessiOS/Views/TaskListView.swift @@ -2,12 +2,6 @@ import SwiftUI import UIKit struct TaskListView: View, TaskListViewProtocol { - struct FocusStateData { - var focusedField: FocusField? - var selectedTaskID: UUID? - var pendingFocus: FocusField? - } - struct InteractionStateData { var dragState: DragState = .idle var pullToCreate = PullToCreateState() @@ -22,10 +16,6 @@ struct TaskListView: View, TaskListViewProtocol { var draftTaskTitle: String = "" } - struct TaskStateData { - var refreshID = UUID() - } - @AppStorage("headingText") var headingText = "Items" @Environment(\.undoManager) var undoManager @Environment(\.managedObjectContext) var managedObjectContext @@ -38,9 +28,8 @@ struct TaskListView: View, TaskListViewProtocol { ) var tasks: FetchedResults<TaskItem> @FocusState private var focusedFieldBinding: FocusField? - @State private var fState = FocusStateData() - @State private var iState = InteractionStateData() - @State private var tState = TaskStateData() + @State var fState = FocusStateData() + @State var iState = InteractionStateData() var focusedField: FocusField? { get { fState.focusedField } @@ -50,51 +39,11 @@ struct TaskListView: View, TaskListViewProtocol { } } - var selectedTaskID: UUID? { - get { fState.selectedTaskID } - nonmutating set { fState.selectedTaskID = newValue } - } - - var pendingFocus: FocusField? { - get { fState.pendingFocus } - nonmutating set { fState.pendingFocus = newValue } - } - var dragState: DragState { get { iState.dragState } nonmutating set { iState.dragState = newValue } } - var pullToCreate: PullToCreateState { - get { iState.pullToCreate } - nonmutating set { iState.pullToCreate = newValue } - } - - var pullUpOffset: CGFloat { - get { iState.pullUpOffset } - nonmutating set { iState.pullUpOffset = newValue } - } - - var isDragging: Bool { - get { iState.isDragging } - nonmutating set { iState.isDragging = newValue } - } - - var rowFrames: [UUID: CGRect] { - get { iState.rowFrames } - nonmutating set { iState.rowFrames = newValue } - } - - var refreshID: UUID { - get { tState.refreshID } - nonmutating set { tState.refreshID = newValue } - } - - var undoToast: UndoToastData? { - get { iState.undoToast } - nonmutating set { iState.undoToast = newValue } - } - var draftTaskPlacement: DraftTaskPlacement? { get { iState.draftTaskPlacement } nonmutating set { iState.draftTaskPlacement = newValue } @@ -149,7 +98,7 @@ struct TaskListView: View, TaskListViewProtocol { } private var selectedIndex: Int? { - guard let currentID = selectedTaskID else { return nil } + guard let currentID = fState.selectedTaskID else { return nil } return activeTasks.firstIndex(where: { $0.id == currentID }) } @@ -175,7 +124,7 @@ struct TaskListView: View, TaskListViewProtocol { private var menuCoordinatorTrigger: MenuState { MenuState( - selectedTaskID: selectedTaskID, + selectedTaskID: fState.selectedTaskID, isScrollViewFocused: focusedField == .scrollView, activeTaskCount: activeTasks.count, completedTaskCount: completedTasks.count, @@ -191,11 +140,11 @@ struct TaskListView: View, TaskListViewProtocol { coord.moveDown = { moveSelectedTaskDown() } coord.markCompleted = { markSelectedTaskCompleted() } let inNavMode = focusedField == .scrollView - coord.canDelete = selectedTaskID != nil && inNavMode + coord.canDelete = fState.selectedTaskID != nil && inNavMode coord.canMoveUp = canMoveSelectionUp coord.canMoveDown = canMoveSelectionDown - coord.canMarkCompleted = selectedTaskID != nil && inNavMode - coord.markCompletedTitle = completedTasks.contains(where: { $0.id == selectedTaskID }) + coord.canMarkCompleted = fState.selectedTaskID != nil && inNavMode + coord.markCompletedTitle = completedTasks.contains(where: { $0.id == fState.selectedTaskID }) ? "Mark as Incomplete" : "Mark as Complete" } @@ -215,16 +164,16 @@ struct TaskListView: View, TaskListViewProtocol { draftTaskPlacement = nil } draftTaskTitle = "" - if selectedTaskID == draftTaskID(for: placement) { - selectedTaskID = nil + if fState.selectedTaskID == draftTaskID(for: placement) { + fState.selectedTaskID = nil } guard placement == .prepend else { return } - var state = pullToCreate + var state = iState.pullToCreate state.isInsertionPending = false state.indicatorOffset = 0 - pullToCreate = state + iState.pullToCreate = state } if placement == .prepend, !hasTitle { @@ -247,7 +196,7 @@ struct TaskListView: View, TaskListViewProtocol { } func didStartDrag() { - isDragging = true + iState.isDragging = true let generator = UIImpactFeedbackGenerator(style: .medium) generator.impactOccurred() } @@ -262,20 +211,20 @@ struct TaskListView: View, TaskListViewProtocol { private func dragScaleEffect(for taskID: UUID) -> CGFloat { let liftPoints: CGFloat = 20 - guard let width = rowFrames[taskID]?.width, width > 0 else { return 1.05 } + guard let width = iState.rowFrames[taskID]?.width, width > 0 else { return 1.05 } return (width + liftPoints) / width } private var pullToCreateRevealHeight: CGFloat { min( - pullToCreate.indicatorDisplayOffset(threshold: pullCreateThreshold), + iState.pullToCreate.indicatorDisplayOffset(threshold: pullCreateThreshold), PullToCreateIndicator.indicatorHeight ) } private var pullToCreateGap: CGFloat { - guard pullToCreate.shouldShowIndicator, !isPrependDraftVisible else { return 0 } - let exposedPull = pullToCreate.indicatorDisplayOffset(threshold: pullCreateThreshold) + guard iState.pullToCreate.shouldShowIndicator, !isPrependDraftVisible else { return 0 } + let exposedPull = iState.pullToCreate.indicatorDisplayOffset(threshold: pullCreateThreshold) return min( vStackSpacing, max(0, exposedPull - PullToCreateIndicator.indicatorHeight) @@ -283,7 +232,7 @@ struct TaskListView: View, TaskListViewProtocol { } private var pullToCreateRowOverlap: CGFloat { - guard pullToCreate.shouldShowIndicator, !isPrependDraftVisible else { + guard iState.pullToCreate.shouldShowIndicator, !isPrependDraftVisible else { return 0 } return PullToCreateIndicator.indicatorHeight - pullToCreateRevealHeight @@ -293,12 +242,12 @@ struct TaskListView: View, TaskListViewProtocol { /// The phantom's UITextView is created while the indicator is visible /// (during the pull), so it's ready when the user releases. @ViewBuilder var pullToCreateIndicatorRow: some View { - let showIndicator = pullToCreate.shouldShowIndicator + let showIndicator = iState.pullToCreate.shouldShowIndicator let showPhantom = isPrependDraftVisible if showIndicator || showPhantom { ZStack(alignment: .topLeading) { PullToCreateIndicator( - pullOffset: pullToCreate.indicatorDisplayOffset( + pullOffset: iState.pullToCreate.indicatorDisplayOffset( threshold: pullCreateThreshold ), threshold: pullCreateThreshold, @@ -326,7 +275,7 @@ struct TaskListView: View, TaskListViewProtocol { let accentColor = taskColor( forIndex: 0, total: max(1, displayActiveTasks.count + 1) ) - let isSelected = selectedTaskID == draftPrependRowID + let isSelected = fState.selectedTaskID == draftPrependRowID HStack(alignment: .center, spacing: TaskRowMetrics.contentSpacing) { Image(systemName: "circle") .frame(width: 22, height: 22) @@ -388,7 +337,7 @@ struct TaskListView: View, TaskListViewProtocol { let total = max(1, displayActiveTasks.count + 1) let index = displayActiveTasks.count let accentColor = taskColor(forIndex: index, total: total) - let isSelected = selectedTaskID == draftAppendRowID + let isSelected = fState.selectedTaskID == draftAppendRowID HStack(alignment: .center, spacing: TaskRowMetrics.contentSpacing) { Image(systemName: "circle") .frame(width: 22, height: 22) @@ -459,7 +408,7 @@ struct TaskListView: View, TaskListViewProtocol { taskID: taskID, index: index, totalTasks: displayActiveTasks.count, - isSelected: selectedTaskID == taskID, + isSelected: fState.selectedTaskID == taskID, isDragging: isDraggingStateBinding, isLastActiveTask: index == displayActiveTasks.count - 1, focusedField: $focusedFieldBinding, @@ -469,8 +418,8 @@ struct TaskListView: View, TaskListViewProtocol { onSelect: { selectTask($0) }, onStartEdit: { startEditing($0) }, onEndEdit: { - if selectedTaskID == $0 { - selectedTaskID = nil + if fState.selectedTaskID == $0 { + fState.selectedTaskID = nil } endEditing($0, shouldCreateNewTask: $1) } @@ -491,7 +440,7 @@ struct TaskListView: View, TaskListViewProtocol { .onGeometryChange(for: CGRect.self) { proxy in proxy.frame(in: .global) } action: { frame in - rowFrames[taskID] = frame + iState.rowFrames[taskID] = frame } .id(taskID) } @@ -504,7 +453,7 @@ struct TaskListView: View, TaskListViewProtocol { TaskRowView( task: task, taskID: taskID, - isSelected: selectedTaskID == taskID, + isSelected: fState.selectedTaskID == taskID, focusedField: $focusedFieldBinding, onToggle: { toggleCompletion($0) }, onTitleChange: { updateTitle($0, $1) }, @@ -596,7 +545,7 @@ struct TaskListView: View, TaskListViewProtocol { } .padding( .bottom, - (pullToCreate.shouldShowIndicator && !isPrependDraftVisible) + (iState.pullToCreate.shouldShowIndicator && !isPrependDraftVisible) ? (pullToCreateGap - vStackSpacing) : 0 ) taskRows @@ -605,7 +554,7 @@ struct TaskListView: View, TaskListViewProtocol { .frame(maxWidth: .infinity, alignment: .topLeading) .padding(.trailing, 16) .padding(.vertical, 12) - .offset(y: -pullToCreate.pullOffset) + .offset(y: -iState.pullToCreate.pullOffset) .onChange(of: focusedFieldBinding) { oldValue, newValue in fState.focusedField = newValue handleFocusChange(from: oldValue, to: newValue) @@ -614,10 +563,10 @@ struct TaskListView: View, TaskListViewProtocol { !iState.isShowingSettings, !iState.isShowingSyncDiagnostics { - if let pending = pendingFocus { + if let pending = fState.pendingFocus { focusedFieldBinding = pending fState.focusedField = pending - pendingFocus = nil + fState.pendingFocus = nil } else { focusedFieldBinding = .scrollView fState.focusedField = .scrollView