style: adopt Liquid Glass design for iOS app
All checks were successful
Security Checks / dependency-audit (push) Successful in 15s
Security Checks / secret-scanning (push) Successful in 4s
Security Checks / dockerfile-lint (push) Successful in 4s

Migrate entire iOS app to Apple's Liquid Glass design language:
- System-adaptive colors (canvas, surfaces, text) for dark mode support
- Translucent material backgrounds (.thinMaterial, .ultraThinMaterial)
- Glass button styles (.glass, .glassProminent) on primary actions
- Liquid Glass navigation bar on Fitness tab
- Tab bar minimizes on scroll (.tabBarMinimizeBehavior)
- Increased corner radii (20pt) matching iOS 26 conventions
- Removed manual shadows (glass materials handle separation)

No business logic changes — purely cosmetic.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Yusuf Suleman
2026-04-03 16:26:53 -05:00
parent c47db95938
commit 7569d1b505
15 changed files with 97 additions and 169 deletions

View File

@@ -9,7 +9,6 @@ struct ContentView: View {
if auth.isCheckingAuth { if auth.isCheckingAuth {
ProgressView() ProgressView()
.frame(maxWidth: .infinity, maxHeight: .infinity) .frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.canvas)
} else if auth.isLoggedIn { } else if auth.isLoggedIn {
MainTabView() MainTabView()
} else { } else {
@@ -39,27 +38,26 @@ struct MainTabView: View {
.tag(1) .tag(1)
} }
.tint(Color.accentWarm) .tint(Color.accentWarm)
.tabBarMinimizeBehavior(.onScrollDown)
// Floating buttons // Floating buttons
VStack { VStack {
Spacer() Spacer()
HStack(alignment: .bottom) { HStack(alignment: .bottom) {
// Feedback button (subtle, bottom-left)
FeedbackButton() FeedbackButton()
.padding(.leading, 20) .padding(.leading, 20)
Spacer() Spacer()
// Add food button (prominent, bottom-right)
Button { showAssistant = true } label: { Button { showAssistant = true } label: {
Image(systemName: "plus") Image(systemName: "plus")
.font(.title2.weight(.semibold)) .font(.title2.weight(.semibold))
.foregroundStyle(.white) .foregroundStyle(.white)
.frame(width: 56, height: 56) .frame(width: 56, height: 56)
.background(Color.accentWarm)
.clipShape(Circle())
.shadow(color: .black.opacity(0.2), radius: 8, y: 4)
} }
.buttonStyle(.glassProminent)
.tint(Color.accentWarm)
.clipShape(Circle())
.padding(.trailing, 20) .padding(.trailing, 20)
} }
.padding(.bottom, 70) .padding(.bottom, 70)
@@ -112,12 +110,11 @@ struct AssistantSheetView: View {
tabButton("Quick Add", icon: "magnifyingglass", index: 1) tabButton("Quick Add", icon: "magnifyingglass", index: 1)
} }
.padding(4) .padding(4)
.background(Color.textTertiary.opacity(0.08)) .background(.ultraThinMaterial)
.clipShape(Capsule()) .clipShape(Capsule())
.padding(.horizontal, 40) .padding(.horizontal, 40)
} }
.padding(.bottom, 12) .padding(.bottom, 12)
.background(Color.canvas)
if selectedMode == 0 { if selectedMode == 0 {
AssistantChatView(onFoodAdded: onFoodAdded) AssistantChatView(onFoodAdded: onFoodAdded)
@@ -125,7 +122,6 @@ struct AssistantSheetView: View {
FoodSearchView(isSheet: true, onFoodAdded: onFoodAdded) FoodSearchView(isSheet: true, onFoodAdded: onFoodAdded)
} }
} }
.background(Color.canvas)
.presentationDetents([.large]) .presentationDetents([.large])
} }
@@ -140,7 +136,7 @@ struct AssistantSheetView: View {
.foregroundStyle(selectedMode == index ? Color.textPrimary : Color.textTertiary) .foregroundStyle(selectedMode == index ? Color.textPrimary : Color.textTertiary)
.padding(.horizontal, 16) .padding(.horizontal, 16)
.padding(.vertical, 8) .padding(.vertical, 8)
.background(selectedMode == index ? Color.surfaceCard : Color.clear) .background(selectedMode == index ? .thinMaterial : .clear)
.clipShape(Capsule()) .clipShape(Capsule())
} }
} }

