import 'dart:math'; import 'package:flutter/material.dart'; class ProgressRing extends StatelessWidget { final double progress; // 0.0 to 1.0 final double size; final double strokeWidth; final Color backgroundColor; final Color progressColor; final Widget? child; const ProgressRing({ super.key, required this.progress, this.size = 120, this.strokeWidth = 12, this.backgroundColor = Colors.grey, this.progressColor = Colors.blue, this.child, }); @override Widget build(BuildContext context) { return SizedBox( width: size, height: size, child: CustomPaint( painter: _ProgressRingPainter( progress: progress, strokeWidth: strokeWidth, backgroundColor: backgroundColor, progressColor: progressColor, ), child: child != null ? Center(child: child) : Center( child: Text( '${(progress * 100).toStringAsFixed(0)}%', style: TextStyle( fontSize: size / 5, fontWeight: FontWeight.bold, color: Colors.grey.shade800, ), ), ), ), ); } } class _ProgressRingPainter extends CustomPainter { final double progress; final double strokeWidth; final Color backgroundColor; final Color progressColor; _ProgressRingPainter({ required this.progress, required this.strokeWidth, required this.backgroundColor, required this.progressColor, }); @override void paint(Canvas canvas, Size size) { final center = Offset(size.width / 2, size.height / 2); final radius = (size.width - strokeWidth) / 2; // Background circle final backgroundPaint = Paint() ..color = backgroundColor.withAlpha(120) ..style = PaintingStyle.stroke ..strokeWidth = strokeWidth ..strokeCap = StrokeCap.round; canvas.drawCircle(center, radius, backgroundPaint); // Progress arc final progressPaint = Paint() ..shader = LinearGradient( colors: [ progressColor, progressColor.withAlpha(170), ], ).createShader(Rect.fromCircle(center: center, radius: radius)) ..style = PaintingStyle.stroke ..strokeWidth = strokeWidth ..strokeCap = StrokeCap.round; final sweepAngle = 2 * pi * progress; canvas.drawArc( Rect.fromCircle(center: center, radius: radius), -pi / 2, // Start from top sweepAngle, false, progressPaint, ); } @override bool shouldRepaint(covariant CustomPainter oldDelegate) => true; }