feat: swipe left to delete food entries + tap for detail
All checks were successful
Security Checks / secret-scanning (push) Successful in 3s
Security Checks / dependency-audit (push) Successful in 13s
Security Checks / dockerfile-lint (push) Successful in 4s

- 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) <noreply@anthropic.com>
This commit is contained in:
Yusuf Suleman
2026-04-03 11:12:09 -05:00
parent 5320083874
commit d95c629845

View File

@@ -68,17 +68,12 @@ struct MealSectionView: View {
if isExpanded {
ForEach(entries) { entry in
SwipeToDeleteRow(onDelete: { onDelete(entry) }) {
NavigationLink(destination: EntryDetailView(entry: entry, onDelete: {
onDelete(entry)
})) {
entryRow(entry)
}
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
Button(role: .destructive) {
onDelete(entry)
} label: {
Label("Delete", systemImage: "trash")
}
}
}
}
@@ -120,3 +115,59 @@ struct MealSectionView: View {
.background(Color.surfaceCard)
}
}
// MARK: - Swipe to Delete Row
struct SwipeToDeleteRow<Content: View>: 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())
}
}