Files
Tracker/lib/widgets/cycle_ring.dart

196 lines
5.7 KiB
Dart

import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'dart:math' as math;
import '../theme/app_theme.dart';
import '../models/cycle_entry.dart';
class CycleRing extends StatelessWidget {
final int dayOfCycle;
final int totalDays;
final CyclePhase phase;
const CycleRing({
super.key,
required this.dayOfCycle,
required this.totalDays,
required this.phase,
});
@override
Widget build(BuildContext context) {
final progress = dayOfCycle / totalDays;
final daysUntilNextPeriod = totalDays - dayOfCycle;
return Container(
width: 220,
height: 220,
child: Stack(
alignment: Alignment.center,
children: [
// Background ring
CustomPaint(
size: const Size(220, 220),
painter: _CycleRingPainter(
progress: progress,
phase: phase,
),
),
// Center content
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Day $dayOfCycle',
style: GoogleFonts.outfit(
fontSize: 32,
fontWeight: FontWeight.w600,
color: AppColors.charcoal,
),
),
const SizedBox(height: 4),
Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: _getPhaseColor(phase).withOpacity(0.2),
borderRadius: BorderRadius.circular(20),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
phase.emoji,
style: const TextStyle(fontSize: 14),
),
const SizedBox(width: 6),
Text(
phase.label,
style: GoogleFonts.outfit(
fontSize: 14,
fontWeight: FontWeight.w500,
color: _getPhaseColor(phase),
),
),
],
),
),
const SizedBox(height: 8),
Text(
daysUntilNextPeriod > 0
? '$daysUntilNextPeriod days until period'
: 'Period expected',
style: GoogleFonts.outfit(
fontSize: 12,
color: AppColors.warmGray,
),
),
],
),
],
),
);
}
Color _getPhaseColor(CyclePhase phase) {
switch (phase) {
case CyclePhase.menstrual:
return AppColors.menstrualPhase;
case CyclePhase.follicular:
return AppColors.follicularPhase;
case CyclePhase.ovulation:
return AppColors.ovulationPhase;
case CyclePhase.luteal:
return AppColors.lutealPhase;
}
}
}
class _CycleRingPainter extends CustomPainter {
final double progress;
final CyclePhase phase;
_CycleRingPainter({required this.progress, required this.phase});
@override
void paint(Canvas canvas, Size size) {
final center = Offset(size.width / 2, size.height / 2);
final radius = size.width / 2 - 15;
const strokeWidth = 12.0;
// Background arc
final bgPaint = Paint()
..color = AppColors.lightGray.withOpacity(0.2)
..style = PaintingStyle.stroke
..strokeWidth = strokeWidth
..strokeCap = StrokeCap.round;
canvas.drawCircle(center, radius, bgPaint);
// Progress arc
final progressPaint = Paint()
..shader = SweepGradient(
startAngle: -math.pi / 2,
endAngle: math.pi * 1.5,
colors: _getGradientColors(phase),
stops: const [0.0, 0.5, 1.0],
).createShader(Rect.fromCircle(center: center, radius: radius))
..style = PaintingStyle.stroke
..strokeWidth = strokeWidth
..strokeCap = StrokeCap.round;
canvas.drawArc(
Rect.fromCircle(center: center, radius: radius),
-math.pi / 2,
2 * math.pi * progress,
false,
progressPaint,
);
// Dot at current position
final dotAngle = -math.pi / 2 + 2 * math.pi * progress;
final dotX = center.dx + radius * math.cos(dotAngle);
final dotY = center.dy + radius * math.sin(dotAngle);
final dotPaint = Paint()
..color = Colors.white
..style = PaintingStyle.fill;
final dotBorderPaint = Paint()
..color = _getPhaseColor(phase)
..style = PaintingStyle.stroke
..strokeWidth = 3;
canvas.drawCircle(Offset(dotX, dotY), 8, dotPaint);
canvas.drawCircle(Offset(dotX, dotY), 8, dotBorderPaint);
}
List<Color> _getGradientColors(CyclePhase phase) {
switch (phase) {
case CyclePhase.menstrual:
return [AppColors.rose, AppColors.menstrualPhase, AppColors.blushPink];
case CyclePhase.follicular:
return [AppColors.sageGreen, AppColors.follicularPhase, AppColors.sageGreen.withOpacity(0.7)];
case CyclePhase.ovulation:
return [AppColors.lavender, AppColors.ovulationPhase, AppColors.rose];
case CyclePhase.luteal:
return [AppColors.lutealPhase, AppColors.lavender, AppColors.blushPink];
}
}
Color _getPhaseColor(CyclePhase phase) {
switch (phase) {
case CyclePhase.menstrual:
return AppColors.menstrualPhase;
case CyclePhase.follicular:
return AppColors.follicularPhase;
case CyclePhase.ovulation:
return AppColors.ovulationPhase;
case CyclePhase.luteal:
return AppColors.lutealPhase;
}
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}