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

@@ -1,9 +1,7 @@
import 'package:hive/hive.dart';
<<<<<<< HEAD
=======
import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import '../theme/app_theme.dart';
>>>>>>> 6742220 (Your commit message here)
part 'cycle_entry.g.dart';
@@ -12,16 +10,16 @@ part 'cycle_entry.g.dart';
enum MoodLevel {
@HiveField(0)
verySad,
@HiveField(1)
sad,
@HiveField(2)
neutral,
@HiveField(3)
happy,
@HiveField(4)
veryHappy,
}
@@ -31,13 +29,13 @@ enum MoodLevel {
enum FlowIntensity {
@HiveField(0)
spotting,
@HiveField(1)
light,
@HiveField(2)
medium,
@HiveField(3)
heavy,
}
@@ -47,16 +45,16 @@ enum FlowIntensity {
enum CervicalMucusType {
@HiveField(0)
dry,
@HiveField(1)
sticky,
@HiveField(2)
creamy,
@HiveField(3)
eggWhite,
@HiveField(4)
watery,
}
@@ -66,13 +64,13 @@ enum CervicalMucusType {
enum CyclePhase {
@HiveField(0)
menstrual,
@HiveField(1)
follicular,
@HiveField(2)
ovulation,
@HiveField(3)
luteal,
}
@@ -82,88 +80,94 @@ enum CyclePhase {
class CycleEntry extends HiveObject {
@HiveField(0)
String id;
@HiveField(1)
DateTime date;
@HiveField(2)
bool isPeriodDay;
@HiveField(3)
FlowIntensity? flowIntensity;
@HiveField(4)
MoodLevel? mood;
@HiveField(5)
int? energyLevel; // 1-5
@HiveField(6)
int? crampIntensity; // 1-5
@HiveField(7)
bool hasHeadache;
@HiveField(8)
bool hasBloating;
@HiveField(9)
bool hasBreastTenderness;
@HiveField(10)
bool hasFatigue;
@HiveField(11)
bool hasAcne;
<<<<<<< HEAD
=======
@HiveField(22)
bool hasLowerBackPain;
@HiveField(23)
bool hasConstipation;
@HiveField(24)
bool hasDiarrhea;
@HiveField(25)
int? stressLevel; // 1-5
@HiveField(26)
bool hasInsomnia;
>>>>>>> 6742220 (Your commit message here)
@HiveField(12)
double? basalBodyTemperature; // in Fahrenheit
@HiveField(13)
CervicalMucusType? cervicalMucus;
@HiveField(14)
bool? ovulationTestPositive;
@HiveField(15)
String? notes;
@HiveField(16)
int? sleepHours;
@HiveField(17)
int? waterIntake; // glasses
@HiveField(18)
bool hadExercise;
@HiveField(19)
bool hadIntimacy; // For married users only
@HiveField(20)
DateTime createdAt;
@HiveField(21)
DateTime updatedAt;
@HiveField(27)
List<String>? cravings;
@HiveField(28)
String? husbandNotes; // Separate notes for husband
@HiveField(29)
bool? intimacyProtected; // null = no intimacy, true = protected, false = unprotected
CycleEntry({
required this.id,
required this.date,
@@ -177,71 +181,54 @@ class CycleEntry extends HiveObject {
this.hasBreastTenderness = false,
this.hasFatigue = false,
this.hasAcne = false,
<<<<<<< HEAD
=======
this.hasLowerBackPain = false,
this.hasConstipation = false,
this.hasDiarrhea = false,
this.stressLevel,
this.hasInsomnia = false,
>>>>>>> 6742220 (Your commit message here)
this.basalBodyTemperature,
this.cervicalMucus,
this.ovulationTestPositive,
this.notes,
this.cravings,
this.sleepHours,
this.waterIntake,
this.hadExercise = false,
this.hadIntimacy = false,
this.intimacyProtected,
required this.createdAt,
required this.updatedAt,
this.husbandNotes,
});
List<bool> get _symptomsList => [
hasHeadache,
hasBloating,
hasBreastTenderness,
hasFatigue,
hasAcne,
hasLowerBackPain,
hasConstipation,
hasDiarrhea,
hasInsomnia,
(crampIntensity != null && crampIntensity! > 0),
(stressLevel != null && stressLevel! > 1),
];
/// Check if any symptoms are logged
bool get hasSymptoms =>
hasHeadache ||
hasBloating ||
hasBreastTenderness ||
hasFatigue ||
hasAcne ||
<<<<<<< HEAD
(crampIntensity != null && crampIntensity! > 0);
=======
hasLowerBackPain ||
hasConstipation ||
hasDiarrhea ||
hasInsomnia ||
(crampIntensity != null && crampIntensity! > 0) ||
(stressLevel != null && stressLevel! > 1);
>>>>>>> 6742220 (Your commit message here)
bool get hasSymptoms => _symptomsList.contains(true);
/// Check if NFP data is logged
bool get hasNFPData =>
basalBodyTemperature != null ||
cervicalMucus != null ||
ovulationTestPositive != null;
/// Get symptom count
int get symptomCount {
int count = 0;
if (hasHeadache) count++;
if (hasBloating) count++;
if (hasBreastTenderness) count++;
if (hasFatigue) count++;
if (hasAcne) count++;
<<<<<<< HEAD
if (crampIntensity != null && crampIntensity! > 0) count++;
=======
if (hasLowerBackPain) count++;
if (hasConstipation) count++;
if (hasDiarrhea) count++;
if (hasInsomnia) count++;
if (crampIntensity != null && crampIntensity! > 0) count++;
if (stressLevel != null && stressLevel! > 1) count++;
>>>>>>> 6742220 (Your commit message here)
return count;
}
int get symptomCount => _symptomsList.where((s) => s).length;
// ... (omitted getters)
/// Copy with updated fields
CycleEntry copyWith({
String? id,
@@ -256,24 +243,24 @@ class CycleEntry extends HiveObject {
bool? hasBreastTenderness,
bool? hasFatigue,
bool? hasAcne,
<<<<<<< HEAD
=======
bool? hasLowerBackPain,
bool? hasConstipation,
bool? hasDiarrhea,
int? stressLevel,
bool? hasInsomnia,
>>>>>>> 6742220 (Your commit message here)
double? basalBodyTemperature,
CervicalMucusType? cervicalMucus,
bool? ovulationTestPositive,
String? notes,
List<String>? cravings,
int? sleepHours,
int? waterIntake,
bool? hadExercise,
bool? hadIntimacy,
bool? intimacyProtected,
DateTime? createdAt,
DateTime? updatedAt,
String? husbandNotes,
}) {
return CycleEntry(
id: id ?? this.id,
@@ -288,24 +275,24 @@ class CycleEntry extends HiveObject {
hasBreastTenderness: hasBreastTenderness ?? this.hasBreastTenderness,
hasFatigue: hasFatigue ?? this.hasFatigue,
hasAcne: hasAcne ?? this.hasAcne,
<<<<<<< HEAD
=======
hasLowerBackPain: hasLowerBackPain ?? this.hasLowerBackPain,
hasConstipation: hasConstipation ?? this.hasConstipation,
hasDiarrhea: hasDiarrhea ?? this.hasDiarrhea,
stressLevel: stressLevel ?? this.stressLevel,
hasInsomnia: hasInsomnia ?? this.hasInsomnia,
>>>>>>> 6742220 (Your commit message here)
basalBodyTemperature: basalBodyTemperature ?? this.basalBodyTemperature,
cervicalMucus: cervicalMucus ?? this.cervicalMucus,
ovulationTestPositive: ovulationTestPositive ?? this.ovulationTestPositive,
notes: notes ?? this.notes,
cravings: cravings ?? this.cravings,
sleepHours: sleepHours ?? this.sleepHours,
waterIntake: waterIntake ?? this.waterIntake,
hadExercise: hadExercise ?? this.hadExercise,
hadIntimacy: hadIntimacy ?? this.hadIntimacy,
intimacyProtected: intimacyProtected ?? this.intimacyProtected,
createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? DateTime.now(),
husbandNotes: husbandNotes ?? this.husbandNotes,
);
}
}
@@ -326,7 +313,7 @@ extension MoodLevelExtension on MoodLevel {
return '😄';
}
}
String get label {
switch (this) {
case MoodLevel.verySad:
@@ -371,7 +358,7 @@ extension CyclePhaseExtension on CyclePhase {
return 'Luteal';
}
}
String get emoji {
switch (this) {
case CyclePhase.menstrual:
@@ -384,8 +371,6 @@ extension CyclePhaseExtension on CyclePhase {
return '🌙';
}
}
<<<<<<< HEAD
=======
Color get color {
switch (this) {
@@ -405,15 +390,22 @@ extension CyclePhaseExtension on CyclePhase {
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
];
}
}
>>>>>>> 6742220 (Your commit message here)
String get description {
switch (this) {
case CyclePhase.menstrual: