listless

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

commit dc334b1feb015162fa79bcf4ed4385334eefeb78
parent b113eac1740c48ee5bb0eaed2176ebee67ae180b
Author: Michael Camilleri <[email protected]>
Date:   Wed, 25 Feb 2026 21:26:04 +0900

Improve failure case when dragging fails

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

Diffstat:
MListless/Extensions/TaskListView+Logic.swift | 20++++++++++++++++----
MListless/Models/TaskStore.swift | 11+++++++++++
MListlessiOS/Extensions/TaskListView+Drag.swift | 8++++++--
3 files changed, 33 insertions(+), 6 deletions(-)

diff --git a/Listless/Extensions/TaskListView+Logic.swift b/Listless/Extensions/TaskListView+Logic.swift @@ -89,7 +89,8 @@ extension TaskListView { func createTask(title: String, afterTaskID: UUID) { clearDragState() do { - let newTask = try store.createTask(title: title, sortOrder: sortOrderAfter(taskID: afterTaskID)) + let sortOrder = try sortOrderAfter(taskID: afterTaskID) + let newTask = try store.createTask(title: title, sortOrder: sortOrder) selectedTaskID = newTask.id focusedField = .scrollView } catch { @@ -97,14 +98,22 @@ extension TaskListView { } } - private func sortOrderAfter(taskID: UUID) -> Int64? { + private func sortOrderAfter(taskID: UUID) throws -> Int64? { guard let afterIndex = activeTasks.firstIndex(where: { $0.id == taskID }) else { return nil } let afterTask = activeTasks[afterIndex] if afterIndex + 1 < activeTasks.count { let nextTask = activeTasks[afterIndex + 1] - return (afterTask.sortOrder + nextTask.sortOrder) / 2 + let midpoint = (afterTask.sortOrder + nextTask.sortOrder) / 2 + if midpoint == afterTask.sortOrder { + // Consecutive sort orders leave no room; re-normalise with 1000-unit gaps + // then recompute. Core Data's identity map ensures afterTask/nextTask reflect + // the updated values immediately after normalisation. + try store.normalizeSortOrders() + return (afterTask.sortOrder + nextTask.sortOrder) / 2 + } + return midpoint } else { return afterTask.sortOrder + 1000 } @@ -449,10 +458,13 @@ extension TaskListView { do { try store.moveTask(taskID: droppedUUID, toIndex: finalIndex) + clearDragState() } catch { + withAnimation(.spring(response: 0.3, dampingFraction: 0.8)) { + clearDragState() + } presentStoreError(error) } - clearDragState() return true } diff --git a/Listless/Models/TaskStore.swift b/Listless/Models/TaskStore.swift @@ -111,6 +111,17 @@ final class TaskStore { try save() } + func normalizeSortOrders() throws { + let activeTasks = try fetchTasks().filter { !$0.isCompleted } + .sorted { $0.sortOrder < $1.sortOrder } + + for (index, task) in activeTasks.enumerated() { + task.sortOrder = Int64(index) * 1000 + } + + try save() + } + func moveTask(taskID: UUID, toIndex: Int) throws { let activeTasks = try fetchTasks().filter { !$0.isCompleted } .sorted { $0.sortOrder < $1.sortOrder } diff --git a/ListlessiOS/Extensions/TaskListView+Drag.swift b/ListlessiOS/Extensions/TaskListView+Drag.swift @@ -37,10 +37,14 @@ extension TaskListView { } do { try store.moveTask(taskID: draggedID, toIndex: finalIndex) + clearDragState() + isDragging = false } catch { + withAnimation(.spring(response: 0.3, dampingFraction: 0.8)) { + clearDragState() + isDragging = false + } presentStoreError(error) } - clearDragState() - isDragging = false } }