View File

@@ -11,7 +11,6 @@ struct AssistantChatView: View {
ScrollViewReader { proxy in ScrollViewReader { proxy in
ScrollView { ScrollView {
LazyVStack(spacing: 8) { LazyVStack(spacing: 8) {
// Welcome state when no messages
if vm.messages.isEmpty { if vm.messages.isEmpty {
VStack(spacing: 16) { VStack(spacing: 16) {
Spacer(minLength: 60) Spacer(minLength: 60)
@@ -37,12 +36,10 @@ struct AssistantChatView: View {
.id(message.id) .id(message.id)
} }
// Draft card
if let draft = vm.currentDraft, !vm.applied { if let draft = vm.currentDraft, !vm.applied {
draftCard(draft) draftCard(draft)
} }
// Multiple drafts
if vm.currentDrafts.count > 1 && !vm.applied { if vm.currentDrafts.count > 1 && !vm.applied {
multipleDraftsCard multipleDraftsCard
} }
@@ -113,9 +110,8 @@ struct AssistantChatView: View {
} }
.padding(.horizontal, 16) .padding(.horizontal, 16)
.padding(.vertical, 10) .padding(.vertical, 10)
.background(Color.surfaceCard) .background(.bar)
} }
.background(Color.canvas)
.onChange(of: vm.selectedPhoto) { .onChange(of: vm.selectedPhoto) {
Task { await vm.handlePhotoSelection() } Task { await vm.handlePhotoSelection() }
} }
@@ -141,10 +137,10 @@ struct AssistantChatView: View {
.padding(.vertical, 10) .padding(.vertical, 10)
.background( .background(
message.role == "user" message.role == "user"
? Color.accentWarm ? AnyShapeStyle(Color.accentWarm)
: Color.surfaceSheet : AnyShapeStyle(.thinMaterial)
) )
.clipShape(RoundedRectangle(cornerRadius: 16)) .clipShape(RoundedRectangle(cornerRadius: 18))
if message.role == "assistant" { Spacer(minLength: 60) } if message.role == "assistant" { Spacer(minLength: 60) }
} }
@@ -189,14 +185,12 @@ struct AssistantChatView: View {
.foregroundStyle(.white) .foregroundStyle(.white)
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.padding(.vertical, 10) .padding(.vertical, 10)
.background(Color.emerald)
.clipShape(RoundedRectangle(cornerRadius: 10))
} }
.buttonStyle(.glassProminent)
.tint(Color.emerald)
} }
.padding() .padding()
.background(Color.surfaceCard) .background(.thinMaterial, in: RoundedRectangle(cornerRadius: 20))
.clipShape(RoundedRectangle(cornerRadius: 14))
.shadow(color: .black.opacity(0.06), radius: 6, y: 2)
.padding(.horizontal, 12) .padding(.horizontal, 12)
} }
@@ -235,14 +229,12 @@ struct AssistantChatView: View {
.foregroundStyle(.white) .foregroundStyle(.white)
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.padding(.vertical, 10) .padding(.vertical, 10)
.background(Color.emerald)
.clipShape(RoundedRectangle(cornerRadius: 10))
} }
.buttonStyle(.glassProminent)
.tint(Color.emerald)
} }
.padding() .padding()
.background(Color.surfaceCard) .background(.thinMaterial, in: RoundedRectangle(cornerRadius: 20))
.clipShape(RoundedRectangle(cornerRadius: 14))
.shadow(color: .black.opacity(0.06), radius: 6, y: 2)
.padding(.horizontal, 12) .padding(.horizontal, 12)
} }

View File

@@ -57,9 +57,8 @@ struct LoginView: View {
} }
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.frame(height: 48) .frame(height: 48)
.background(Color.accentWarm)
.foregroundStyle(.white) .foregroundStyle(.white)
.clipShape(RoundedRectangle(cornerRadius: 12)) .background(Color.accentWarm, in: RoundedRectangle(cornerRadius: 14))
.disabled(username.isEmpty || password.isEmpty || isLoading) .disabled(username.isEmpty || password.isEmpty || isLoading)
.opacity(username.isEmpty || password.isEmpty ? 0.6 : 1) .opacity(username.isEmpty || password.isEmpty ? 0.6 : 1)
} }
@@ -68,6 +67,5 @@ struct LoginView: View {
Spacer() Spacer()
Spacer() Spacer()
} }
.background(Color.canvas)
} }
} }

