Routines: workout, meditation and must-do routines with timed, rep and tick steps, sets and rest. A library screen, a builder with templates and drag-to-reorder steps, and a full-screen player that auto-advances timed phases, counts backgrounding and logs broken chains as partial. Goals: a goal editor (deadline, colour, category, default class, archive), routines linked to goals, and a goal field on tasks and routines. Tasks added from inside a goal are now actually attached to it via parentId. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KhQh614DMeYoQEH9SoMRUw
117 lines
2.7 KiB
Dart
117 lines
2.7 KiB
Dart
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<double>(
|
|
tween: Tween<double>(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;
|
|
}
|
|
}
|