feat: Implement husband features and fix iOS Safari web startup

Implement initial features for husband's companion app, including mock data
service and husband notes screen. Refactor scripture and cycle services
for improved stability and testability. Address iOS Safari web app
startup issue by removing deprecated initialization.

- Implemented MockDataService and HusbandNotesScreen.
- Converted _DashboardTab and DevotionalScreen to StatefulWidgets for robust
  scripture provider initialization.
- Refactored CycleService to use immutable CycleInfo class, reducing UI rebuilds.
- Removed deprecated window.flutterConfiguration from index.html, resolving
  Flutter web app startup failure on iOS Safari.
- Updated and fixed related tests.
This commit is contained in:
2025-12-26 22:40:52 -06:00
parent 464692ce56
commit b4b2bfe749
47 changed files with 240110 additions and 2578 deletions

View File

@@ -4,11 +4,7 @@ import 'dart:math' as math;
import '../theme/app_theme.dart';
import '../models/cycle_entry.dart';
<<<<<<< HEAD
class CycleRing extends StatelessWidget {
=======
class CycleRing extends StatefulWidget {
>>>>>>> 6742220 (Your commit message here)
final int dayOfCycle;
final int totalDays;
final CyclePhase phase;
@@ -21,77 +17,11 @@ class CycleRing extends StatefulWidget {
});
@override
<<<<<<< HEAD
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,
=======
State<CycleRing> createState() => _CycleRingState();
}
class _CycleRingState extends State<CycleRing> with SingleTickerProviderStateMixin {
class _CycleRingState extends State<CycleRing>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
@@ -125,7 +55,7 @@ class _CycleRingState extends State<CycleRing> with SingleTickerProviderStateMix
animation: _animation,
builder: (context, child) {
final currentProgress = targetProgress * _animation.value;
return SizedBox(
width: 220,
height: 220,
@@ -141,7 +71,7 @@ class _CycleRingState extends State<CycleRing> with SingleTickerProviderStateMix
isDark: isDark,
),
),
// Center content with scale and fade animation
Transform.scale(
scale: 0.8 + (0.2 * _animation.value),
@@ -152,19 +82,28 @@ class _CycleRingState extends State<CycleRing> with SingleTickerProviderStateMix
children: [
Text(
'Day ${widget.dayOfCycle}',
style: Theme.of(context).textTheme.displayMedium?.copyWith(
fontSize: 32,
fontWeight: FontWeight.w600,
color: Theme.of(context).colorScheme.onSurface,
),
style: Theme.of(context)
.textTheme
.displayMedium
?.copyWith(
fontSize: 32,
fontWeight: FontWeight.w600,
color: Theme.of(context).colorScheme.onSurface,
),
),
const SizedBox(height: 4),
Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
padding: const EdgeInsets.symmetric(
horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: _getPhaseColor(widget.phase).withOpacity(isDark ? 0.3 : 0.2),
color: _getPhaseColor(widget.phase)
.withOpacity(isDark ? 0.3 : 0.2),
borderRadius: BorderRadius.circular(20),
border: isDark ? Border.all(color: _getPhaseColor(widget.phase).withOpacity(0.5)) : null,
border: isDark
? Border.all(
color: _getPhaseColor(widget.phase)
.withOpacity(0.5))
: null,
),
child: Row(
mainAxisSize: MainAxisSize.min,
@@ -179,7 +118,9 @@ class _CycleRingState extends State<CycleRing> with SingleTickerProviderStateMix
style: GoogleFonts.outfit(
fontSize: 14,
fontWeight: FontWeight.w500,
color: isDark ? Colors.white : _getPhaseColor(widget.phase),
color: isDark
? Colors.white
: _getPhaseColor(widget.phase),
),
),
],
@@ -191,24 +132,19 @@ class _CycleRingState extends State<CycleRing> with SingleTickerProviderStateMix
? '$daysUntilNextPeriod days until period'
: 'Period expected',
style: Theme.of(context).textTheme.bodySmall?.copyWith(
fontSize: 12,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
fontSize: 12,
color:
Theme.of(context).colorScheme.onSurfaceVariant,
),
),
],
),
>>>>>>> 6742220 (Your commit message here)
),
),
],
),
<<<<<<< HEAD
],
),
=======
);
},
>>>>>>> 6742220 (Your commit message here)
);
}
@@ -229,18 +165,13 @@ class _CycleRingState extends State<CycleRing> with SingleTickerProviderStateMix
class _CycleRingPainter extends CustomPainter {
final double progress;
final CyclePhase phase;
<<<<<<< HEAD
_CycleRingPainter({required this.progress, required this.phase});
=======
final bool isDark;
_CycleRingPainter({
required this.progress,
required this.progress,
required this.phase,
required this.isDark,
});
>>>>>>> 6742220 (Your commit message here)
@override
void paint(Canvas canvas, Size size) {
@@ -250,11 +181,8 @@ class _CycleRingPainter extends CustomPainter {
// Background arc
final bgPaint = Paint()
<<<<<<< HEAD
..color = AppColors.lightGray.withOpacity(0.2)
=======
..color = (isDark ? Colors.white : AppColors.lightGray).withOpacity(isDark ? 0.05 : 0.1)
>>>>>>> 6742220 (Your commit message here)
..color =
(isDark ? Colors.white : AppColors.lightGray).withOpacity(isDark ? 0.05 : 0.1)
..style = PaintingStyle.stroke
..strokeWidth = strokeWidth
..strokeCap = StrokeCap.round;
@@ -287,11 +215,7 @@ class _CycleRingPainter extends CustomPainter {
final dotY = center.dy + radius * math.sin(dotAngle);
final dotPaint = Paint()
<<<<<<< HEAD
..color = Colors.white
=======
..color = isDark ? const Color(0xFF1E1E1E) : Colors.white
>>>>>>> 6742220 (Your commit message here)
..style = PaintingStyle.fill;
final dotBorderPaint = Paint()
@@ -308,11 +232,19 @@ class _CycleRingPainter extends CustomPainter {
case CyclePhase.menstrual:
return [AppColors.rose, AppColors.menstrualPhase, AppColors.blushPink];
case CyclePhase.follicular:
return [AppColors.sageGreen, AppColors.follicularPhase, AppColors.sageGreen.withOpacity(0.7)];
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];
return [
AppColors.lutealPhase,
AppColors.lavender,
AppColors.blushPink
];
}
}