From ebe7e0ea41215132eb71fc2b0143d8e9df3e0868 Mon Sep 17 00:00:00 2001 From: Advay Monga Date: Sun, 20 Sep 2026 00:37:44 -0400 Subject: [PATCH 1/6] Fix food card taps swallowed by the press effect PressEffect used DragGesture(minimumDistance: 0), which recognizes on touch-down and beat the card's onTapGesture, so tapping a food no longer opened its page. Track the press with a long press that never completes instead; @GestureState releases it on lift or scroll. Co-Authored-By: Claude Opus 5 --- umd-dining-app/Utilities/UMDColors.swift | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/umd-dining-app/Utilities/UMDColors.swift b/umd-dining-app/Utilities/UMDColors.swift index 6d9b953..80e834c 100644 --- a/umd-dining-app/Utilities/UMDColors.swift +++ b/umd-dining-app/Utilities/UMDColors.swift @@ -59,18 +59,19 @@ struct PressableButtonStyle: ButtonStyle { } } -// Same dip for views driven by tap gestures rather than a Button +// Same dip for views driven by tap gestures rather than a Button. +// A long press that never completes tracks touch-down without eating taps; +// @GestureState releases it when the finger lifts or the view scrolls away. struct PressEffect: ViewModifier { - @State private var isPressed = false + @GestureState private var isPressed = false func body(content: Content) -> some View { content .scaleEffect(isPressed ? 0.97 : 1) .animation(.easeOut(duration: 0.12), value: isPressed) .simultaneousGesture( - DragGesture(minimumDistance: 0) - .onChanged { _ in isPressed = true } - .onEnded { _ in isPressed = false } + LongPressGesture(minimumDuration: .infinity, maximumDistance: 20) + .updating($isPressed) { _, state, _ in state = true } ) } } From 367d28750cfd324df5f8523661509f2b41629073 Mon Sep 17 00:00:00 2001 From: Advay Monga Date: Sun, 20 Sep 2026 00:43:06 -0400 Subject: [PATCH 2/6] Dip food cards on tap instead of touch-down The touch-down gesture fought the scroll view and the card's own tap gestures. Drop PressEffect entirely and animate from the tap handler, which leaves scrolling and both tap gestures untouched. Co-Authored-By: Claude Opus 5 --- umd-dining-app/Utilities/UMDColors.swift | 23 ----------------------- umd-dining-app/Views/FoodItemRow.swift | 13 +++++++++++-- 2 files changed, 11 insertions(+), 25 deletions(-) diff --git a/umd-dining-app/Utilities/UMDColors.swift b/umd-dining-app/Utilities/UMDColors.swift index 80e834c..395d381 100644 --- a/umd-dining-app/Utilities/UMDColors.swift +++ b/umd-dining-app/Utilities/UMDColors.swift @@ -59,29 +59,6 @@ struct PressableButtonStyle: ButtonStyle { } } -// Same dip for views driven by tap gestures rather than a Button. -// A long press that never completes tracks touch-down without eating taps; -// @GestureState releases it when the finger lifts or the view scrolls away. -struct PressEffect: ViewModifier { - @GestureState private var isPressed = false - - func body(content: Content) -> some View { - content - .scaleEffect(isPressed ? 0.97 : 1) - .animation(.easeOut(duration: 0.12), value: isPressed) - .simultaneousGesture( - LongPressGesture(minimumDuration: .infinity, maximumDistance: 20) - .updating($isPressed) { _, state, _ in state = true } - ) - } -} - -extension View { - func pressEffect() -> some View { - modifier(PressEffect()) - } -} - extension View { func homeCard(tint: Color = .umdRed) -> some View { modifier(HomeCardStyle(tint: tint)) diff --git a/umd-dining-app/Views/FoodItemRow.swift b/umd-dining-app/Views/FoodItemRow.swift index fd2e371..67d7cb5 100644 --- a/umd-dining-app/Views/FoodItemRow.swift +++ b/umd-dining-app/Views/FoodItemRow.swift @@ -15,6 +15,7 @@ struct FoodItemRow: View { @State private var pendingNutrition: [String: String]? @State private var showHeartAnimation = false @State private var resolvedCalories: String? + @State private var isPressed = false private var displayCalories: String? { // Prefer nutrition bundled inline in API response (e.g. from fetchMenu) @@ -59,7 +60,7 @@ struct FoodItemRow: View { .padding(.vertical, 16) .padding(.horizontal, 16) .homeCard() - .pressEffect() + .scaleEffect(isPressed ? 0.97 : 1) .overlay { if showHeartAnimation { Image(systemName: "heart.fill") @@ -83,7 +84,15 @@ struct FoodItemRow: View { withAnimation(.easeOut(duration: 0.25)) { showHeartAnimation = false } } } - .onTapGesture(count: 1) { onTap?() } + .onTapGesture(count: 1) { + // Dip on the tap itself, not on touch-down, so scrolling stays clean + withAnimation(.easeOut(duration: 0.08)) { isPressed = true } + Task { + try? await Task.sleep(for: .milliseconds(90)) + withAnimation(.easeOut(duration: 0.12)) { isPressed = false } + onTap?() + } + } .fullScreenCover(isPresented: $showServingPicker) { if let nutrition = pendingNutrition { ServingPickerSheet( From 90e68f8b1c73b2f1aa0b1f11143ad9904b53a76f Mon Sep 17 00:00:00 2001 From: Advay Monga Date: Sun, 20 Sep 2026 00:48:43 -0400 Subject: [PATCH 3/6] Speed up the food card dip Co-Authored-By: Claude Opus 5 --- umd-dining-app/Views/FoodItemRow.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/umd-dining-app/Views/FoodItemRow.swift b/umd-dining-app/Views/FoodItemRow.swift index 67d7cb5..bec731d 100644 --- a/umd-dining-app/Views/FoodItemRow.swift +++ b/umd-dining-app/Views/FoodItemRow.swift @@ -86,10 +86,10 @@ struct FoodItemRow: View { } .onTapGesture(count: 1) { // Dip on the tap itself, not on touch-down, so scrolling stays clean - withAnimation(.easeOut(duration: 0.08)) { isPressed = true } + withAnimation(.easeOut(duration: 0.05)) { isPressed = true } Task { - try? await Task.sleep(for: .milliseconds(90)) - withAnimation(.easeOut(duration: 0.12)) { isPressed = false } + try? await Task.sleep(for: .milliseconds(45)) + withAnimation(.easeOut(duration: 0.08)) { isPressed = false } onTap?() } } From 0e9d32df6705478462b501f49b374725baf1bc48 Mon Sep 17 00:00:00 2001 From: Advay Monga Date: Sun, 20 Sep 2026 00:49:02 -0400 Subject: [PATCH 4/6] Launch picker: plain Dining Halls heading Co-Authored-By: Claude Opus 5 --- umd-dining-app/Views/DiningHallPickerView.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/umd-dining-app/Views/DiningHallPickerView.swift b/umd-dining-app/Views/DiningHallPickerView.swift index d54d183..db08b7e 100644 --- a/umd-dining-app/Views/DiningHallPickerView.swift +++ b/umd-dining-app/Views/DiningHallPickerView.swift @@ -35,9 +35,9 @@ struct DiningHallPickerView: View { .padding(.top, 12) .padding(.bottom, 8) - // Prompt + View Hours link + // Section label + View Hours link HStack { - Text("Hungry? Pick a dining hall.") + Text("Dining Halls") .font(.inter(size: 17, weight: .semibold)) .foregroundStyle(.primary) Spacer() From b08b1499a2d0e112f3d16096cdc26d25309a6cc7 Mon Sep 17 00:00:00 2001 From: Advay Monga Date: Sun, 20 Sep 2026 00:51:52 -0400 Subject: [PATCH 5/6] Gray, thinner outlines for secondary cards Co-Authored-By: Claude Opus 5 --- umd-dining-app/Utilities/UMDColors.swift | 10 +++++++--- umd-dining-app/Views/NutritionDetailView.swift | 4 ++-- umd-dining-app/Views/ProfileView.swift | 6 +++--- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/umd-dining-app/Utilities/UMDColors.swift b/umd-dining-app/Utilities/UMDColors.swift index 6d9b953..5b510aa 100644 --- a/umd-dining-app/Utilities/UMDColors.swift +++ b/umd-dining-app/Utilities/UMDColors.swift @@ -34,6 +34,8 @@ extension Color { struct HomeCardStyle: ViewModifier { var tint: Color + // Secondary cards (gray) wear a lighter outline than the red ones + var thin: Bool = false @Environment(\.colorScheme) private var colorScheme func body(content: Content) -> some View { @@ -42,7 +44,9 @@ struct HomeCardStyle: ViewModifier { .clipShape(RoundedRectangle(cornerRadius: 12)) .overlay(RoundedRectangle(cornerRadius: 12).stroke( tint.opacity(colorScheme == .dark ? 0.7 : 0.35), - lineWidth: colorScheme == .dark ? 2 : 1.5 + lineWidth: thin + ? (colorScheme == .dark ? 1.5 : 1) + : (colorScheme == .dark ? 2 : 1.5) )) .shadow(color: .black.opacity(colorScheme == .dark ? 0.35 : 0.10), radius: 5, x: 0, y: 2) } @@ -82,8 +86,8 @@ extension View { } extension View { - func homeCard(tint: Color = .umdRed) -> some View { - modifier(HomeCardStyle(tint: tint)) + func homeCard(tint: Color = .umdRed, thin: Bool = false) -> some View { + modifier(HomeCardStyle(tint: tint, thin: thin)) } } diff --git a/umd-dining-app/Views/NutritionDetailView.swift b/umd-dining-app/Views/NutritionDetailView.swift index 87a3336..3523289 100644 --- a/umd-dining-app/Views/NutritionDetailView.swift +++ b/umd-dining-app/Views/NutritionDetailView.swift @@ -236,7 +236,7 @@ struct NutritionDetailView: View { .foregroundStyle(.secondary) .padding(16) .frame(maxWidth: .infinity, alignment: .leading) - .homeCard() + .homeCard(tint: .gray, thin: true) } .padding(.horizontal, 16) } @@ -407,7 +407,7 @@ struct NutritionDetailView: View { } } } - .homeCard() + .homeCard(tint: .gray, thin: true) } .padding(.horizontal, 16) } diff --git a/umd-dining-app/Views/ProfileView.swift b/umd-dining-app/Views/ProfileView.swift index 5f5ce03..193d159 100644 --- a/umd-dining-app/Views/ProfileView.swift +++ b/umd-dining-app/Views/ProfileView.swift @@ -80,7 +80,7 @@ struct ProfileView: View { } } .padding(16) - .homeCard(tint: .gray) + .homeCard(tint: .gray, thin: true) .padding(.horizontal, 16) .padding(.top, 8) .padding(.bottom, 20) @@ -235,7 +235,7 @@ struct ProfileView: View { ) -> some View { Button(action: action) { content() - .homeCard(tint: .gray) + .homeCard(tint: .gray, thin: true) } .buttonStyle(PressableButtonStyle()) } @@ -258,7 +258,7 @@ struct ProfileView: View { } .padding(.horizontal, 16) .padding(.vertical, 16) - .homeCard(tint: .gray) + .homeCard(tint: .gray, thin: true) } // MARK: - Header From ec06cf45105f5081a3af07ef8ba339b106eb1df6 Mon Sep 17 00:00:00 2001 From: Advay Monga Date: Sun, 20 Sep 2026 00:56:32 -0400 Subject: [PATCH 6/6] Open the food page immediately; fade the filter in like search Co-Authored-By: Claude Opus 5 --- umd-dining-app/Views/FoodItemRow.swift | 5 ++-- umd-dining-app/Views/HomeView.swift | 37 +++++++++++++++----------- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/umd-dining-app/Views/FoodItemRow.swift b/umd-dining-app/Views/FoodItemRow.swift index bec731d..b89bdc3 100644 --- a/umd-dining-app/Views/FoodItemRow.swift +++ b/umd-dining-app/Views/FoodItemRow.swift @@ -85,12 +85,13 @@ struct FoodItemRow: View { } } .onTapGesture(count: 1) { - // Dip on the tap itself, not on touch-down, so scrolling stays clean + // Dip on the tap itself, not on touch-down, so scrolling stays clean. + // Navigation fires immediately; the dip plays out behind the push. + onTap?() withAnimation(.easeOut(duration: 0.05)) { isPressed = true } Task { try? await Task.sleep(for: .milliseconds(45)) withAnimation(.easeOut(duration: 0.08)) { isPressed = false } - onTap?() } } .fullScreenCover(isPresented: $showServingPicker) { diff --git a/umd-dining-app/Views/HomeView.swift b/umd-dining-app/Views/HomeView.swift index 4fed5ec..6aa578f 100644 --- a/umd-dining-app/Views/HomeView.swift +++ b/umd-dining-app/Views/HomeView.swift @@ -62,21 +62,6 @@ struct HomeView: View { ) .navigationTransition(.zoom(sourceID: "\(data.station)-\(data.diningHallId)", in: namespace)) } - .sheet(isPresented: $showFilter) { - FilterOverlay( - filterVegetarian: $viewModel.filterVegetarian, - filterVegan: $viewModel.filterVegan, - filterHalal: $viewModel.filterHalal, - filterGlutenFree: $viewModel.filterGlutenFree, - filterDairyFree: $viewModel.filterDairyFree, - filterHighProtein: $viewModel.filterHighProtein, - filterAllergens: $viewModel.filterAllergens, - onDismiss: { - showFilter = false - Task { await viewModel.loadMenus() } - } - ) - } .task { viewModel.autoSelectMealPeriod() await viewModel.loadMenus() @@ -99,6 +84,24 @@ struct HomeView: View { .transition(.opacity) .zIndex(2) } + + if showFilter { + FilterOverlay( + filterVegetarian: $viewModel.filterVegetarian, + filterVegan: $viewModel.filterVegan, + filterHalal: $viewModel.filterHalal, + filterGlutenFree: $viewModel.filterGlutenFree, + filterDairyFree: $viewModel.filterDairyFree, + filterHighProtein: $viewModel.filterHighProtein, + filterAllergens: $viewModel.filterAllergens, + onDismiss: { + withAnimation(.easeInOut(duration: 0.3)) { showFilter = false } + Task { await viewModel.loadMenus() } + } + ) + .transition(.opacity) + .zIndex(3) + } } .id(tabResetID) .onChange(of: tabResetID) { @@ -118,7 +121,9 @@ struct HomeView: View { Spacer() - Button { showFilter = true } label: { + Button { + withAnimation(.easeInOut(duration: 0.3)) { showFilter = true } + } label: { Image(systemName: "slider.horizontal.3") .font(.system(size: 18, weight: .medium)) .foregroundStyle(Color.umdRed)