fix: half-width animated calorie widget, remove duplicate +, warm colors
All checks were successful
Security Checks / dependency-audit (push) Successful in 13s
Security Checks / secret-scanning (push) Successful in 4s
Security Checks / dockerfile-lint (push) Successful in 3s

- Calorie widget: half width with animated ring (spring animation)
- Removed floating + from dashboard (only on fitness tab)
- surfaceCard changed from white to warm white (#FFFCF8)
- AssistantChatView gets canvas background
- Future widget placeholder on right side of grid

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Yusuf Suleman
2026-04-03 09:12:43 -05:00
parent 3040e55475
commit 39d7628c9e
4 changed files with 45 additions and 51 deletions

View File

@@ -88,6 +88,7 @@ struct AssistantChatView: View {
.padding(.vertical, 10) .padding(.vertical, 10)
.background(Color.surfaceCard) .background(Color.surfaceCard)
} }
.background(Color.canvas)
.onChange(of: vm.selectedPhoto) { .onChange(of: vm.selectedPhoto) {
Task { await vm.handlePhotoSelection() } Task { await vm.handlePhotoSelection() }
} }

View File

@@ -5,9 +5,10 @@ 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 showAssistant = false @State private var showAssistant = false
@State private var ringAnimated = false
var body: some View { var body: some View {
ZStack(alignment: .bottomTrailing) { ZStack {
// Background // Background
if let bg = vm.backgroundImage { if let bg = vm.backgroundImage {
Image(uiImage: bg) Image(uiImage: bg)
@@ -55,38 +56,24 @@ struct HomeView: View {
.padding(.horizontal) .padding(.horizontal)
.padding(.top, 60) .padding(.top, 60)
// Calorie widget full width card // Widget grid half width
calorieWidget HStack(spacing: 12) {
.padding(.horizontal) calorieWidget
// Future widget placeholder invisible spacer
Color.clear
}
.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()
withAnimation(.spring(response: 0.8, dampingFraction: 0.7)) {
ringAnimated = true
}
} }
.onChange(of: vm.selectedPhoto) { .onChange(of: vm.selectedPhoto) {
Task { await vm.handlePhotoSelection() } Task { await vm.handlePhotoSelection() }
@@ -94,37 +81,43 @@ struct HomeView: View {
} }
private var calorieWidget: some View { private var calorieWidget: some View {
HStack(spacing: 20) { VStack(spacing: 12) {
// Ring Text("Calories")
MacroRingWithLabel( .font(.caption.weight(.semibold))
consumed: vm.totalCalories, .foregroundStyle(vm.hasBackground ? .white.opacity(0.7) : Color.textTertiary)
goal: vm.calorieGoal, .textCase(.uppercase)
label: "kcal", .tracking(0.5)
color: .emerald,
size: 90,
lineWidth: 9
)
.frame(width: 90, height: 90)
// Text info // Animated ring
VStack(alignment: .leading, spacing: 6) { ZStack {
Text("Calories") Circle()
.font(.headline) .stroke(Color.emerald.opacity(0.15), lineWidth: 9)
.foregroundStyle(vm.hasBackground ? .white : Color.textPrimary)
Text("\(Int(vm.totalCalories)) / \(Int(vm.calorieGoal))") Circle()
.font(.subheadline.weight(.medium)) .trim(from: 0, to: ringAnimated ? min(max(vm.calorieGoal > 0 ? vm.totalCalories / vm.calorieGoal : 0, 0), 1) : 0)
.foregroundStyle(vm.hasBackground ? .white.opacity(0.8) : Color.textSecondary) .stroke(Color.emerald, style: StrokeStyle(lineWidth: 9, lineCap: .round))
.rotationEffect(.degrees(-90))
.animation(.spring(response: 0.8, dampingFraction: 0.7), value: ringAnimated)
let remaining = max(vm.calorieGoal - vm.totalCalories, 0) VStack(spacing: 1) {
Text("\(Int(remaining)) remaining") Text("\(Int(vm.totalCalories))")
.font(.caption) .font(.system(size: 22, weight: .bold, design: .rounded))
.foregroundStyle(vm.hasBackground ? .white.opacity(0.6) : Color.textTertiary) .foregroundStyle(vm.hasBackground ? .white : Color.textPrimary)
Text("/ \(Int(vm.calorieGoal))")
.font(.system(size: 10, weight: .medium, design: .rounded))
.foregroundStyle(vm.hasBackground ? .white.opacity(0.6) : Color.textTertiary)
}
} }
.frame(width: 80, height: 80)
Spacer() let remaining = max(vm.calorieGoal - vm.totalCalories, 0)
Text("\(Int(remaining)) left")
.font(.caption2.weight(.medium))
.foregroundStyle(vm.hasBackground ? .white.opacity(0.6) : Color.textTertiary)
} }
.padding(20) .padding(16)
.frame(maxWidth: .infinity)
.aspectRatio(1, contentMode: .fit)
.background { .background {
if vm.hasBackground { if vm.hasBackground {
RoundedRectangle(cornerRadius: 16) RoundedRectangle(cornerRadius: 16)

View File

@@ -9,7 +9,7 @@ extension Color {
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
static let surfaceCard = Color.white static let surfaceCard = Color(red: 255/255, green: 252/255, blue: 248/255) // warm white
static let surfaceSheet = Color(red: 0.98, green: 0.97, blue: 0.95) static let surfaceSheet = Color(red: 0.98, green: 0.97, blue: 0.95)
// MARK: - Text // MARK: - Text