Files
Tracker/lib/models/cycle_entry.dart
Sterlen b4b2bfe749 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.
2025-12-26 22:40:52 -06:00

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';
}
}
}