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.
422 lines
9.2 KiB
Dart
422 lines
9.2 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:hive/hive.dart';
|
|
|
|
import '../theme/app_theme.dart';
|
|
|
|
part 'cycle_entry.g.dart';
|
|
|
|
/// Mood levels for tracking
|
|
@HiveType(typeId: 3)
|
|
enum MoodLevel {
|
|
@HiveField(0)
|
|
verySad,
|
|
|
|
@HiveField(1)
|
|
sad,
|
|
|
|
@HiveField(2)
|
|
neutral,
|
|
|
|
@HiveField(3)
|
|
happy,
|
|
|
|
@HiveField(4)
|
|
veryHappy,
|
|
}
|
|
|
|
/// Flow intensity for period days
|
|
@HiveType(typeId: 4)
|
|
enum FlowIntensity {
|
|
@HiveField(0)
|
|
spotting,
|
|
|
|
@HiveField(1)
|
|
light,
|
|
|
|
@HiveField(2)
|
|
medium,
|
|
|
|
@HiveField(3)
|
|
heavy,
|
|
}
|
|
|
|
/// Cervical mucus type for NFP tracking
|
|
@HiveType(typeId: 5)
|
|
enum CervicalMucusType {
|
|
@HiveField(0)
|
|
dry,
|
|
|
|
@HiveField(1)
|
|
sticky,
|
|
|
|
@HiveField(2)
|
|
creamy,
|
|
|
|
@HiveField(3)
|
|
eggWhite,
|
|
|
|
@HiveField(4)
|
|
watery,
|
|
}
|
|
|
|
/// Cycle phase
|
|
@HiveType(typeId: 6)
|
|
enum CyclePhase {
|
|
@HiveField(0)
|
|
menstrual,
|
|
|
|
@HiveField(1)
|
|
follicular,
|
|
|
|
@HiveField(2)
|
|
ovulation,
|
|
|
|
@HiveField(3)
|
|
luteal,
|
|
}
|
|
|
|
/// Daily cycle entry
|
|
@HiveType(typeId: 7)
|
|
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;
|
|
|
|
@HiveField(22)
|
|
bool hasLowerBackPain;
|
|
|
|
@HiveField(23)
|
|
bool hasConstipation;
|
|
|
|
@HiveField(24)
|
|
bool hasDiarrhea;
|
|
|
|
@HiveField(25)
|
|
int? stressLevel; // 1-5
|
|
|
|
@HiveField(26)
|
|
bool hasInsomnia;
|
|
|
|
@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,
|
|
this.isPeriodDay = false,
|
|
this.flowIntensity,
|
|
this.mood,
|
|
this.energyLevel,
|
|
this.crampIntensity,
|
|
this.hasHeadache = false,
|
|
this.hasBloating = false,
|
|
this.hasBreastTenderness = false,
|
|
this.hasFatigue = false,
|
|
this.hasAcne = false,
|
|
this.hasLowerBackPain = false,
|
|
this.hasConstipation = false,
|
|
this.hasDiarrhea = false,
|
|
this.stressLevel,
|
|
this.hasInsomnia = false,
|
|
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 => _symptomsList.contains(true);
|
|
|
|
/// Check if NFP data is logged
|
|
bool get hasNFPData =>
|
|
basalBodyTemperature != null ||
|
|
cervicalMucus != null ||
|
|
ovulationTestPositive != null;
|
|
|
|
/// Get symptom count
|
|
int get symptomCount => _symptomsList.where((s) => s).length;
|
|
|
|
// ... (omitted getters)
|
|
|
|
/// Copy with updated fields
|
|
CycleEntry copyWith({
|
|
String? id,
|
|
DateTime? date,
|
|
bool? isPeriodDay,
|
|
FlowIntensity? flowIntensity,
|
|
MoodLevel? mood,
|
|
int? energyLevel,
|
|
int? crampIntensity,
|
|
bool? hasHeadache,
|
|
bool? hasBloating,
|
|
bool? hasBreastTenderness,
|
|
bool? hasFatigue,
|
|
bool? hasAcne,
|
|
bool? hasLowerBackPain,
|
|
bool? hasConstipation,
|
|
bool? hasDiarrhea,
|
|
int? stressLevel,
|
|
bool? hasInsomnia,
|
|
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,
|
|
date: date ?? this.date,
|
|
isPeriodDay: isPeriodDay ?? this.isPeriodDay,
|
|
flowIntensity: flowIntensity ?? this.flowIntensity,
|
|
mood: mood ?? this.mood,
|
|
energyLevel: energyLevel ?? this.energyLevel,
|
|
crampIntensity: crampIntensity ?? this.crampIntensity,
|
|
hasHeadache: hasHeadache ?? this.hasHeadache,
|
|
hasBloating: hasBloating ?? this.hasBloating,
|
|
hasBreastTenderness: hasBreastTenderness ?? this.hasBreastTenderness,
|
|
hasFatigue: hasFatigue ?? this.hasFatigue,
|
|
hasAcne: hasAcne ?? this.hasAcne,
|
|
hasLowerBackPain: hasLowerBackPain ?? this.hasLowerBackPain,
|
|
hasConstipation: hasConstipation ?? this.hasConstipation,
|
|
hasDiarrhea: hasDiarrhea ?? this.hasDiarrhea,
|
|
stressLevel: stressLevel ?? this.stressLevel,
|
|
hasInsomnia: hasInsomnia ?? this.hasInsomnia,
|
|
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,
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Extension to get display string for enums
|
|
extension MoodLevelExtension on MoodLevel {
|
|
String get emoji {
|
|
switch (this) {
|
|
case MoodLevel.verySad:
|
|
return '😢';
|
|
case MoodLevel.sad:
|
|
return '😕';
|
|
case MoodLevel.neutral:
|
|
return '😐';
|
|
case MoodLevel.happy:
|
|
return '🙂';
|
|
case MoodLevel.veryHappy:
|
|
return '😄';
|
|
}
|
|
}
|
|
|
|
String get label {
|
|
switch (this) {
|
|
case MoodLevel.verySad:
|
|
return 'Very Sad';
|
|
case MoodLevel.sad:
|
|
return 'Sad';
|
|
case MoodLevel.neutral:
|
|
return 'Neutral';
|
|
case MoodLevel.happy:
|
|
return 'Happy';
|
|
case MoodLevel.veryHappy:
|
|
return 'Very Happy';
|
|
}
|
|
}
|
|
}
|
|
|
|
extension FlowIntensityExtension on FlowIntensity {
|
|
String get label {
|
|
switch (this) {
|
|
case FlowIntensity.spotting:
|
|
return 'Spotting';
|
|
case FlowIntensity.light:
|
|
return 'Light';
|
|
case FlowIntensity.medium:
|
|
return 'Medium';
|
|
case FlowIntensity.heavy:
|
|
return 'Heavy';
|
|
}
|
|
}
|
|
}
|
|
|
|
extension CyclePhaseExtension on CyclePhase {
|
|
String get label {
|
|
switch (this) {
|
|
case CyclePhase.menstrual:
|
|
return 'Menstrual';
|
|
case CyclePhase.follicular:
|
|
return 'Follicular';
|
|
case CyclePhase.ovulation:
|
|
return 'Ovulation';
|
|
case CyclePhase.luteal:
|
|
return 'Luteal';
|
|
}
|
|
}
|
|
|
|
String get emoji {
|
|
switch (this) {
|
|
case CyclePhase.menstrual:
|
|
return '🩸';
|
|
case CyclePhase.follicular:
|
|
return '🌱';
|
|
case CyclePhase.ovulation:
|
|
return '🌸';
|
|
case CyclePhase.luteal:
|
|
return '🌙';
|
|
}
|
|
}
|
|
|
|
Color get color {
|
|
switch (this) {
|
|
case CyclePhase.menstrual:
|
|
return AppColors.menstrualPhase;
|
|
case CyclePhase.follicular:
|
|
return AppColors.follicularPhase;
|
|
case CyclePhase.ovulation:
|
|
return AppColors.ovulationPhase;
|
|
case CyclePhase.luteal:
|
|
return AppColors.lutealPhase;
|
|
}
|
|
}
|
|
|
|
List<Color> get gradientColors {
|
|
switch (this) {
|
|
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
|
|
];
|
|
}
|
|
}
|
|
|
|
String get description {
|
|
switch (this) {
|
|
case CyclePhase.menstrual:
|
|
return 'A time for rest and reflection';
|
|
case CyclePhase.follicular:
|
|
return 'A time of renewal and energy';
|
|
case CyclePhase.ovulation:
|
|
return 'Peak fertility window';
|
|
case CyclePhase.luteal:
|
|
return 'A time for patience and self-care';
|
|
}
|
|
}
|
|
}
|