← All repositories

listless

A simple list app for Apple platforms

git clone https://code.inqk.net/listless.git


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