From d95c629845522e6dca176dd64c9fd12126b26967 Mon Sep 17 00:00:00 2001 From: Yusuf Suleman Date: Fri, 3 Apr 2026 11:12:09 -0500 Subject: [PATCH] feat: swipe left to delete food entries + tap for detail - Custom SwipeToDeleteRow with drag gesture - Swipe left reveals red trash button - Tap still opens EntryDetailView with delete option - Both paths call the same onDelete callback Co-Authored-By: Claude Opus 4.6 (1M context) --- .../Fitness/Views/MealSectionView.swift | 69 ++++++++++++++++--- 1 file changed, 60 insertions(+), 9 deletions(-) diff --git a/ios/Platform/Platform/Features/Fitness/Views/MealSectionView.swift b/ios/Platform/Platform/Features/Fitness/Views/MealSectionView.swift index 6c3f226..a66a470 100644 --- a/ios/Platform/Platform/Features/Fitness/Views/MealSectionView.swift +++ b/ios/Platform/Platform/Features/Fitness/Views/MealSectionView.swift @@ -68,16 +68,11 @@ struct MealSectionView: View { if isExpanded { ForEach(entries) { entry in - NavigationLink(destination: EntryDetailView(entry: entry, onDelete: { - onDelete(entry) - })) { - entryRow(entry) - } - .swipeActions(edge: .trailing, allowsFullSwipe: true) { - Button(role: .destructive) { + SwipeToDeleteRow(onDelete: { onDelete(entry) }) { + NavigationLink(destination: EntryDetailView(entry: entry, onDelete: { onDelete(entry) - } label: { - Label("Delete", systemImage: "trash") + })) { + entryRow(entry) } } } @@ -120,3 +115,59 @@ struct MealSectionView: View { .background(Color.surfaceCard) } } + +// MARK: - Swipe to Delete Row + +struct SwipeToDeleteRow: View { + let onDelete: () -> Void + @ViewBuilder let content: () -> Content + @State private var offset: CGFloat = 0 + @State private var showDelete = false + + var body: some View { + ZStack(alignment: .trailing) { + // Delete background + HStack { + Spacer() + Button { + withAnimation(.easeInOut(duration: 0.2)) { + onDelete() + offset = 0 + } + } label: { + Image(systemName: "trash.fill") + .foregroundStyle(.white) + .frame(width: 70, height: .infinity) + } + .frame(width: 70) + .background(Color.red) + } + + // Content + content() + .offset(x: offset) + .gesture( + DragGesture() + .onChanged { value in + if value.translation.width < 0 { + offset = max(value.translation.width, -80) + } else if showDelete { + offset = min(value.translation.width - 70, 0) + } + } + .onEnded { value in + withAnimation(.easeOut(duration: 0.2)) { + if value.translation.width < -40 { + offset = -70 + showDelete = true + } else { + offset = 0 + showDelete = false + } + } + } + ) + } + .clipShape(Rectangle()) + } +}