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.
97 lines
2.9 KiB
Dart
97 lines
2.9 KiB
Dart
import '../models/user_profile.dart';
|
|
import '../models/cycle_entry.dart';
|
|
|
|
class CycleInfo {
|
|
final CyclePhase phase;
|
|
final int dayOfCycle;
|
|
final int daysUntilPeriod;
|
|
final bool isPeriodExpected;
|
|
|
|
CycleInfo({
|
|
required this.phase,
|
|
required this.dayOfCycle,
|
|
required this.daysUntilPeriod,
|
|
required this.isPeriodExpected,
|
|
});
|
|
|
|
@override
|
|
bool operator ==(Object other) =>
|
|
identical(this, other) ||
|
|
other is CycleInfo &&
|
|
runtimeType == other.runtimeType &&
|
|
phase == other.phase &&
|
|
dayOfCycle == other.dayOfCycle &&
|
|
daysUntilPeriod == other.daysUntilPeriod &&
|
|
isPeriodExpected == other.isPeriodExpected;
|
|
|
|
@override
|
|
int get hashCode =>
|
|
phase.hashCode ^
|
|
dayOfCycle.hashCode ^
|
|
daysUntilPeriod.hashCode ^
|
|
isPeriodExpected.hashCode;
|
|
}
|
|
|
|
class CycleService {
|
|
/// Calculates the current cycle information based on user profile
|
|
static CycleInfo calculateCycleInfo(UserProfile? user) {
|
|
if (user?.lastPeriodStartDate == null) {
|
|
return CycleInfo(
|
|
phase: CyclePhase.follicular,
|
|
dayOfCycle: 1,
|
|
daysUntilPeriod: user?.averageCycleLength ?? 28,
|
|
isPeriodExpected: false,
|
|
);
|
|
}
|
|
|
|
final lastPeriod = user!.lastPeriodStartDate!;
|
|
final cycleLength = user.averageCycleLength;
|
|
final now = DateTime.now();
|
|
|
|
// Normalize dates to midnight for accurate day counting
|
|
final startOfToday = DateTime(now.year, now.month, now.day);
|
|
final startOfLastPeriod = DateTime(lastPeriod.year, lastPeriod.month, lastPeriod.day);
|
|
|
|
final daysSinceLastPeriod = startOfToday.difference(startOfLastPeriod).inDays + 1;
|
|
|
|
// Handle cases where last period was long ago (more than one cycle)
|
|
final dayOfCycle = ((daysSinceLastPeriod - 1) % cycleLength) + 1;
|
|
final daysUntilPeriod = cycleLength - dayOfCycle;
|
|
|
|
CyclePhase phase;
|
|
if (dayOfCycle <= 5) {
|
|
phase = CyclePhase.menstrual;
|
|
} else if (dayOfCycle <= 13) {
|
|
phase = CyclePhase.follicular;
|
|
} else if (dayOfCycle <= 16) {
|
|
phase = CyclePhase.ovulation;
|
|
} else {
|
|
phase = CyclePhase.luteal;
|
|
}
|
|
|
|
return CycleInfo(
|
|
phase: phase,
|
|
dayOfCycle: dayOfCycle,
|
|
daysUntilPeriod: daysUntilPeriod,
|
|
isPeriodExpected: daysUntilPeriod <= 0 || dayOfCycle <= 5,
|
|
);
|
|
}
|
|
|
|
/// Format cycle day for display
|
|
static String getDayOfCycleDisplay(int day) => 'Day $day';
|
|
|
|
/// Get phase description
|
|
static String getPhaseDescription(CyclePhase phase) {
|
|
switch (phase) {
|
|
case CyclePhase.menstrual:
|
|
return 'Your body is resting and clearing. Be gentle with yourself.';
|
|
case CyclePhase.follicular:
|
|
return 'Energy and optimism are rising. A great time for new projects.';
|
|
case CyclePhase.ovulation:
|
|
return 'You are at your peak energy and fertility.';
|
|
case CyclePhase.luteal:
|
|
return 'Progesterone is rising. You may feel more introverted or sensitive.';
|
|
}
|
|
}
|
|
}
|