Initial commit: Fixes for linting and compilation
This commit is contained in:
195
lib/widgets/cycle_ring.dart
Normal file
195
lib/widgets/cycle_ring.dart
Normal file
@@ -0,0 +1,195 @@
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user