Your avatar is your photo, picked from the device by tapping it on Home or from a new Profile photo row in Settings, shrunk to 256 px and kept on the phone (there is no upload endpoint yet). Group members show the photo the server sends in a new `photo` field, everywhere avatars appear. Without a photo, an initial on a neutral disc. Mock mode bundles free Unsplash portraits for the sample people. 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;
|
|
}
|
|
}
|