listless

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

commit 169d0e78f11c809764421878723a3b5380e522de
parent e542c7f27df71ce7393f12739caf6f5b050a7563
Author: Michael Camilleri <[email protected]>
Date:   Mon, 23 Mar 2026 16:04:16 +0900

Fix pull-to-create animation

A previous commit attempted to make the pull-to-create animation
smoother by fixing the structure of the view hierarchy. After using the
app on my phone, it became evident that this new structure was broken.
When submitting a new item from a draft row located at the top of the
list, the new list would jump down. This commit restructures the view
hierarchy in a way that eliminates this bug.

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

Diffstat:
MListlessiOS/Views/TaskListView.swift | 42+++++++++++++++++-------------------------
1 file changed, 17 insertions(+), 25 deletions(-)

diff --git a/ListlessiOS/Views/TaskListView.swift b/ListlessiOS/Views/TaskListView.swift @@ -239,35 +239,23 @@ 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 showPhantom = isPrependDraftVisible let pullOffset = pState.pullToCreate.pullOffset let indicatorHeight = PullToCreateIndicator.indicatorHeight - ZStack(alignment: .topLeading) { - PullToCreateIndicator( - pullOffset: max( - 0, - pState.pullToCreate.indicatorDisplayOffset( - threshold: pullCreateThreshold - ) - vStackSpacing - ), - threshold: max(0, pullCreateThreshold - vStackSpacing), - hasRowsBelow: false - ) - .padding(.bottom, -indicatorHeight) - .opacity(showPhantom ? 0 : 1) - - draftPrependRow - .frame(height: showPhantom ? nil : 0) - .opacity(showPhantom ? 1 : 0) - // Instant swap — no animation on height or opacity. - .animation(nil, value: showPhantom) - } + PullToCreateIndicator( + pullOffset: max( + 0, + pState.pullToCreate.indicatorDisplayOffset( + threshold: pullCreateThreshold + ) - vStackSpacing + ), + threshold: max(0, pullCreateThreshold - vStackSpacing), + hasRowsBelow: false + ) + .padding(.bottom, -indicatorHeight) + .opacity(isPrependDraftVisible ? 0 : 1) .offset( - y: showPhantom - ? pState.draftSettleOffset - : vStackSpacing - min(pullOffset, indicatorHeight + vStackSpacing) + y: vStackSpacing - min(pullOffset, indicatorHeight + vStackSpacing) ) - .animation(nil, value: showPhantom) } /// The draft row content styled to match a task row. Controlled by the @@ -477,6 +465,10 @@ struct TaskListView: View, TaskListViewProtocol { Color.clear.frame(height: pState.headerHeight) pullToCreateIndicatorRow } + if isPrependDraftVisible { + draftPrependRow + .offset(y: pState.draftSettleOffset) + } taskRows } .offset(