Files
platform/ios/Platform/Platform/Shared/Components/MacroRing.swift
Yusuf Suleman d8f0e5d845
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 4s
feat: animated macro rings, bars, and staggered meal card entrance
MacroRing:
- Animates from 0 to target on appear (spring, 1.0s response)
- Center value fades in + scales up with 0.4s delay
- Updates animate smoothly on data change
- .contentTransition(.numericText()) on calorie count

MacroBar:
- Bar width animates from 0 to target on appear (spring, 0.3s delay)
- Updates animate smoothly on data change
- .contentTransition(.numericText()) on values

TodayView:
- Meal sections stagger in: fade up with 0.08s delay per card
- Re-animates on tab switch (onAppear resets animated flag)
- Re-animates on date change
- Spring physics for natural feel

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 12:20:21 -05:00

90 lines
2.6 KiB
Swift

import SwiftUI
struct MacroRing: View {
let consumed: Double
let goal: Double
var lineWidth: CGFloat = 10
var color: Color = .emerald
var size: CGFloat = 100
@State private var animatedProgress: Double = 0
private var targetProgress: Double {
guard goal > 0 else { return 0 }
return min(max(consumed / goal, 0), 1.0)
}
var body: some View {
ZStack {
Circle()
.stroke(color.opacity(0.15), lineWidth: lineWidth)
Circle()
.trim(from: 0, to: animatedProgress)
.stroke(
color,
style: StrokeStyle(lineWidth: lineWidth, lineCap: .round)
)
.rotationEffect(.degrees(-90))
}
.frame(width: size, height: size)
.onAppear {
animatedProgress = 0
withAnimation(.spring(response: 1.0, dampingFraction: 0.7).delay(0.2)) {
animatedProgress = targetProgress
}
}
.onChange(of: consumed) { _, _ in
withAnimation(.spring(response: 0.6, dampingFraction: 0.8)) {
animatedProgress = targetProgress
}
}
.onChange(of: goal) { _, _ in
withAnimation(.spring(response: 0.6, dampingFraction: 0.8)) {
animatedProgress = targetProgress
}
}
}
}
struct MacroRingWithLabel: View {
let consumed: Double
let goal: Double
let label: String
var color: Color = .emerald
var size: CGFloat = 100
var lineWidth: CGFloat = 10
@State private var showValue = false
var body: some View {
ZStack {
MacroRing(
consumed: consumed,
goal: goal,
lineWidth: lineWidth,
color: color,
size: size
)
VStack(spacing: 2) {
Text("\(Int(consumed))")
.font(.system(size: size * 0.22, weight: .bold, design: .rounded))
.foregroundStyle(Color.textPrimary)
.contentTransition(.numericText())
Text(label)
.font(.system(size: size * 0.1, weight: .medium))
.foregroundStyle(Color.textSecondary)
}
.opacity(showValue ? 1 : 0)
.scaleEffect(showValue ? 1 : 0.5)
}
.onAppear {
showValue = false
withAnimation(.spring(response: 0.6, dampingFraction: 0.7).delay(0.4)) {
showValue = true
}
}
}
}