listless

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

commit e82df68743c94eb5bd70cf004b9d1f900233e89c
parent ebe07dff1a78256cf65cd8099458bfb6661f8fa4
Author: Michael Camilleri <[email protected]>
Date:   Thu, 19 Feb 2026 05:18:39 +0900

Use enum-based drag state

Co-Authored-By: Codex GPT 5.3 <[email protected]>

Diffstat:
MListless/Extensions/TaskListView+Logic.swift | 54++++++++++++++++++++++++++++++++++++------------------
MListlessMac/Views/TaskListView.swift | 8++++++--
MListlessiOS/Extensions/TaskListView+Drag.swift | 10++++------
MListlessiOS/Views/TaskListView.swift | 8++++++--
4 files changed, 52 insertions(+), 28 deletions(-)

diff --git a/Listless/Extensions/TaskListView+Logic.swift b/Listless/Extensions/TaskListView+Logic.swift @@ -10,7 +10,7 @@ extension TaskListView { } var displayActiveTasks: [TaskItem] { - guard let visualOrder = visualOrder else { + guard let visualOrder else { return activeTasks } @@ -35,6 +35,20 @@ extension TaskListView { return nil } + var draggedTaskID: UUID? { + if case .dragging(let id, _) = dragState { + return id + } + return nil + } + + var visualOrder: [UUID]? { + if case .dragging(_, let order) = dragState { + return order + } + return nil + } + func presentStoreError(_ error: Error) { syncMonitor.ingest(error: error) } @@ -47,8 +61,7 @@ extension TaskListView { // MARK: - Task Creation func createNewTaskAtTop() -> UUID { - draggedTaskID = nil - visualOrder = nil + clearDragState() do { let task = try store.createTask(title: "", atBeginning: true) pendingFocus = .task(task.id) @@ -62,8 +75,7 @@ extension TaskListView { } func createNewTask() { - draggedTaskID = nil - visualOrder = nil + clearDragState() do { let task = try store.createTask(title: "") pendingFocus = .task(task.id) @@ -332,9 +344,8 @@ extension TaskListView { // MARK: - Drag and Drop func startDrag(taskID: UUID) { - guard draggedTaskID == nil else { return } - draggedTaskID = taskID - visualOrder = activeTasks.map(\.id) + guard case .idle = dragState else { return } + dragState = .dragging(id: taskID, order: activeTasks.map(\.id)) didStartDrag() } @@ -348,9 +359,9 @@ extension TaskListView { newOrder.insert(draggedID, at: targetIndex) } - if newOrder != visualOrder { + if newOrder != order { withAnimation(.spring(response: 0.3, dampingFraction: 0.8)) { - visualOrder = newOrder + setDragOrder(newOrder) } } } @@ -365,9 +376,9 @@ extension TaskListView { newOrder.insert(draggedID, at: targetIndex + 1) } - if newOrder != visualOrder { + if newOrder != order { withAnimation(.spring(response: 0.3, dampingFraction: 0.8)) { - visualOrder = newOrder + setDragOrder(newOrder) } } } @@ -396,9 +407,9 @@ extension TaskListView { var newOrder = order.filter { $0 != draggedID } newOrder.append(draggedID) - if newOrder != visualOrder { + if newOrder != order { withAnimation(.spring(response: 0.3, dampingFraction: 0.8)) { - visualOrder = newOrder + setDragOrder(newOrder) } } } @@ -409,8 +420,7 @@ extension TaskListView { let order = visualOrder, let finalIndex = order.firstIndex(of: droppedUUID) else { - draggedTaskID = nil - visualOrder = nil + clearDragState() return false } @@ -419,9 +429,17 @@ extension TaskListView { } catch { presentStoreError(error) } - draggedTaskID = nil - visualOrder = nil + clearDragState() return true } + + func setDragOrder(_ order: [UUID]) { + guard case .dragging(let id, _) = dragState else { return } + dragState = .dragging(id: id, order: order) + } + + func clearDragState() { + dragState = .idle + } } diff --git a/ListlessMac/Views/TaskListView.swift b/ListlessMac/Views/TaskListView.swift @@ -6,6 +6,11 @@ struct TaskListView: View { case scrollView } + enum DragState: Equatable { + case idle + case dragging(id: UUID, order: [UUID]) + } + @Environment(\.undoManager) var undoManager @Environment(\.managedObjectContext) var managedObjectContext @@ -22,8 +27,7 @@ struct TaskListView: View { @FocusState var focusedField: FocusField? @State var selectedTaskID: UUID? @State private var refreshID = UUID() - @State var draggedTaskID: UUID? - @State var visualOrder: [UUID]? + @State var dragState: DragState = .idle @State var pendingFocus: FocusField? @State var pullOffset: CGFloat = 0 diff --git a/ListlessiOS/Extensions/TaskListView+Drag.swift b/ListlessiOS/Extensions/TaskListView+Drag.swift @@ -13,7 +13,7 @@ extension TaskListView { if currentIndex < order.count - 1 && point.y > draggedFrame.maxY + threshold { order.swapAt(currentIndex, currentIndex + 1) withAnimation(.spring(response: 0.3, dampingFraction: 0.8)) { - visualOrder = order + setDragOrder(order) } return } @@ -22,7 +22,7 @@ extension TaskListView { if currentIndex > 0 && point.y < draggedFrame.minY - threshold { order.swapAt(currentIndex, currentIndex - 1) withAnimation(.spring(response: 0.3, dampingFraction: 0.8)) { - visualOrder = order + setDragOrder(order) } } } @@ -31,8 +31,7 @@ extension TaskListView { guard let draggedID = draggedTaskID, let order = visualOrder, let finalIndex = order.firstIndex(of: draggedID) else { - draggedTaskID = nil - visualOrder = nil + clearDragState() isDragging = false return } @@ -41,8 +40,7 @@ extension TaskListView { } catch { presentStoreError(error) } - draggedTaskID = nil - visualOrder = nil + clearDragState() isDragging = false } } diff --git a/ListlessiOS/Views/TaskListView.swift b/ListlessiOS/Views/TaskListView.swift @@ -6,6 +6,11 @@ struct TaskListView: View { case scrollView } + enum DragState: Equatable { + case idle + case dragging(id: UUID, order: [UUID]) + } + @Environment(\.undoManager) var undoManager @Environment(\.managedObjectContext) var managedObjectContext @@ -22,8 +27,7 @@ struct TaskListView: View { @FocusState var focusedField: FocusField? @State var selectedTaskID: UUID? @State private var refreshID = UUID() - @State var draggedTaskID: UUID? - @State var visualOrder: [UUID]? + @State var dragState: DragState = .idle @State var pendingFocus: FocusField? @State var pullToCreate = PullToCreateState() @State var pullUpOffset: CGFloat = 0