fix: dashboard calorie widget horizontal layout + bold meal section headers
Dashboard: - Removed LazyVGrid, calorie widget is full-width HStack - Ring (90px) + text info side by side - No more vertical text issue Meal sections: - Title3 bold font for meal name - Meal icon in tinted circle background - Item count subtitle - Calorie total in meal color (bold) - Subtle tinted background on header - Clear visual separation from food entries Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -19,41 +19,51 @@ struct MealSectionView: View {
|
||||
isExpanded.toggle()
|
||||
}
|
||||
} label: {
|
||||
HStack(spacing: 12) {
|
||||
// Accent bar
|
||||
HStack(spacing: 0) {
|
||||
// Colored accent bar
|
||||
RoundedRectangle(cornerRadius: 2)
|
||||
.fill(Color.mealColor(for: mealType.rawValue))
|
||||
.frame(width: 4, height: 32)
|
||||
.frame(width: 4, height: 40)
|
||||
.padding(.trailing, 12)
|
||||
|
||||
// Meal icon in tinted circle
|
||||
Image(systemName: mealType.icon)
|
||||
.font(.body.weight(.medium))
|
||||
.font(.title3.weight(.semibold))
|
||||
.foregroundStyle(Color.mealColor(for: mealType.rawValue))
|
||||
.frame(width: 32, height: 32)
|
||||
.background(Color.mealColor(for: mealType.rawValue).opacity(0.12))
|
||||
.clipShape(Circle())
|
||||
.padding(.trailing, 10)
|
||||
|
||||
Text(mealType.displayName)
|
||||
.font(.headline)
|
||||
.foregroundStyle(Color.textPrimary)
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text(mealType.displayName)
|
||||
.font(.title3.weight(.bold))
|
||||
.foregroundStyle(Color.textPrimary)
|
||||
|
||||
Text("\(entries.count)")
|
||||
.font(.caption.weight(.medium))
|
||||
.foregroundStyle(Color.textSecondary)
|
||||
.padding(.horizontal, 6)
|
||||
.padding(.vertical, 2)
|
||||
.background(Color.textSecondary.opacity(0.1))
|
||||
.clipShape(Capsule())
|
||||
Text("\(entries.count) item\(entries.count == 1 ? "" : "s")")
|
||||
.font(.caption)
|
||||
.foregroundStyle(Color.textTertiary)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
Text("\(Int(totalCalories)) kcal")
|
||||
.font(.subheadline.weight(.semibold))
|
||||
Text("\(Int(totalCalories))")
|
||||
.font(.title3.weight(.bold))
|
||||
.foregroundStyle(Color.mealColor(for: mealType.rawValue))
|
||||
+ Text(" kcal")
|
||||
.font(.caption.weight(.medium))
|
||||
.foregroundStyle(Color.textSecondary)
|
||||
|
||||
Image(systemName: "chevron.right")
|
||||
.font(.caption.weight(.medium))
|
||||
.foregroundStyle(Color.textTertiary)
|
||||
.rotationEffect(.degrees(isExpanded ? 90 : 0))
|
||||
.padding(.leading, 8)
|
||||
}
|
||||
.padding(.horizontal, 16)
|
||||
.padding(.vertical, 12)
|
||||
.padding(.vertical, 14)
|
||||
.background(Color.mealColor(for: mealType.rawValue).opacity(0.04))
|
||||
.clipShape(RoundedRectangle(cornerRadius: 14))
|
||||
}
|
||||
|
||||
if isExpanded {
|
||||
|
||||
@@ -4,10 +4,10 @@ import PhotosUI
|
||||
struct HomeView: View {
|
||||
@Environment(AuthManager.self) private var auth
|
||||
@State private var vm = HomeViewModel()
|
||||
@State private var showProfileMenu = false
|
||||
@State private var showAssistant = false
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
ZStack(alignment: .bottomTrailing) {
|
||||
// Background
|
||||
if let bg = vm.backgroundImage {
|
||||
Image(uiImage: bg)
|
||||
@@ -23,7 +23,7 @@ struct HomeView: View {
|
||||
// Top bar
|
||||
HStack {
|
||||
Text("Home")
|
||||
.font(.largeTitle.weight(.bold))
|
||||
.font(.title.weight(.bold))
|
||||
.foregroundStyle(vm.hasBackground ? .white : Color.textPrimary)
|
||||
Spacer()
|
||||
Menu {
|
||||
@@ -55,22 +55,36 @@ struct HomeView: View {
|
||||
.padding(.horizontal)
|
||||
.padding(.top, 60)
|
||||
|
||||
// Widget grid
|
||||
LazyVGrid(columns: [
|
||||
GridItem(.flexible(), spacing: 12),
|
||||
GridItem(.flexible(), spacing: 12),
|
||||
], spacing: 12) {
|
||||
// Calorie widget
|
||||
calorieWidget
|
||||
.gridCellColumns(2)
|
||||
}
|
||||
.padding(.horizontal)
|
||||
// Calorie widget — full width card
|
||||
calorieWidget
|
||||
.padding(.horizontal)
|
||||
|
||||
Spacer(minLength: 100)
|
||||
}
|
||||
}
|
||||
|
||||
// Floating + button
|
||||
Button {
|
||||
showAssistant = true
|
||||
} label: {
|
||||
Image(systemName: "plus")
|
||||
.font(.title2.weight(.semibold))
|
||||
.foregroundStyle(.white)
|
||||
.frame(width: 56, height: 56)
|
||||
.background(Color.accentWarm)
|
||||
.clipShape(Circle())
|
||||
.shadow(color: Color.accentWarm.opacity(0.3), radius: 8, y: 4)
|
||||
}
|
||||
.padding(.trailing, 20)
|
||||
.padding(.bottom, 100)
|
||||
}
|
||||
.navigationBarHidden(true)
|
||||
.sheet(isPresented: $showAssistant) {
|
||||
FoodSearchView(
|
||||
date: Date().apiDateString,
|
||||
onFoodAdded: { Task { await vm.loadTodayData() } }
|
||||
)
|
||||
}
|
||||
.task {
|
||||
await vm.loadTodayData()
|
||||
}
|
||||
@@ -81,22 +95,25 @@ struct HomeView: View {
|
||||
|
||||
private var calorieWidget: some View {
|
||||
HStack(spacing: 20) {
|
||||
// Ring
|
||||
MacroRingWithLabel(
|
||||
consumed: vm.totalCalories,
|
||||
goal: vm.calorieGoal,
|
||||
label: "kcal",
|
||||
color: .emerald,
|
||||
size: 100,
|
||||
lineWidth: 10
|
||||
size: 90,
|
||||
lineWidth: 9
|
||||
)
|
||||
.frame(width: 90, height: 90)
|
||||
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
// Text info
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
Text("Calories")
|
||||
.font(.headline)
|
||||
.foregroundStyle(vm.hasBackground ? .white : Color.textPrimary)
|
||||
|
||||
Text("\(Int(vm.totalCalories)) / \(Int(vm.calorieGoal))")
|
||||
.font(.subheadline)
|
||||
.font(.subheadline.weight(.medium))
|
||||
.foregroundStyle(vm.hasBackground ? .white.opacity(0.8) : Color.textSecondary)
|
||||
|
||||
let remaining = max(vm.calorieGoal - vm.totalCalories, 0)
|
||||
|
||||
Reference in New Issue
Block a user