fix: half-width animated calorie widget, remove duplicate +, warm colors
- 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:
0
ios/Platform/.stfolder/syncthing-folder-marker
Normal file
0
ios/Platform/.stfolder/syncthing-folder-marker
Normal 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() }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
HStack(spacing: 12) {
|
||||||
calorieWidget
|
calorieWidget
|
||||||
|
// Future widget placeholder — invisible spacer
|
||||||
|
Color.clear
|
||||||
|
}
|
||||||
.padding(.horizontal)
|
.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
|
|
||||||
MacroRingWithLabel(
|
|
||||||
consumed: vm.totalCalories,
|
|
||||||
goal: vm.calorieGoal,
|
|
||||||
label: "kcal",
|
|
||||||
color: .emerald,
|
|
||||||
size: 90,
|
|
||||||
lineWidth: 9
|
|
||||||
)
|
|
||||||
.frame(width: 90, height: 90)
|
|
||||||
|
|
||||||
// Text info
|
|
||||||
VStack(alignment: .leading, spacing: 6) {
|
|
||||||
Text("Calories")
|
Text("Calories")
|
||||||
.font(.headline)
|
.font(.caption.weight(.semibold))
|
||||||
|
.foregroundStyle(vm.hasBackground ? .white.opacity(0.7) : Color.textTertiary)
|
||||||
|
.textCase(.uppercase)
|
||||||
|
.tracking(0.5)
|
||||||
|
|
||||||
|
// Animated ring
|
||||||
|
ZStack {
|
||||||
|
Circle()
|
||||||
|
.stroke(Color.emerald.opacity(0.15), lineWidth: 9)
|
||||||
|
|
||||||
|
Circle()
|
||||||
|
.trim(from: 0, to: ringAnimated ? min(max(vm.calorieGoal > 0 ? vm.totalCalories / vm.calorieGoal : 0, 0), 1) : 0)
|
||||||
|
.stroke(Color.emerald, style: StrokeStyle(lineWidth: 9, lineCap: .round))
|
||||||
|
.rotationEffect(.degrees(-90))
|
||||||
|
.animation(.spring(response: 0.8, dampingFraction: 0.7), value: ringAnimated)
|
||||||
|
|
||||||
|
VStack(spacing: 1) {
|
||||||
|
Text("\(Int(vm.totalCalories))")
|
||||||
|
.font(.system(size: 22, weight: .bold, design: .rounded))
|
||||||
.foregroundStyle(vm.hasBackground ? .white : Color.textPrimary)
|
.foregroundStyle(vm.hasBackground ? .white : Color.textPrimary)
|
||||||
|
Text("/ \(Int(vm.calorieGoal))")
|
||||||
Text("\(Int(vm.totalCalories)) / \(Int(vm.calorieGoal))")
|
.font(.system(size: 10, weight: .medium, design: .rounded))
|
||||||
.font(.subheadline.weight(.medium))
|
|
||||||
.foregroundStyle(vm.hasBackground ? .white.opacity(0.8) : Color.textSecondary)
|
|
||||||
|
|
||||||
let remaining = max(vm.calorieGoal - vm.totalCalories, 0)
|
|
||||||
Text("\(Int(remaining)) remaining")
|
|
||||||
.font(.caption)
|
|
||||||
.foregroundStyle(vm.hasBackground ? .white.opacity(0.6) : Color.textTertiary)
|
.foregroundStyle(vm.hasBackground ? .white.opacity(0.6) : Color.textTertiary)
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer()
|
|
||||||
}
|
}
|
||||||
.padding(20)
|
.frame(width: 80, height: 80)
|
||||||
|
|
||||||
|
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(16)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.aspectRatio(1, contentMode: .fit)
|
||||||
.background {
|
.background {
|
||||||
if vm.hasBackground {
|
if vm.hasBackground {
|
||||||
RoundedRectangle(cornerRadius: 16)
|
RoundedRectangle(cornerRadius: 16)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user