feat: replace linear calorie bar with goal-aware ring widget
The home screen CaloriesCard now uses a circular ring (CustomPainter) instead of a LinearProgressIndicator. Ring colour is determined by the user's goal type (lose / maintain / gain) with inverted semantics for the gain goal — red means undereating, not overeating. Overflow beyond 100% is shown as a thinner second-lap arc in the same warning colour. Numbers (logged kcal, goal, remaining/overage) are displayed both inside the ring and in a stat column to the right. Adds docs/calorie_ring_color_spec.md with the full colour threshold specification per goal type. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
104
docs/calorie_ring_color_spec.md
Normal file
104
docs/calorie_ring_color_spec.md
Normal file
@@ -0,0 +1,104 @@
|
||||
# Calorie Ring — Colour Logic Specification
|
||||
|
||||
## Overview
|
||||
|
||||
The home screen displays daily calorie progress as a circular ring. The ring colour conveys
|
||||
how the user is tracking relative to their calorie goal. Because the meaning of "over goal"
|
||||
differs by goal type, each goal type has its own colour thresholds.
|
||||
|
||||
All colours are from the iOS system palette already defined in `AppColors`.
|
||||
|
||||
---
|
||||
|
||||
## Goal types
|
||||
|
||||
| Value | Russian | Semantic |
|
||||
|---|---|---|
|
||||
| `lose` | Похудение | Daily calories are a **ceiling** — eating less is success, eating over is a warning |
|
||||
| `maintain` | Поддержание | Daily calories are a **target** — closeness in either direction is success |
|
||||
| `gain` | Набор массы | Daily calories are a **floor** — eating enough (or more) is success, eating too little is a warning |
|
||||
|
||||
---
|
||||
|
||||
## Colour thresholds
|
||||
|
||||
Progress is defined as `loggedCalories / dailyGoal` (raw, unclamped — can exceed 1.0).
|
||||
|
||||
### `lose` — calorie ceiling
|
||||
|
||||
| Progress range | Colour | Hex | Message to user |
|
||||
|---|---|---|---|
|
||||
| 0% – 74% | Blue | `#007AFF` | Well within budget |
|
||||
| 75% – 94% | Green | `#34C759` | Approaching limit — on track |
|
||||
| 95% – 109% | Yellow / Orange | `#FF9500` | Slightly over limit |
|
||||
| ≥ 110% | Red | `#FF3B30` | Significantly over limit |
|
||||
|
||||
**Rationale:** For weight loss, the calorie limit is the primary constraint. Green signals
|
||||
the user is tracking well towards their limit without excess. Red signals a meaningful
|
||||
overshoot that threatens the calorie deficit required for weight loss.
|
||||
|
||||
---
|
||||
|
||||
### `maintain` — calorie target (bidirectional)
|
||||
|
||||
| Progress range | Colour | Hex | Message to user |
|
||||
|---|---|---|---|
|
||||
| < 70% | Blue | `#007AFF` | Significantly under target |
|
||||
| 70% – 89% | Yellow / Orange | `#FF9500` | Slightly under target |
|
||||
| 90% – 110% | Green | `#34C759` | On target ✓ |
|
||||
| 111% – 124% | Yellow / Orange | `#FF9500` | Slightly over target |
|
||||
| ≥ 125% | Red | `#FF3B30` | Significantly over target |
|
||||
|
||||
**Rationale:** Maintenance is a two-sided target. Green covers a ±10% acceptance band.
|
||||
Yellow is a soft nudge in either direction. Red only appears at ≥ 125% over (meaningful
|
||||
calorie surplus that would cause weight gain) or the symmetric < 70% (meaningful deficit
|
||||
that would cause weight loss).
|
||||
|
||||
---
|
||||
|
||||
### `gain` — calorie floor (semantics inverted)
|
||||
|
||||
| Progress range | Colour | Hex | Message to user |
|
||||
|---|---|---|---|
|
||||
| < 60% | Red | `#FF3B30` | Far below minimum — eat more |
|
||||
| 60% – 84% | Yellow / Orange | `#FF9500` | Under target |
|
||||
| 85% – 115% | Green | `#34C759` | At or above target ✓ |
|
||||
| > 115% | Blue | `#007AFF` | Well above target — informational |
|
||||
|
||||
**Rationale:** For muscle gain, not eating enough is the primary failure mode. Red means
|
||||
"not enough calories for muscle synthesis". Green means the calorie surplus target is met.
|
||||
Blue (rather than red) is used above 115% because eating extra while bulking is neutral to
|
||||
positive — it does not warrant an alarming colour.
|
||||
|
||||
---
|
||||
|
||||
### Unknown / unset goal
|
||||
|
||||
When `user.goal` is `null` or an unrecognised value, the ring always shows Blue `#007AFF`
|
||||
(the app's primary colour) with no semantic judgement applied.
|
||||
|
||||
---
|
||||
|
||||
## Overflow arc
|
||||
|
||||
When `loggedCalories > dailyGoal` (progress > 100%), the ring is visually "full" and a
|
||||
second overlapping arc is drawn from the start point (top/12 o'clock) clockwise to
|
||||
`(progress − 1.0) × 360°`. This overflow arc is rendered:
|
||||
|
||||
- Same colour as the main arc (which has already shifted to warning/error colour)
|
||||
- `strokeWidth` reduced to 6 px (vs. 10 px for the main arc) for visual distinction
|
||||
- Opacity 70% to distinguish it from the primary stroke
|
||||
|
||||
The text inside the ring switches from "осталось X" to "+X перебор" when in overflow state.
|
||||
|
||||
---
|
||||
|
||||
## Implementation reference
|
||||
|
||||
| Symbol | Location |
|
||||
|---|---|
|
||||
| `_ringColorFor(double rawProgress, String? goalType)` | `client/lib/features/home/home_screen.dart` |
|
||||
| `AppColors.primary` (`#007AFF`) | `client/lib/core/theme/app_colors.dart` |
|
||||
| `AppColors.success` (`#34C759`) | `client/lib/core/theme/app_colors.dart` |
|
||||
| `AppColors.warning` (`#FF9500`) | `client/lib/core/theme/app_colors.dart` |
|
||||
| `AppColors.error` (`#FF3B30`) | `client/lib/core/theme/app_colors.dart` |
|
||||
Reference in New Issue
Block a user