screen-locker/stronglift_replacement/workout_app/lib/widgets/rep_circle.dart
Krzysztof kuhy Rudnicki 323e3cd02a
Some checks failed
pre-commit / pre-commit (push) Has been cancelled
Tests / test (3.10) (push) Has been cancelled
Tests / test (3.11) (push) Has been cancelled
Tests / test (3.12) (push) Has been cancelled
workout_app: warm-tint the unified-design-system palette
Replaces the dead ColorScheme.fromSeed(Colors.indigo) (declared but
never consumed by any screen/widget) with theme.dart: an explicit
dark ColorScheme plus a real AppStatusColors extension, since this
app genuinely uses success/warning status colors throughout (unlike
dufs-cloud's app). Routes all 10 screen/widget files' ~280 hardcoded
Colors.*/fontSize literals through it. Fixes the two flagged
DESIGN_AUDIT_TODO violations: home_screen's Resume/Start buttons now
use a real 2x horizontal:vertical padding ratio, and rep_circle's
BoxShadow (the one shadow in an otherwise flat dark UI) is removed.
2026-07-22 21:08:45 +02:00

124 lines
3.5 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/// Tappable circle widget representing one set of an exercise.
///
/// States:
/// neutral – white, shows target reps, not yet acted upon
/// success – green, shows target reps (full set done)
/// partial – orange, shows how many reps were actually done (< target)
/// failed – red, shows 0 (all reps deducted)
///
/// Interaction:
/// single tap → neutral→success, success→partial(-1 rep),
/// partial→partial(-1 rep), failed stays failed
/// long press → reset to neutral
library;
import 'package:flutter/material.dart';
import 'package:workout_app/ui/theme.dart';
/// Visual state of a [RepCircle].
enum RepCircleState {
/// Not yet tapped; shows target reps.
neutral,
/// All reps completed; green.
success,
/// Some reps completed; orange, shows actual count.
partial,
/// All reps deducted; red.
failed,
}
/// Tappable circle representing one working set of an exercise.
class RepCircle extends StatelessWidget {
/// Creates a [RepCircle].
const RepCircle({
required this.targetReps,
required this.doneReps,
required this.tapped,
required this.onTap,
required this.onLongPress,
super.key,
});
/// Number of reps the user is aiming for this set.
final int targetReps;
/// Reps currently registered (may be < targetReps after repeated taps).
final int doneReps;
/// Whether this circle has been tapped at all (neutral vs success).
final bool tapped;
/// Called on a single tap.
final VoidCallback onTap;
/// Called on a long press (resets to neutral).
final VoidCallback onLongPress;
RepCircleState get _state {
if (!tapped) return RepCircleState.neutral;
if (doneReps >= targetReps) return RepCircleState.success;
if (doneReps > 0) return RepCircleState.partial;
return RepCircleState.failed;
}
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
final status = Theme.of(context).extension<AppStatusColors>()!;
final state = _state;
// on-fill: this circle is a solid filled surface — text on it always
// needs dark (ink) text, never the page's near-white text-on-dark.
final fg = colorScheme.onPrimary;
final Color bg;
final String label;
switch (state) {
case RepCircleState.neutral:
// Not yet acted upon — a near-white fill (not pure Colors.white)
// still reads as "the bright/blank one" against the dark page.
bg = colorScheme.onSurface;
label = '$targetReps';
case RepCircleState.success:
bg = status.success;
label = '$targetReps';
case RepCircleState.partial:
bg = status.warning;
label = '$doneReps';
case RepCircleState.failed:
bg = colorScheme.error;
label = '0';
}
return GestureDetector(
onTap: onTap,
onLongPress: onLongPress,
child: Container(
width: 52,
height: 52,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: bg,
border: Border.all(
color: state == RepCircleState.neutral ? colorScheme.outline : bg,
width: 2,
),
// No shadow (rule 26: dark surfaces never shadow) — the fill
// color already differentiates this circle from the page.
),
alignment: Alignment.center,
child: Text(
label,
style: TextStyle(
color: fg,
fontWeight: FontWeight.bold,
fontSize: AppTextSize.body,
),
),
),
);
}
}