View File

@@ -10,10 +10,9 @@ struct FeedbackButton: View {
.font(.system(size: 14)) .font(.system(size: 14))
.foregroundStyle(Color.textTertiary) .foregroundStyle(Color.textTertiary)
.frame(width: 32, height: 32) .frame(width: 32, height: 32)
.background(Color.surfaceCard.opacity(0.8))
.clipShape(Circle())
.shadow(color: .black.opacity(0.08), radius: 4, y: 2)
} }
.buttonStyle(.glass)
.clipShape(Circle())
.sheet(isPresented: $showSheet) { .sheet(isPresented: $showSheet) {
FeedbackSheet() FeedbackSheet()
.presentationDetents([.large, .medium]) .presentationDetents([.large, .medium])
@@ -61,12 +60,7 @@ struct FeedbackSheet: View {
.font(.body) .font(.body)
.scrollContentBackground(.hidden) .scrollContentBackground(.hidden)
.padding(12) .padding(12)
.background(Color.canvas) .background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 14))
.clipShape(RoundedRectangle(cornerRadius: 12))
.overlay(
RoundedRectangle(cornerRadius: 12)
.stroke(Color.textTertiary.opacity(0.2), lineWidth: 1)
)
// Photo attachment // Photo attachment
HStack { HStack {
@@ -80,8 +74,7 @@ struct FeedbackSheet: View {
.foregroundStyle(Color.accentWarm) .foregroundStyle(Color.accentWarm)
.padding(.horizontal, 12) .padding(.horizontal, 12)
.padding(.vertical, 8) .padding(.vertical, 8)
.background(Color.accentWarm.opacity(0.1)) .background(Color.accentWarm.opacity(0.1), in: Capsule())
.clipShape(Capsule())
} }
if let preview = photoPreview { if let preview = photoPreview {
@@ -123,14 +116,15 @@ struct FeedbackSheet: View {
} }
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.frame(height: 48) .frame(height: 48)
.background(text.trimmingCharacters(in: .whitespaces).isEmpty ? Color.textTertiary : Color.accentWarm)
.foregroundStyle(.white) .foregroundStyle(.white)
.clipShape(RoundedRectangle(cornerRadius: 12)) .background(
text.trimmingCharacters(in: .whitespaces).isEmpty ? Color.textTertiary : Color.accentWarm,
in: RoundedRectangle(cornerRadius: 14)
)
.disabled(text.trimmingCharacters(in: .whitespaces).isEmpty || isSending) .disabled(text.trimmingCharacters(in: .whitespaces).isEmpty || isSending)
} }
} }
.padding() .padding()
.background(Color.surfaceCard)
.onChange(of: selectedPhoto) { .onChange(of: selectedPhoto) {
Task { Task {
if let item = selectedPhoto, if let item = selectedPhoto,

View File

@@ -80,7 +80,6 @@ struct AddFoodSheet: View {
} }
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
// Serving picker
if let servings = food.servings, !servings.isEmpty { if let servings = food.servings, !servings.isEmpty {
Picker("Serving", selection: $selectedServingId) { Picker("Serving", selection: $selectedServingId) {
Text("Base (\(food.baseUnit))").tag(nil as String?) Text("Base (\(food.baseUnit))").tag(nil as String?)
@@ -92,8 +91,7 @@ struct AddFoodSheet: View {
} }
} }
.padding() .padding()
.background(Color.surfaceCard) .background(.thinMaterial, in: RoundedRectangle(cornerRadius: 16))
.clipShape(RoundedRectangle(cornerRadius: 12))
// Meal picker // Meal picker
VStack(alignment: .leading, spacing: 8) { VStack(alignment: .leading, spacing: 8) {
@@ -116,15 +114,15 @@ struct AddFoodSheet: View {
.padding(.vertical, 10) .padding(.vertical, 10)
.background(selectedMeal == meal .background(selectedMeal == meal
? Color.mealColor(for: meal.rawValue).opacity(0.15) ? Color.mealColor(for: meal.rawValue).opacity(0.15)
: Color.surfaceCard : .clear
) )
.foregroundStyle(selectedMeal == meal .foregroundStyle(selectedMeal == meal
? Color.mealColor(for: meal.rawValue) ? Color.mealColor(for: meal.rawValue)
: Color.textSecondary : Color.textSecondary
) )
.clipShape(RoundedRectangle(cornerRadius: 10)) .clipShape(RoundedRectangle(cornerRadius: 12))
.overlay( .overlay(
RoundedRectangle(cornerRadius: 10) RoundedRectangle(cornerRadius: 12)
.stroke(selectedMeal == meal .stroke(selectedMeal == meal
? Color.mealColor(for: meal.rawValue).opacity(0.3) ? Color.mealColor(for: meal.rawValue).opacity(0.3)
: Color.clear, lineWidth: 1 : Color.clear, lineWidth: 1
@@ -135,8 +133,7 @@ struct AddFoodSheet: View {
} }
} }
.padding() .padding()
.background(Color.surfaceCard) .background(.thinMaterial, in: RoundedRectangle(cornerRadius: 16))
.clipShape(RoundedRectangle(cornerRadius: 12))
// Nutrition preview // Nutrition preview
VStack(spacing: 8) { VStack(spacing: 8) {
@@ -157,8 +154,7 @@ struct AddFoodSheet: View {
} }
} }
.padding() .padding()
.background(Color.surfaceCard) .background(.thinMaterial, in: RoundedRectangle(cornerRadius: 16))
.clipShape(RoundedRectangle(cornerRadius: 12))
if let error { if let error {
Text(error) Text(error)
@@ -169,7 +165,6 @@ struct AddFoodSheet: View {
.padding() .padding()
} }
.scrollDismissesKeyboard(.immediately) .scrollDismissesKeyboard(.immediately)
.background(Color.canvas)
.safeAreaInset(edge: .bottom) { .safeAreaInset(edge: .bottom) {
Button { Button {
addEntry() addEntry()
@@ -184,13 +179,12 @@ struct AddFoodSheet: View {
} }
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.frame(height: 50) .frame(height: 50)
.background(Color.accentWarm)
.foregroundStyle(.white) .foregroundStyle(.white)
.clipShape(RoundedRectangle(cornerRadius: 12)) .background(Color.accentWarm, in: RoundedRectangle(cornerRadius: 16))
.disabled(isAdding) .disabled(isAdding)
.padding(.horizontal) .padding(.horizontal)
.padding(.bottom, 8) .padding(.bottom, 8)
.background(Color.canvas) .background(.bar)
} }
.navigationTitle("Add Food") .navigationTitle("Add Food")
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)

View File

@@ -48,7 +48,6 @@ struct EntryDetailView: View {
.foregroundStyle(Color.textPrimary) .foregroundStyle(Color.textPrimary)
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
// Meal type picker
VStack(alignment: .leading, spacing: 8) { VStack(alignment: .leading, spacing: 8) {
Text("Meal") Text("Meal")
.font(.subheadline.weight(.medium)) .font(.subheadline.weight(.medium))
@@ -62,7 +61,6 @@ struct EntryDetailView: View {
.pickerStyle(.segmented) .pickerStyle(.segmented)
} }
// Quantity field
VStack(alignment: .leading, spacing: 8) { VStack(alignment: .leading, spacing: 8) {
Text("Quantity (\(entry.unit))") Text("Quantity (\(entry.unit))")
.font(.subheadline.weight(.medium)) .font(.subheadline.weight(.medium))
@@ -85,8 +83,7 @@ struct EntryDetailView: View {
.keyboardType(.decimalPad) .keyboardType(.decimalPad)
.frame(width: 80) .frame(width: 80)
.padding(.vertical, 8) .padding(.vertical, 8)
.background(Color.canvas) .background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 10))
.clipShape(RoundedRectangle(cornerRadius: 8))
Button { Button {
if let val = Double(editQuantity) { if let val = Double(editQuantity) {
@@ -133,15 +130,13 @@ struct EntryDetailView: View {
.foregroundStyle(.white) .foregroundStyle(.white)
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.padding(.vertical, 12) .padding(.vertical, 12)
.background(Color.emerald)
.clipShape(RoundedRectangle(cornerRadius: 10))
} }
.buttonStyle(.glassProminent)
.tint(Color.emerald)
.disabled(isSaving) .disabled(isSaving)
} }
.padding() .padding()
.background(Color.surfaceCard) .background(.thinMaterial, in: RoundedRectangle(cornerRadius: 20))
.clipShape(RoundedRectangle(cornerRadius: 14))
.shadow(color: .black.opacity(0.04), radius: 6, y: 2)
// Nutrition grid // Nutrition grid
LazyVGrid(columns: [ LazyVGrid(columns: [
@@ -157,8 +152,7 @@ struct EntryDetailView: View {
nutritionCell("Fiber", value: entry.fiber, unit: "g", color: .green) nutritionCell("Fiber", value: entry.fiber, unit: "g", color: .green)
} }
.padding() .padding()
.background(Color.surfaceCard) .background(.thinMaterial, in: RoundedRectangle(cornerRadius: 20))
.clipShape(RoundedRectangle(cornerRadius: 14))
// Metadata // Metadata
VStack(spacing: 8) { VStack(spacing: 8) {
@@ -170,8 +164,7 @@ struct EntryDetailView: View {
} }
} }
.padding() .padding()
.background(Color.surfaceCard) .background(.thinMaterial, in: RoundedRectangle(cornerRadius: 20))
.clipShape(RoundedRectangle(cornerRadius: 14))
// Delete button // Delete button
Button(role: .destructive) { Button(role: .destructive) {
@@ -182,12 +175,11 @@ struct EntryDetailView: View {
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.frame(height: 48) .frame(height: 48)
} }
.buttonStyle(.borderedProminent) .buttonStyle(.glass)
.tint(.red) .tint(.red)
} }
.padding() .padding()
} }
.background(Color.canvas)
.navigationTitle("Entry Detail") .navigationTitle("Entry Detail")
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
.alert("Delete Entry?", isPresented: $showDeleteConfirmation) { .alert("Delete Entry?", isPresented: $showDeleteConfirmation) {

View File

@@ -5,46 +5,45 @@ struct FitnessTabView: View {
var body: some View { var body: some View {
NavigationStack { NavigationStack {
VStack(spacing: 0) { VStack(spacing: 0) {
// Sub-tab selector // Sub-tab selector
HStack(spacing: 0) { HStack(spacing: 0) {
ForEach(Array(fitnessSubTabs.enumerated()), id: \.offset) { index, tab in ForEach(Array(fitnessSubTabs.enumerated()), id: \.offset) { index, tab in
Button { Button {
withAnimation(.easeInOut(duration: 0.2)) { withAnimation(.easeInOut(duration: 0.2)) {
selectedSubTab = index selectedSubTab = index
}
} label: {
Text(tab)
.font(.subheadline.weight(selectedSubTab == index ? .semibold : .regular))
.foregroundStyle(selectedSubTab == index ? Color.accentWarm : Color.textSecondary)
.padding(.vertical, 10)
.padding(.horizontal, 16)
.background {
if selectedSubTab == index {
Capsule()
.fill(Color.accentWarm.opacity(0.12))
}
} }
} label: {
Text(tab)
.font(.subheadline.weight(selectedSubTab == index ? .semibold : .regular))
.foregroundStyle(selectedSubTab == index ? Color.accentWarm : Color.textSecondary)
.padding(.vertical, 10)
.padding(.horizontal, 16)
.background {
if selectedSubTab == index {
Capsule()
.fill(.ultraThinMaterial)
}
}
}
} }
} }
} .padding(.horizontal)
.padding(.horizontal) .padding(.top, 8)
.padding(.top, 8)
// Content tap tabs to switch (no page swipe, preserves swipe-to-delete) Group {
Group { switch selectedSubTab {
switch selectedSubTab { case 0: TodayView()
case 0: TodayView() case 1: TemplatesView()
case 1: TemplatesView() case 2: GoalsView()
case 2: GoalsView() case 3: FoodLibraryView()
case 3: FoodLibraryView() default: TodayView()
default: TodayView() }
} }
.animation(.easeInOut(duration: 0.2), value: selectedSubTab)
} }
.animation(.easeInOut(duration: 0.2), value: selectedSubTab) .navigationTitle("Fitness")
} .navigationBarTitleDisplayMode(.large)
.background(Color.canvas)
.navigationBarHidden(true)
} }
} }

View File

@@ -24,8 +24,7 @@ struct FoodLibraryView: View {
} }
} }
.padding(12) .padding(12)
.background(Color.surfaceCard) .background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 12))
.clipShape(RoundedRectangle(cornerRadius: 10))
.padding(.horizontal) .padding(.horizontal)
.padding(.top, 8) .padding(.top, 8)
@@ -82,7 +81,6 @@ struct FoodLibraryView: View {
} }
} }
} }
.background(Color.canvas)
.task { .task {
await vm.loadInitial() await vm.loadInitial()
} }

View File

@@ -26,22 +26,18 @@ struct FoodSearchView: View {
} }
} }
.padding(12) .padding(12)
.background(Color.surfaceCard) .background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 12))
.clipShape(RoundedRectangle(cornerRadius: 10))
.padding(.horizontal) .padding(.horizontal)
.padding(.top, 8) .padding(.top, 8)
if vm.isLoadingInitial { if vm.isLoadingInitial {
LoadingView() LoadingView()
} else if !vm.searchText.isEmpty { } else if !vm.searchText.isEmpty {
// Search results
searchResultsList searchResultsList
} else { } else {
// Recent + All
defaultList defaultList
} }
} }
.background(Color.canvas)
.task { .task {
await vm.loadInitial() await vm.loadInitial()
} }
@@ -132,14 +128,11 @@ struct FoodSearchView: View {
private func recentFoodRow(_ recent: RecentFood) -> some View { private func recentFoodRow(_ recent: RecentFood) -> some View {
Button { Button {
// Navigate to add sheet by loading the full food
Task { Task {
do { do {
let food = try await FitnessAPI().getFood(id: recent.foodId) let food = try await FitnessAPI().getFood(id: recent.foodId)
selectedFood = food selectedFood = food
} catch { } catch {}
// Silently fail
}
} }
} label: { } label: {
HStack { HStack {

View File

@@ -23,7 +23,6 @@ struct GoalsView: View {
.padding(.horizontal) .padding(.horizontal)
.padding(.top, 8) .padding(.top, 8)
} }
.background(Color.canvas)
.task { .task {
await vm.load() await vm.load()
} }
@@ -92,15 +91,13 @@ struct GoalsView: View {
.foregroundStyle(.white) .foregroundStyle(.white)
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.padding(.vertical, 12) .padding(.vertical, 12)
.background(Color.emerald)
.clipShape(RoundedRectangle(cornerRadius: 10))
} }
.buttonStyle(.glassProminent)
.tint(Color.emerald)
.disabled(vm.isSaving) .disabled(vm.isSaving)
} }
.padding() .padding()
.background(Color.surfaceCard) .background(.thinMaterial, in: RoundedRectangle(cornerRadius: 20))
.clipShape(RoundedRectangle(cornerRadius: 14))
.shadow(color: .black.opacity(0.04), radius: 6, y: 2)
} }
private func goalField(_ label: String, text: Binding<String>, unit: String, color: Color) -> some View { private func goalField(_ label: String, text: Binding<String>, unit: String, color: Color) -> some View {
@@ -120,6 +117,6 @@ struct GoalsView: View {
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.padding(.vertical, 12) .padding(.vertical, 12)
.background(color.opacity(0.06)) .background(color.opacity(0.06))
.clipShape(RoundedRectangle(cornerRadius: 10)) .clipShape(RoundedRectangle(cornerRadius: 12))
} }
} }

