import 'dart:math' as math; import 'package:flutter/material.dart'; import '../../../../core/theme/app_colors.dart'; /// Non-anxiety-inducing timer widget. /// /// Design rules: /// - Circular progress (NOT countdown — progress forward). /// - Soft pulsing animation. /// - Color stays calm (teal/blue). /// - No alarming sounds or red colors. /// - Shows "X min so far" not "X min remaining." class GentleTimer extends StatefulWidget { /// Total elapsed seconds. final int elapsedSeconds; /// Optional estimated total in seconds (used only for the progress ring). final int? estimatedTotalSeconds; /// Widget size (width & height). final double size; const GentleTimer({ super.key, required this.elapsedSeconds, this.estimatedTotalSeconds, this.size = 180, }); @override State createState() => _GentleTimerState(); } class _GentleTimerState extends State with SingleTickerProviderStateMixin { late final AnimationController _pulseController; late final Animation _pulse; @override void initState() { super.initState(); _pulseController = AnimationController( vsync: this, duration: const Duration(seconds: 2), )..repeat(reverse: true); _pulse = Tween(begin: 0.96, end: 1.0).animate( CurvedAnimation(parent: _pulseController, curve: Curves.easeInOut), ); } @override void dispose() { _pulseController.dispose(); super.dispose(); } String _formatElapsed() { final m = widget.elapsedSeconds ~/ 60; final s = widget.elapsedSeconds % 60; if (m == 0) return '${s}s so far'; return '${m}m ${s.toString().padLeft(2, '0')}s so far'; } double get _progress { final total = widget.estimatedTotalSeconds; if (total == null || total == 0) { // No estimate — use a slow modular fill so the ring still moves. return (widget.elapsedSeconds % 300) / 300; } return (widget.elapsedSeconds / total).clamp(0.0, 1.0); } @override Widget build(BuildContext context) { final theme = Theme.of(context); return AnimatedBuilder( animation: _pulse, builder: (context, child) { return Transform.scale( scale: _pulse.value, child: child, ); }, child: SizedBox( width: widget.size, height: widget.size, child: CustomPaint( painter: _GentleTimerPainter( progress: _progress, color: AppColors.primary, backgroundColor: AppColors.primary.withAlpha(25), ), child: Center( child: Text( _formatElapsed(), style: theme.textTheme.titleMedium?.copyWith( fontWeight: FontWeight.w600, color: AppColors.primary, ), textAlign: TextAlign.center, ), ), ), ), ); } } class _GentleTimerPainter extends CustomPainter { final double progress; final Color color; final Color backgroundColor; _GentleTimerPainter({ required this.progress, required this.color, required this.backgroundColor, }); @override void paint(Canvas canvas, Size size) { final center = Offset(size.width / 2, size.height / 2); final radius = (size.shortestSide / 2) - 10; const strokeWidth = 10.0; const startAngle = -math.pi / 2; // Background circle canvas.drawCircle( center, radius, Paint() ..color = backgroundColor ..style = PaintingStyle.stroke ..strokeWidth = strokeWidth ..strokeCap = StrokeCap.round, ); // Progress arc if (progress > 0) { canvas.drawArc( Rect.fromCircle(center: center, radius: radius), startAngle, 2 * math.pi * progress, false, Paint() ..color = color ..style = PaintingStyle.stroke ..strokeWidth = strokeWidth ..strokeCap = StrokeCap.round, ); } } @override bool shouldRepaint(covariant _GentleTimerPainter oldDelegate) => progress != oldDelegate.progress; }