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()
|
isExpanded.toggle()
|
||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
HStack(spacing: 12) {
|
HStack(spacing: 0) {
|
||||||
// Accent bar
|
// 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: 32)
|
.frame(width: 4, height: 40)
|
||||||
|
.padding(.trailing, 12)
|
||||||
|
|
||||||
|
// Meal icon in tinted circle
|
||||||
Image(systemName: mealType.icon)
|
Image(systemName: mealType.icon)
|
||||||
.font(.body.weight(.medium))
|
.font(.title3.weight(.semibold))
|
||||||
.foregroundStyle(Color.mealColor(for: mealType.rawValue))
|
.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)
|
VStack(alignment: .leading, spacing: 2) {
|
||||||
.font(.headline)
|
Text(mealType.displayName)
|
||||||
.foregroundStyle(Color.textPrimary)
|
.font(.title3.weight(.bold))
|
||||||
|
.foregroundStyle(Color.textPrimary)
|
||||||
|
|
||||||
Text("\(entries.count)")
|
Text("\(entries.count) item\(entries.count == 1 ? "" : "s")")
|
||||||
.font(.caption.weight(.medium))
|
.font(.caption)
|
||||||
.foregroundStyle(Color.textSecondary)
|
.foregroundStyle(Color.textTertiary)
|
||||||
.padding(.horizontal, 6)
|
}
|
||||||
.padding(.vertical, 2)
|
|
||||||
.background(Color.textSecondary.opacity(0.1))
|
|
||||||
.clipShape(Capsule())
|
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
Text("\(Int(totalCalories)) kcal")
|
Text("\(Int(totalCalories))")
|
||||||
.font(.subheadline.weight(.semibold))
|
.font(.title3.weight(.bold))
|
||||||
|
.foregroundStyle(Color.mealColor(for: mealType.rawValue))
|
||||||
|
+ Text(" kcal")
|
||||||
|
.font(.caption.weight(.medium))
|
||||||
.foregroundStyle(Color.textSecondary)
|
.foregroundStyle(Color.textSecondary)
|
||||||
|
|
||||||
Image(systemName: "chevron.right")
|
Image(systemName: "chevron.right")
|
||||||
.font(.caption.weight(.medium))
|
.font(.caption.weight(.medium))
|
||||||
.foregroundStyle(Color.textTertiary)
|
.foregroundStyle(Color.textTertiary)
|
||||||
.rotationEffect(.degrees(isExpanded ? 90 : 0))
|
.rotationEffect(.degrees(isExpanded ? 90 : 0))
|
||||||
|
.padding(.leading, 8)
|
||||||
}
|
}
|
||||||
.padding(.horizontal, 16)
|
.padding(.horizontal, 16)
|
||||||
.padding(.vertical, 12)
|
.padding(.vertical, 14)
|
||||||
|
.background(Color.mealColor(for: mealType.rawValue).opacity(0.04))
|
||||||
|
.clipShape(RoundedRectangle(cornerRadius: 14))
|
||||||
}
|
}
|
||||||
|
|
||||||
if isExpanded {
|
if isExpanded {
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ import PhotosUI
|
|||||||
struct HomeView: View {
|
struct HomeView: View {
|
||||||
@Environment(AuthManager.self) private var auth
|
@Environment(AuthManager.self) private var auth
|
||||||
@State private var vm = HomeViewModel()
|
@State private var vm = HomeViewModel()
|
||||||
@State private var showProfileMenu = false
|
@State private var showAssistant = false
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack {
|
ZStack(alignment: .bottomTrailing) {
|
||||||
// Background
|
// Background
|
||||||
if let bg = vm.backgroundImage {
|
if let bg = vm.backgroundImage {
|
||||||
Image(uiImage: bg)
|
Image(uiImage: bg)
|
||||||
@@ -23,7 +23,7 @@ struct HomeView: View {
|
|||||||
// Top bar
|
// Top bar
|
||||||
HStack {
|
HStack {
|
||||||
Text("Home")
|
Text("Home")
|
||||||
.font(.largeTitle.weight(.bold))
|
.font(.title.weight(.bold))
|
||||||
.foregroundStyle(vm.hasBackground ? .white : Color.textPrimary)
|
.foregroundStyle(vm.hasBackground ? .white : Color.textPrimary)
|
||||||
Spacer()
|
Spacer()
|
||||||
Menu {
|
Menu {
|
||||||
@@ -55,22 +55,36 @@ struct HomeView: View {
|
|||||||
.padding(.horizontal)
|
.padding(.horizontal)
|
||||||
.padding(.top, 60)
|
.padding(.top, 60)
|
||||||
|
|
||||||
// Widget grid
|
// Calorie widget — full width card
|
||||||
LazyVGrid(columns: [
|
calorieWidget
|
||||||
GridItem(.flexible(), spacing: 12),
|
.padding(.horizontal)
|
||||||
GridItem(.flexible(), spacing: 12),
|
|
||||||
], spacing: 12) {
|
|
||||||
// Calorie widget
|
|
||||||
calorieWidget
|
|
||||||
.gridCellColumns(2)
|
|
||||||
}
|
|
||||||
.padding(.horizontal)
|
|
||||||
|
|
||||||
Spacer(minLength: 100)
|
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)
|
.navigationBarHidden(true)
|
||||||
|
.sheet(isPresented: $showAssistant) {
|
||||||
|
FoodSearchView(
|
||||||
|
date: Date().apiDateString,
|
||||||
|
onFoodAdded: { Task { await vm.loadTodayData() } }
|
||||||
|
)
|
||||||
|
}
|
||||||
.task {
|
.task {
|
||||||
await vm.loadTodayData()
|
await vm.loadTodayData()
|
||||||
}
|
}
|
||||||
@@ -81,22 +95,25 @@ struct HomeView: View {
|
|||||||
|
|
||||||
private var calorieWidget: some View {
|
private var calorieWidget: some View {
|
||||||
HStack(spacing: 20) {
|
HStack(spacing: 20) {
|
||||||
|
// Ring
|
||||||
MacroRingWithLabel(
|
MacroRingWithLabel(
|
||||||
consumed: vm.totalCalories,
|
consumed: vm.totalCalories,
|
||||||
goal: vm.calorieGoal,
|
goal: vm.calorieGoal,
|
||||||
label: "kcal",
|
label: "kcal",
|
||||||
color: .emerald,
|
color: .emerald,
|
||||||
size: 100,
|
size: 90,
|
||||||
lineWidth: 10
|
lineWidth: 9
|
||||||
)
|
)
|
||||||
|
.frame(width: 90, height: 90)
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 8) {
|
// Text info
|
||||||
|
VStack(alignment: .leading, spacing: 6) {
|
||||||
Text("Calories")
|
Text("Calories")
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
.foregroundStyle(vm.hasBackground ? .white : Color.textPrimary)
|
.foregroundStyle(vm.hasBackground ? .white : Color.textPrimary)
|
||||||
|
|
||||||
Text("\(Int(vm.totalCalories)) / \(Int(vm.calorieGoal))")
|
Text("\(Int(vm.totalCalories)) / \(Int(vm.calorieGoal))")
|
||||||
.font(.subheadline)
|
.font(.subheadline.weight(.medium))
|
||||||
.foregroundStyle(vm.hasBackground ? .white.opacity(0.8) : Color.textSecondary)
|
.foregroundStyle(vm.hasBackground ? .white.opacity(0.8) : Color.textSecondary)
|
||||||
|
|
||||||
let remaining = max(vm.calorieGoal - vm.totalCalories, 0)
|
let remaining = max(vm.calorieGoal - vm.totalCalories, 0)
|
||||||
|
|||||||
Reference in New Issue
Block a user