View File

@@ -20,13 +20,11 @@ struct MealSectionView: View {
} }
} label: { } label: {
HStack(spacing: 0) { HStack(spacing: 0) {
// Colored accent bar
RoundedRectangle(cornerRadius: 2) RoundedRectangle(cornerRadius: 2)
.fill(Color.mealColor(for: mealType.rawValue)) .fill(Color.mealColor(for: mealType.rawValue))
.frame(width: 4, height: 40) .frame(width: 4, height: 40)
.padding(.trailing, 12) .padding(.trailing, 12)
// Meal icon in tinted circle
Image(systemName: mealType.icon) Image(systemName: mealType.icon)
.font(.title3.weight(.semibold)) .font(.title3.weight(.semibold))
.foregroundStyle(Color.mealColor(for: mealType.rawValue)) .foregroundStyle(Color.mealColor(for: mealType.rawValue))
@@ -63,14 +61,14 @@ struct MealSectionView: View {
.padding(.horizontal, 16) .padding(.horizontal, 16)
.padding(.vertical, 14) .padding(.vertical, 14)
.background(Color.mealColor(for: mealType.rawValue).opacity(0.04)) .background(Color.mealColor(for: mealType.rawValue).opacity(0.04))
.clipShape(RoundedRectangle(cornerRadius: 14)) .clipShape(RoundedRectangle(cornerRadius: 16))
} }
if isExpanded { if isExpanded {
ForEach(entries) { entry in ForEach(entries) { entry in
SwipeToDeleteRow( SwipeToDeleteRow(
onDelete: { onDelete(entry) }, onDelete: { onDelete(entry) },
onTap: { /* handled by NavigationLink */ }, onTap: { },
destination: { EntryDetailView(entry: entry, onDelete: { onDelete(entry) }) } destination: { EntryDetailView(entry: entry, onDelete: { onDelete(entry) }) }
) { ) {
entryRow(entry) entryRow(entry)
@@ -78,9 +76,7 @@ struct MealSectionView: View {
} }
} }
} }
.background(Color.surfaceCard) .background(.thinMaterial, in: RoundedRectangle(cornerRadius: 20))
.clipShape(RoundedRectangle(cornerRadius: 14))
.shadow(color: .black.opacity(0.04), radius: 6, y: 2)
.padding(.horizontal) .padding(.horizontal)
} }
@@ -112,7 +108,6 @@ struct MealSectionView: View {
} }
.padding(.horizontal, 16) .padding(.horizontal, 16)
.padding(.vertical, 10) .padding(.vertical, 10)
.background(Color.surfaceCard)
} }
} }
@@ -130,7 +125,6 @@ struct SwipeToDeleteRow<Content: View, Destination: View>: View {
var body: some View { var body: some View {
ZStack(alignment: .trailing) { ZStack(alignment: .trailing) {
// Delete button background
HStack { HStack {
Spacer() Spacer()
Button { Button {
@@ -148,7 +142,6 @@ struct SwipeToDeleteRow<Content: View, Destination: View>: View {
.background(Color.red) .background(Color.red)
} }
// Content row
content() content()
.offset(x: offset) .offset(x: offset)
.navigationDestination(isPresented: $showDetail) { .navigationDestination(isPresented: $showDetail) {
@@ -158,7 +151,6 @@ struct SwipeToDeleteRow<Content: View, Destination: View>: View {
if !isSwiping { if !isSwiping {
showDetail = true showDetail = true
} else { } else {
// Close swipe
withAnimation(.easeOut(duration: 0.2)) { withAnimation(.easeOut(duration: 0.2)) {
offset = 0 offset = 0
isSwiping = false isSwiping = false

View File

@@ -39,7 +39,6 @@ struct TemplatesView: View {
.padding(.horizontal) .padding(.horizontal)
.padding(.top, 8) .padding(.top, 8)
} }
.background(Color.canvas)
.task { .task {
await vm.load() await vm.load()
} }
@@ -118,8 +117,6 @@ struct TemplatesView: View {
} }
} }
.padding() .padding()
.background(Color.surfaceCard) .background(.thinMaterial, in: RoundedRectangle(cornerRadius: 16))
.clipShape(RoundedRectangle(cornerRadius: 12))
.shadow(color: .black.opacity(0.04), radius: 4, y: 2)
} }
} }

View File

@@ -6,13 +6,9 @@ struct TodayView: View {
var body: some View { var body: some View {
ScrollView { ScrollView {
VStack(spacing: 16) { VStack(spacing: 16) {
// Date selector
dateSelector dateSelector
// Macro summary
macroSummary macroSummary
// Meal sections
if vm.entries.isEmpty && !vm.isLoading { if vm.entries.isEmpty && !vm.isLoading {
EmptyStateView( EmptyStateView(
icon: "fork.knife", icon: "fork.knife",
@@ -38,7 +34,6 @@ struct TodayView: View {
.refreshable { .refreshable {
await vm.load() await vm.load()
} }
.background(Color.canvas)
.task { .task {
await vm.load() await vm.load()
} }
@@ -128,9 +123,7 @@ struct TodayView: View {
} }
} }
.padding(16) .padding(16)
.background(Color.surfaceCard) .background(.thinMaterial, in: RoundedRectangle(cornerRadius: 20))
.clipShape(RoundedRectangle(cornerRadius: 14))
.shadow(color: .black.opacity(0.04), radius: 6, y: 2)
.padding(.horizontal) .padding(.horizontal)
} }
} }

View File

@@ -64,7 +64,7 @@ struct HomeView: View {
.padding(.horizontal) .padding(.horizontal)
.padding(.top, 16) .padding(.top, 16)
// Widget grid half width, tap to go to fitness // Widget grid
HStack(spacing: 12) { HStack(spacing: 12) {
Button { selectedTab = 1 } label: { Button { selectedTab = 1 } label: {
calorieWidget calorieWidget
@@ -99,7 +99,6 @@ struct HomeView: View {
.textCase(.uppercase) .textCase(.uppercase)
.tracking(0.5) .tracking(0.5)
// Animated ring
ZStack { ZStack {
Circle() Circle()
.stroke(Color.emerald.opacity(0.15), lineWidth: 9) .stroke(Color.emerald.opacity(0.15), lineWidth: 9)
@@ -130,14 +129,8 @@ struct HomeView: View {
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.aspectRatio(1, contentMode: .fit) .aspectRatio(1, contentMode: .fit)
.background { .background {
if vm.hasBackground { RoundedRectangle(cornerRadius: 20)
RoundedRectangle(cornerRadius: 16) .fill(.ultraThinMaterial)
.fill(.ultraThinMaterial)
} else {
RoundedRectangle(cornerRadius: 16)
.fill(Color.surfaceCard)
.shadow(color: .black.opacity(0.05), radius: 8, y: 2)
}
} }
} }
} }

View File

@@ -1,21 +1,21 @@
import SwiftUI import SwiftUI
extension Color { extension Color {
// MARK: - Canvas / Background // MARK: - Canvas / Background (system-adaptive for Liquid Glass)
static let canvas = Color(red: 0.96, green: 0.94, blue: 0.90) // #F5EFE6 static let canvas = Color(.systemBackground)
// MARK: - Accent // MARK: - Accent
static let accentWarm = Color(red: 0.545, green: 0.412, blue: 0.078) // #8B6914 static let accentWarm = Color(red: 0.545, green: 0.412, blue: 0.078) // #8B6914
static let emerald = Color(red: 0.020, green: 0.588, blue: 0.412) // #059669 static let emerald = Color(red: 0.020, green: 0.588, blue: 0.412) // #059669
// MARK: - Surfaces // MARK: - Surfaces (system-adaptive)
static let surfaceCard = Color(red: 255/255, green: 252/255, blue: 248/255) // warm white static let surfaceCard = Color(.secondarySystemGroupedBackground)
static let surfaceSheet = Color(red: 0.98, green: 0.97, blue: 0.95) static let surfaceSheet = Color(.systemGroupedBackground)
// MARK: - Text // MARK: - Text (system-adaptive for Liquid Glass + dark mode)
static let textPrimary = Color(red: 0.12, green: 0.12, blue: 0.12) static let textPrimary = Color(.label)
static let textSecondary = Color(red: 0.45, green: 0.45, blue: 0.45) static let textSecondary = Color(.secondaryLabel)
static let textTertiary = Color(red: 0.65, green: 0.65, blue: 0.65) static let textTertiary = Color(.tertiaryLabel)
// MARK: - Meal Colors // MARK: - Meal Colors
static let mealBreakfast = Color(red: 1.0, green: 0.72, blue: 0.27) // warm orange static let mealBreakfast = Color(red: 1.0, green: 0.72, blue: 0.27) // warm orange