import 'dart:math' as math; import 'package:flutter/material.dart'; import '../about/internal/application/TextType.dart'; import '../utils/Colors.dart'; import 'text/Text.dart'; /// A thin circular meter with its value in the middle — the routine row's /// "how far did I get" dial, and the runner's phase clock ring. class ProgressRing extends StatelessWidget { final double fraction; final double size; final double stroke; final Color? fill; final Color? track; /// Drawn in the centre. Defaults to the percentage. final Widget? child; const ProgressRing({ super.key, required this.fraction, this.size = 44, this.stroke = 3, this.fill, this.track, this.child, }); @override Widget build(BuildContext context) { final double clamped = fraction.isNaN ? 0 : fraction.clamp(0, 1).toDouble(); return SizedBox( width: size, height: size, child: TweenAnimationBuilder( tween: Tween(end: clamped), duration: const Duration(milliseconds: 450), curve: Curves.easeOutCubic, builder: (context, value, _) { return CustomPaint( painter: _RingPainter( value, stroke, fill ?? colorPrimaryDark, track ?? colorMuted, ), child: Center( child: child ?? text("${(clamped * 100).round()}%", size * 0.22, TextType.Bold, color: colorPrimaryDark), ), ); }, ), ); } } class _RingPainter extends CustomPainter { final double fraction; final double stroke; final Color fill; final Color track; _RingPainter(this.fraction, this.stroke, this.fill, this.track); @override void paint(Canvas canvas, Size size) { final Offset centre = Offset(size.width / 2, size.height / 2); final double radius = (math.min(size.width, size.height) - stroke) / 2; final Paint base = Paint() ..style = PaintingStyle.stroke ..strokeWidth = stroke ..color = track; canvas.drawCircle(centre, radius, base); if (fraction <= 0) { return; } final Paint arc = Paint() ..style = PaintingStyle.stroke ..strokeWidth = stroke ..strokeCap = StrokeCap.round ..color = fill; canvas.drawArc( Rect.fromCircle(center: centre, radius: radius), -math.pi / 2, 2 * math.pi * fraction, false, arc, ); } @override bool shouldRepaint(_RingPainter old) { return old.fraction != fraction || old.fill != fill || old.track != track || old.stroke != stroke; } }