listless

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

commit cfeed7a5f1aa2c64e5b8382009d5ab76eb3c9f6f
parent 073bb63d24395b47ba5e4230506ef4c7fb766c8e
Author: Michael Camilleri <[email protected]>
Date:   Wed,  1 Apr 2026 15:49:59 +0900

Implement more robust reset after undoing

Prior to this commit, it is possible to trigger an animation bug where a
swipe-to-delete animation is 'preserved' after a user presses undo. This
change addresses this using a state value that all rows observe (which
is a little inelegant but has a simpler implementation).

Co-Authored-By: Claude 4.6 Opus <[email protected]>

Diffstat:
MListlessiOS/Extensions/ItemListView+Undo.swift | 1+
MListlessiOS/Views/ItemListView.swift | 1+
MListlessiOS/Views/ItemRowView.swift | 15++++++++++++---
3 files changed, 14 insertions(+), 3 deletions(-)

diff --git a/ListlessiOS/Extensions/ItemListView+Undo.swift b/ListlessiOS/Extensions/ItemListView+Undo.swift @@ -68,6 +68,7 @@ extension ItemListView { } catch { presentStoreError(error) } + iState.undoGeneration &+= 1 dismissUndoToast() } diff --git a/ListlessiOS/Views/ItemListView.swift b/ListlessiOS/Views/ItemListView.swift @@ -23,6 +23,7 @@ struct ItemListView: View, ItemListViewProtocol { var draftPlacement: DraftItemPlacement? var draftTitle: String = "" var fetchWorkaround: Int = 0 + var undoGeneration: Int = 0 var isShowingOverlay: Bool { isShowingSettings || isShowingSyncDiagnostics || isShowingRenameAlert diff --git a/ListlessiOS/Views/ItemRowView.swift b/ListlessiOS/Views/ItemRowView.swift @@ -15,6 +15,7 @@ struct ItemRowView: View { let isLastActiveItem: Bool let onStartEdit: (UUID) -> Void let onEndEdit: (UUID, _ shouldCreateNewItem: Bool) -> Void + let undoGeneration: Int @FocusState.Binding var focusedField: FocusField? @AppStorage("colorTheme") private var colorThemeRaw = 0 @@ -36,6 +37,7 @@ struct ItemRowView: View { isDragging: Binding<Bool> = .constant(false), isSwiping: Binding<Bool> = .constant(false), isLastActiveItem: Bool = false, + undoGeneration: Int = 0, focusedField: FocusState<FocusField?>.Binding, onToggle: @escaping (UUID) -> Void, onTitleChange: @escaping (UUID, String) -> Void, @@ -52,6 +54,7 @@ struct ItemRowView: View { _isDragging = isDragging _isSwiping = isSwiping self.isLastActiveItem = isLastActiveItem + self.undoGeneration = undoGeneration self.onToggle = onToggle self.onTitleChange = onTitleChange self.onDelete = onDelete @@ -158,9 +161,6 @@ struct ItemRowView: View { .onAppear { editingTitle = item.title cachedAccentColor = computeAccentColor() - swipeOffset = 0 - swipeDirection = .none - isSwipeTriggered = false } .onChange(of: item.title) { _, newValue in if !isCurrentlyEditing { @@ -176,6 +176,15 @@ struct ItemRowView: View { .onChange(of: colorThemeRaw) { _, _ in cachedAccentColor = computeAccentColor() } + .onChange(of: undoGeneration) { _, _ in + var transaction = Transaction(animation: nil) + transaction.disablesAnimations = true + withTransaction(transaction) { + swipeOffset = 0 + swipeDirection = .none + isSwipeTriggered = false + } + } .itemSwipeGesture( isDragging: $isDragging, isEditing: focusedField == .item(itemID),