From dc6bcad83f07bc3a9892ea378902adcb1b1ad9dd Mon Sep 17 00:00:00 2001 From: Sterlen Date: Fri, 9 Jan 2026 13:35:07 -0600 Subject: [PATCH] Enhance Pad Tracking with new Flow and Supply logic --- lib/main.dart | 37 +- lib/providers/user_provider.dart | 64 ++- lib/screens/calendar/calendar_screen.dart | 14 +- lib/screens/devotional/devotional_screen.dart | 27 +- lib/screens/home/home_screen.dart | 4 +- lib/screens/husband/husband_home_screen.dart | 36 +- .../husband/husband_settings_screen.dart | 22 +- lib/screens/log/log_screen.dart | 71 +-- lib/screens/log/pad_tracker_screen.dart | 410 ++++++++++++++++-- lib/screens/onboarding/onboarding_screen.dart | 8 + .../settings/supplies_settings_screen.dart | 8 +- lib/services/cycle_service.dart | 31 +- lib/widgets/pad_settings_dialog.dart | 5 +- lib/widgets/pad_tracker_card.dart | 13 +- lib/widgets/quick_log_buttons.dart | 6 +- lib/widgets/quick_log_dialog.dart | 169 +++++++- lib/widgets/tip_card.dart | 11 +- 17 files changed, 765 insertions(+), 171 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index c8b0d1b..efb062d 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -62,17 +62,32 @@ class ChristianPeriodTrackerApp extends ConsumerWidget { final Color accentColor; if (userProfile != null) { - accentColor = _colorFromHex(userProfile.accentColor); - switch (userProfile.themeMode) { - case AppThemeMode.light: - themeMode = ThemeMode.light; - break; - case AppThemeMode.dark: - themeMode = ThemeMode.dark; - - case AppThemeMode.system: - themeMode = ThemeMode.system; - break; + if (userProfile.isHusband) { + accentColor = _colorFromHex(userProfile.husbandAccentColor); + switch (userProfile.husbandThemeMode) { + case AppThemeMode.light: + themeMode = ThemeMode.light; + break; + case AppThemeMode.dark: + themeMode = ThemeMode.dark; + break; + case AppThemeMode.system: + themeMode = ThemeMode.system; + break; + } + } else { + accentColor = _colorFromHex(userProfile.accentColor); + switch (userProfile.themeMode) { + case AppThemeMode.light: + themeMode = ThemeMode.light; + break; + case AppThemeMode.dark: + themeMode = ThemeMode.dark; + break; + case AppThemeMode.system: + themeMode = ThemeMode.system; + break; + } } } else { // Default theme for initial load or if profile is null diff --git a/lib/providers/user_provider.dart b/lib/providers/user_provider.dart index a3140f9..4b12f8f 100644 --- a/lib/providers/user_provider.dart +++ b/lib/providers/user_provider.dart @@ -2,10 +2,13 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:hive_flutter/hive_flutter.dart'; import '../models/user_profile.dart'; import '../models/cycle_entry.dart'; + +import 'package:uuid/uuid.dart'; import '../services/cycle_service.dart'; /// Provider for the user profile -final userProfileProvider = StateNotifierProvider((ref) { +final userProfileProvider = + StateNotifierProvider((ref) { return UserProfileNotifier(); }); @@ -38,9 +41,11 @@ class UserProfileNotifier extends StateNotifier { } } - Future updateRelationshipStatus(RelationshipStatus relationshipStatus) async { + Future updateRelationshipStatus( + RelationshipStatus relationshipStatus) async { if (state != null) { - await updateProfile(state!.copyWith(relationshipStatus: relationshipStatus)); + await updateProfile( + state!.copyWith(relationshipStatus: relationshipStatus)); } } @@ -52,7 +57,8 @@ class UserProfileNotifier extends StateNotifier { } /// Provider for cycle entries -final cycleEntriesProvider = StateNotifierProvider>((ref) { +final cycleEntriesProvider = + StateNotifierProvider>((ref) { return CycleEntriesNotifier(); }); @@ -102,6 +108,56 @@ class CycleEntriesNotifier extends StateNotifier> { await box.clear(); state = []; } + + Future generateExampleData(String userId) async { + await clearEntries(); + final box = Hive.box('cycle_entries'); + final today = DateTime.now(); + + // Generate 3 past cycles (~28 days each) + DateTime cycleStart = today.subtract(const Duration(days: 84)); // 3 * 28 + + while (cycleStart.isBefore(today)) { + // Create Period (5 days) + for (int i = 0; i < 5; i++) { + final date = cycleStart.add(Duration(days: i)); + if (date.isAfter(today)) break; + + final isHeavy = i == 1 || i == 2; + final entry = CycleEntry( + id: const Uuid().v4(), + date: date, + isPeriodDay: true, + flowIntensity: isHeavy ? FlowIntensity.heavy : FlowIntensity.medium, + mood: i == 1 ? MoodLevel.sad : null, + crampIntensity: i == 0 ? 3 : null, + hasHeadache: i == 0, + createdAt: date, + updatedAt: date, + ); + await box.put(entry.id, entry); + } + + // Add random ovulation symptoms near day 14 + final ovulationDay = cycleStart.add(const Duration(days: 14)); + if (ovulationDay.isBefore(today)) { + final entry = CycleEntry( + id: const Uuid().v4(), + date: ovulationDay, + isPeriodDay: false, + energyLevel: 4, // High energy + mood: MoodLevel.veryHappy, + createdAt: ovulationDay, + updatedAt: ovulationDay, + ); + await box.put(entry.id, entry); + } + + cycleStart = cycleStart.add(const Duration(days: 28)); + } + + _loadEntries(); + } } /// Computed provider for current cycle info diff --git a/lib/screens/calendar/calendar_screen.dart b/lib/screens/calendar/calendar_screen.dart index f239e36..45b92c4 100644 --- a/lib/screens/calendar/calendar_screen.dart +++ b/lib/screens/calendar/calendar_screen.dart @@ -326,7 +326,11 @@ class _CalendarScreenState extends ConsumerState { child: Container( padding: const EdgeInsets.symmetric(vertical: 8), decoration: BoxDecoration( - color: isSelected ? Colors.white : Colors.transparent, + color: isSelected + ? (Theme.of(context).brightness == Brightness.dark + ? const Color(0xFF333333) + : Colors.white) + : Colors.transparent, borderRadius: BorderRadius.circular(8), boxShadow: isSelected ? [ @@ -396,7 +400,7 @@ class _CalendarScreenState extends ConsumerState { style: GoogleFonts.outfit( fontSize: 20, fontWeight: FontWeight.w600, - color: AppColors.charcoal, + color: Theme.of(context).colorScheme.onSurface, ), ), const SizedBox(height: 20), @@ -429,7 +433,7 @@ class _CalendarScreenState extends ConsumerState { label, style: GoogleFonts.outfit( fontSize: 14, - color: AppColors.charcoal, + color: Theme.of(context).colorScheme.onSurface, ), ), ], @@ -637,8 +641,8 @@ class _CalendarScreenState extends ConsumerState { Expanded( child: Text( 'Did you use pantyliners today?', - style: - GoogleFonts.outfit(fontSize: 14, color: AppColors.charcoal), + style: GoogleFonts.outfit( + fontSize: 14, color: Theme.of(context).colorScheme.onSurface), ), ), TextButton( diff --git a/lib/screens/devotional/devotional_screen.dart b/lib/screens/devotional/devotional_screen.dart index eb4406d..7afd662 100644 --- a/lib/screens/devotional/devotional_screen.dart +++ b/lib/screens/devotional/devotional_screen.dart @@ -143,7 +143,7 @@ class _DevotionalScreenState extends ConsumerState { phase.description, style: GoogleFonts.outfit( fontSize: 14, - color: AppColors.warmGray, + color: Theme.of(context).colorScheme.onSurfaceVariant, ), ), const SizedBox(height: 32), @@ -170,7 +170,7 @@ class _DevotionalScreenState extends ConsumerState { onPressed: () => ref .read(scriptureProvider.notifier) .getPreviousScripture(), - color: AppColors.charcoal, + color: Theme.of(context).colorScheme.onSurface, ), ), Positioned( @@ -180,7 +180,7 @@ class _DevotionalScreenState extends ConsumerState { onPressed: () => ref .read(scriptureProvider.notifier) .getNextScripture(), - color: AppColors.charcoal, + color: Theme.of(context).colorScheme.onSurface, ), ), ], @@ -321,16 +321,16 @@ class _DevotionalScreenState extends ConsumerState { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Row( + Row( children: [ - Text('🙏', style: TextStyle(fontSize: 20)), - SizedBox(width: 8), + const Text('🙏', style: TextStyle(fontSize: 20)), + const SizedBox(width: 8), Text( 'Prayer Prompt', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, - color: AppColors.charcoal, // Assuming a default color + color: Theme.of(context).colorScheme.onSurface, ), ), ], @@ -460,7 +460,7 @@ class _DevotionalScreenState extends ConsumerState { width: double.infinity, padding: const EdgeInsets.all(20), decoration: BoxDecoration( - color: Colors.white, + color: Theme.of(context).cardColor, borderRadius: BorderRadius.circular(16), border: Border.all(color: AppColors.gold.withValues(alpha: 0.5)), boxShadow: [ @@ -510,7 +510,7 @@ class _DevotionalScreenState extends ConsumerState { style: GoogleFonts.outfit( fontSize: 18, fontWeight: FontWeight.w600, - color: AppColors.charcoal, + color: Theme.of(context).colorScheme.onSurface, ), ), if (latestPlan.scriptureReference.isNotEmpty) ...[ @@ -530,7 +530,7 @@ class _DevotionalScreenState extends ConsumerState { style: GoogleFonts.lora( fontSize: 15, height: 1.5, - color: AppColors.charcoal.withValues(alpha: 0.9), + color: Theme.of(context).textTheme.bodyMedium?.color, ), ), ], @@ -543,7 +543,7 @@ class _DevotionalScreenState extends ConsumerState { width: double.infinity, padding: const EdgeInsets.all(20), decoration: BoxDecoration( - color: Colors.white, + color: Theme.of(context).cardColor, borderRadius: BorderRadius.circular(16), border: Border.all( color: AppColors.warmGray.withValues(alpha: 0.3), @@ -595,7 +595,10 @@ class _DevotionalScreenState extends ConsumerState { style: GoogleFonts.outfit( fontSize: 18, fontWeight: FontWeight.w600, - color: AppColors.charcoal.withValues(alpha: 0.7), + color: Theme.of(context) + .colorScheme + .onSurface + .withValues(alpha: 0.7), ), ), const SizedBox(height: 4), diff --git a/lib/screens/home/home_screen.dart b/lib/screens/home/home_screen.dart index 57299d6..00a89f6 100644 --- a/lib/screens/home/home_screen.dart +++ b/lib/screens/home/home_screen.dart @@ -245,7 +245,7 @@ class _DashboardTabState extends ConsumerState<_DashboardTab> { onPressed: () => ref .read(scriptureProvider.notifier) .getPreviousScripture(), - color: AppColors.charcoal, + color: Theme.of(context).colorScheme.onSurface, ), ), Positioned( @@ -255,7 +255,7 @@ class _DashboardTabState extends ConsumerState<_DashboardTab> { onPressed: () => ref .read(scriptureProvider.notifier) .getNextScripture(), - color: AppColors.charcoal, + color: Theme.of(context).colorScheme.onSurface, ), ), ], diff --git a/lib/screens/husband/husband_home_screen.dart b/lib/screens/husband/husband_home_screen.dart index 7e53aa7..2b0e182 100644 --- a/lib/screens/husband/husband_home_screen.dart +++ b/lib/screens/husband/husband_home_screen.dart @@ -329,6 +329,8 @@ class _HusbandDashboardState extends ConsumerState<_HusbandDashboard> { Widget build(BuildContext context) { final user = ref.watch(userProfileProvider); final cycleInfo = ref.watch(currentCycleInfoProvider); + final theme = Theme.of(context); + final isDark = theme.brightness == Brightness.dark; final wifeName = user?.partnerName ?? "Wife"; final phase = cycleInfo.phase; @@ -349,7 +351,7 @@ class _HusbandDashboardState extends ConsumerState<_HusbandDashboard> { 'Hey there,', style: GoogleFonts.outfit( fontSize: 16, - color: AppColors.warmGray, + color: isDark ? Colors.white70 : AppColors.warmGray, ), ), Text( @@ -357,7 +359,7 @@ class _HusbandDashboardState extends ConsumerState<_HusbandDashboard> { style: GoogleFonts.outfit( fontSize: 28, fontWeight: FontWeight.w600, - color: AppColors.navyBlue, + color: isDark ? Colors.white : AppColors.navyBlue, ), ), const SizedBox(height: 24), @@ -460,11 +462,12 @@ class _HusbandDashboardState extends ConsumerState<_HusbandDashboard> { width: double.infinity, padding: const EdgeInsets.all(16), decoration: BoxDecoration( - color: Colors.white, + color: isDark ? const Color(0xFF1E1E1E) : Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( - color: AppColors.navyBlue.withValues(alpha: 0.05), + color: (isDark ? Colors.black : AppColors.navyBlue) + .withValues(alpha: 0.05), blurRadius: 10, offset: const Offset(0, 4), ), @@ -494,7 +497,7 @@ class _HusbandDashboardState extends ConsumerState<_HusbandDashboard> { style: GoogleFonts.outfit( fontSize: 16, fontWeight: FontWeight.w600, - color: AppColors.navyBlue, + color: isDark ? Colors.white : AppColors.navyBlue, ), ), ], @@ -504,7 +507,7 @@ class _HusbandDashboardState extends ConsumerState<_HusbandDashboard> { _getSupportTip(phase), style: GoogleFonts.outfit( fontSize: 14, - color: AppColors.charcoal, + color: isDark ? Colors.white70 : AppColors.charcoal, height: 1.5, ), ), @@ -538,7 +541,7 @@ class _HusbandDashboardState extends ConsumerState<_HusbandDashboard> { margin: const EdgeInsets.only(bottom: 20), padding: const EdgeInsets.all(16), decoration: BoxDecoration( - color: Colors.white, + color: isDark ? const Color(0xFF1E1E1E) : Colors.white, borderRadius: BorderRadius.circular(16), border: Border.all( color: AppColors.rose.withValues(alpha: 0.3)), @@ -574,7 +577,7 @@ class _HusbandDashboardState extends ConsumerState<_HusbandDashboard> { style: GoogleFonts.outfit( fontSize: 16, fontWeight: FontWeight.w600, - color: AppColors.navyBlue, + color: isDark ? Colors.white : AppColors.navyBlue, ), ), ], @@ -589,7 +592,9 @@ class _HusbandDashboardState extends ConsumerState<_HusbandDashboard> { backgroundColor: AppColors.rose.withValues(alpha: 0.1), labelStyle: GoogleFonts.outfit( - color: AppColors.navyBlue, + color: isDark + ? Colors.white + : AppColors.navyBlue, fontWeight: FontWeight.w500), side: BorderSide.none, )) @@ -636,7 +641,7 @@ class _HusbandDashboardState extends ConsumerState<_HusbandDashboard> { style: GoogleFonts.outfit( fontSize: 14, fontWeight: FontWeight.w500, - color: AppColors.warmGray, + color: isDark ? Colors.white70 : AppColors.warmGray, ), ), ), @@ -676,7 +681,7 @@ class _HusbandDashboardState extends ConsumerState<_HusbandDashboard> { style: GoogleFonts.lora( fontSize: 15, fontStyle: FontStyle.italic, - color: AppColors.navyBlue, + color: isDark ? Colors.white : AppColors.navyBlue, height: 1.6, ), ), @@ -689,7 +694,7 @@ class _HusbandDashboardState extends ConsumerState<_HusbandDashboard> { style: GoogleFonts.outfit( fontSize: 12, fontWeight: FontWeight.w500, - color: AppColors.warmGray, + color: isDark ? Colors.white70 : AppColors.warmGray, ), ), GestureDetector( @@ -737,8 +742,9 @@ class _HusbandDashboardState extends ConsumerState<_HusbandDashboard> { style: GoogleFonts.outfit(fontWeight: FontWeight.w500), ), style: OutlinedButton.styleFrom( - foregroundColor: AppColors.navyBlue, - side: const BorderSide(color: AppColors.navyBlue), + foregroundColor: isDark ? Colors.white : AppColors.navyBlue, + side: BorderSide( + color: isDark ? Colors.white70 : AppColors.navyBlue), padding: const EdgeInsets.symmetric(vertical: 14), ), ), @@ -854,7 +860,7 @@ class _HusbandDashboardState extends ConsumerState<_HusbandDashboard> { style: GoogleFonts.lora( fontSize: 16, fontStyle: FontStyle.italic, - color: AppColors.charcoal, + color: Theme.of(context).textTheme.bodyLarge?.color, height: 1.6, ), ), diff --git a/lib/screens/husband/husband_settings_screen.dart b/lib/screens/husband/husband_settings_screen.dart index 32303e0..83f5adf 100644 --- a/lib/screens/husband/husband_settings_screen.dart +++ b/lib/screens/husband/husband_settings_screen.dart @@ -108,7 +108,7 @@ class HusbandSettingsScreen extends ConsumerWidget { style: GoogleFonts.outfit( fontSize: 20, fontWeight: FontWeight.w600, - color: AppColors.navyBlue, + color: Theme.of(context).textTheme.titleLarge?.color, ), ), const SizedBox(height: 16), @@ -147,11 +147,11 @@ class HusbandSettingsScreen extends ConsumerWidget { context: context, builder: (context) => StatefulBuilder( builder: (context, setState) => AlertDialog( - title: const Row( + title: Row( children: [ - Icon(Icons.link, color: AppColors.navyBlue), - SizedBox(width: 8), - Text('Connect with Wife'), + Icon(Icons.link, color: Theme.of(context).colorScheme.primary), + const SizedBox(width: 8), + const Text('Connect with Wife'), ], ), content: Column( @@ -159,8 +159,10 @@ class HusbandSettingsScreen extends ConsumerWidget { children: [ Text( 'Enter the pairing code from your wife\'s app:', - style: - GoogleFonts.outfit(fontSize: 14, color: AppColors.warmGray), + style: GoogleFonts.outfit( + fontSize: 14, + color: Theme.of(context).textTheme.bodyMedium?.color, + ), ), const SizedBox(height: 16), TextField( @@ -174,8 +176,10 @@ class HusbandSettingsScreen extends ConsumerWidget { const SizedBox(height: 16), Text( 'Your wife can find this code in her Settings under "Share with Husband".', - style: - GoogleFonts.outfit(fontSize: 12, color: AppColors.warmGray), + style: GoogleFonts.outfit( + fontSize: 12, + color: Theme.of(context).textTheme.bodySmall?.color, + ), ), const SizedBox(height: 24), Row( diff --git a/lib/screens/log/log_screen.dart b/lib/screens/log/log_screen.dart index 4e268c9..4020136 100644 --- a/lib/screens/log/log_screen.dart +++ b/lib/screens/log/log_screen.dart @@ -23,8 +23,8 @@ class LogScreen extends ConsumerStatefulWidget { class _LogScreenState extends ConsumerState { late DateTime _selectedDate; String? _existingEntryId; - bool _isPeriodDay = false; - bool _isSpotting = false; + bool? _isPeriodDay; + bool? _isSpotting; FlowIntensity? _flowIntensity; MoodLevel? _mood; int? _energyLevel; @@ -45,12 +45,12 @@ class _LogScreenState extends ConsumerState { TextEditingController(); // Intimacy tracking - bool _hadIntimacy = false; + bool? _hadIntimacy; bool? _intimacyProtected; // null = no selection, true = protected, false = unprotected // Pantyliner / Supply tracking - bool _usedPantyliner = false; // Used for "Did you use supplies?" + bool? _usedPantyliner; // Used for "Did you use supplies?" int _pantylinerCount = 0; int? _selectedSupplyIndex; // Index of selected supply from inventory @@ -126,10 +126,10 @@ class _LogScreenState extends ConsumerState { final entry = CycleEntry( id: _existingEntryId ?? const Uuid().v4(), date: _selectedDate, - isPeriodDay: _isPeriodDay, - flowIntensity: _isPeriodDay + isPeriodDay: _isPeriodDay ?? false, + flowIntensity: _isPeriodDay == true ? _flowIntensity - : (_isSpotting ? FlowIntensity.spotting : null), + : (_isSpotting == true ? FlowIntensity.spotting : null), mood: _mood, energyLevel: _energyLevel, crampIntensity: _crampIntensity > 0 ? _crampIntensity : null, @@ -146,10 +146,10 @@ class _LogScreenState extends ConsumerState { notes: _notesController.text.isNotEmpty ? _notesController.text : null, cravings: cravings, husbandNotes: _husbandNotes, - hadIntimacy: _hadIntimacy, - intimacyProtected: _hadIntimacy ? _intimacyProtected : null, - usedPantyliner: _usedPantyliner, - pantylinerCount: _usedPantyliner ? _pantylinerCount : 0, + hadIntimacy: _hadIntimacy ?? false, + intimacyProtected: _hadIntimacy == true ? _intimacyProtected : null, + usedPantyliner: _usedPantyliner ?? false, + pantylinerCount: _usedPantyliner == true ? _pantylinerCount : 0, createdAt: DateTime.now(), updatedAt: DateTime.now(), ); @@ -161,7 +161,7 @@ class _LogScreenState extends ConsumerState { } // Trigger Notification if Period Start - if (_isPeriodDay && + if (_isPeriodDay == true && ref.read(userProfileProvider)?.notifyPeriodStart == true) { final entries = ref.read(cycleEntriesProvider); final yesterday = _selectedDate.subtract(const Duration(days: 1)); @@ -198,8 +198,8 @@ class _LogScreenState extends ConsumerState { void _resetForm() { setState(() { _existingEntryId = null; - _isPeriodDay = false; - _isSpotting = false; + _isPeriodDay = null; + _isSpotting = null; _flowIntensity = null; _mood = null; _energyLevel = 3; @@ -217,9 +217,9 @@ class _LogScreenState extends ConsumerState { _notesController.clear(); _cravingsController.clear(); _husbandNotes = null; - _hadIntimacy = false; + _hadIntimacy = null; _intimacyProtected = null; - _usedPantyliner = false; + _usedPantyliner = null; _pantylinerCount = 0; }); } @@ -321,7 +321,7 @@ class _LogScreenState extends ConsumerState { ), // Are you spotting? (only if NOT period day) - if (!_isPeriodDay) ...[ + if (_isPeriodDay != true) ...[ const SizedBox(height: 16), _buildSectionCard( context, @@ -350,7 +350,8 @@ class _LogScreenState extends ConsumerState { ], // Still on Period? (If predicted but toggle is NO) - if (!_isPeriodDay && _shouldShowPeriodCompletionPrompt()) ...[ + if (_isPeriodDay == false && + _shouldShowPeriodCompletionPrompt()) ...[ const SizedBox(height: 16), _buildSectionCard( context, @@ -406,7 +407,7 @@ class _LogScreenState extends ConsumerState { ], // Flow Intensity (only if period day) - if (_isPeriodDay) ...[ + if (_isPeriodDay == true) ...[ const SizedBox(height: 16), _buildSectionCard( context, @@ -474,7 +475,7 @@ class _LogScreenState extends ConsumerState { context, MaterialPageRoute( builder: (context) => PadTrackerScreen( - isSpotting: _isSpotting, + isSpotting: _isSpotting ?? false, initialFlow: _flowIntensity, ), ), @@ -515,7 +516,7 @@ class _LogScreenState extends ConsumerState { ), ], ), - if (_usedPantyliner) ...[ + if (_usedPantyliner == true) ...[ const SizedBox(height: 12), if (userProfile?.padSupplies?.isNotEmpty == true) ...[ Text( @@ -844,15 +845,17 @@ class _LogScreenState extends ConsumerState { SwitchListTile( title: Text('Had Intimacy Today', style: GoogleFonts.outfit(fontSize: 14)), - value: _hadIntimacy, + value: _hadIntimacy ?? false, onChanged: (val) => setState(() { _hadIntimacy = val; - if (!val) _intimacyProtected = null; + if (!val) { + _intimacyProtected = null; + } }), activeThumbColor: AppColors.sageGreen, contentPadding: EdgeInsets.zero, ), - if (_hadIntimacy) ...[ + if (_hadIntimacy == true) ...[ const SizedBox(height: 8), Text('Protection:', style: GoogleFonts.outfit( @@ -1022,7 +1025,7 @@ class _LogScreenState extends ConsumerState { } Widget _buildYesNoControl(BuildContext context, - {required bool value, + {required bool? value, required ValueChanged onChanged, required Color activeColor}) { final theme = Theme.of(context); @@ -1037,24 +1040,24 @@ class _LogScreenState extends ConsumerState { duration: const Duration(milliseconds: 200), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), decoration: BoxDecoration( - color: !value + color: value == false ? theme.colorScheme.error .withValues(alpha: isDark ? 0.3 : 0.2) : theme.colorScheme.surfaceContainerHighest .withValues(alpha: 0.3), borderRadius: const BorderRadius.horizontal(left: Radius.circular(8)), - border: !value + border: value == false ? Border.all(color: theme.colorScheme.error) : Border.all(color: Colors.transparent), ), child: Text( 'No', style: GoogleFonts.outfit( - color: !value + color: value == false ? theme.colorScheme.error : theme.colorScheme.onSurfaceVariant, - fontWeight: !value ? FontWeight.w600 : FontWeight.w400, + fontWeight: value == false ? FontWeight.w600 : FontWeight.w400, ), ), ), @@ -1065,21 +1068,23 @@ class _LogScreenState extends ConsumerState { duration: const Duration(milliseconds: 200), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), decoration: BoxDecoration( - color: value + color: value == true ? activeColor.withValues(alpha: isDark ? 0.3 : 0.2) : theme.colorScheme.surfaceContainerHighest .withValues(alpha: 0.3), borderRadius: const BorderRadius.horizontal(right: Radius.circular(8)), - border: value + border: value == true ? Border.all(color: activeColor) : Border.all(color: Colors.transparent), ), child: Text( 'Yes', style: GoogleFonts.outfit( - color: value ? activeColor : theme.colorScheme.onSurfaceVariant, - fontWeight: value ? FontWeight.w600 : FontWeight.w400, + color: value == true + ? activeColor + : theme.colorScheme.onSurfaceVariant, + fontWeight: value == true ? FontWeight.w600 : FontWeight.w400, ), ), ), diff --git a/lib/screens/log/pad_tracker_screen.dart b/lib/screens/log/pad_tracker_screen.dart index 91628ab..e4a5f76 100644 --- a/lib/screens/log/pad_tracker_screen.dart +++ b/lib/screens/log/pad_tracker_screen.dart @@ -28,6 +28,7 @@ class _PadTrackerScreenState extends ConsumerState { Timer? _timer; Duration _timeSinceLastChange = Duration.zero; int? _activeSupplyIndex; + SupplyItem? _manualSupply; @override void initState() { @@ -81,67 +82,78 @@ class _PadTrackerScreenState extends ConsumerState { lastChange.day == now.day; if (!changedToday) { - await showDialog( + final result = await showDialog<_PadLogResult>( context: context, barrierDismissible: false, - builder: (context) => AlertDialog( - title: Text('Track Your Change', - style: GoogleFonts.outfit(fontWeight: FontWeight.bold)), - content: Text( - 'When did you last change your pad/tampon?', - style: GoogleFonts.outfit(), - ), - actions: [ - TextButton( - onPressed: () { - _updateLastChangeTime(DateTime.now()); - Navigator.pop(context); - }, - child: const Text('Just Now'), - ), - TextButton( - onPressed: () async { - final time = await showTimePicker( - context: context, - initialTime: TimeOfDay.now(), - ); - if (time != null && context.mounted) { - final now = DateTime.now(); - final selectedDate = DateTime( - now.year, now.month, now.day, time.hour, time.minute); - if (selectedDate.isAfter(now)) { - _updateLastChangeTime(now); - } else { - _updateLastChangeTime(selectedDate); - } - Navigator.pop(context); - } - }, - child: const Text('Pick Time'), - ), - TextButton( - onPressed: () { - Navigator.pop(context); - }, - child: const Text('Skip'), - ), - ], - ), + builder: (context) => const _PadCheckInDialog(), ); + + if (result != null) { + if (result.skipped) return; + + _finalizeLog( + result.time, + result.flow, + supply: result.supply, + supplyIndex: result.supplyIndex, + deductInventory: result.deductInventory, + ); + } } } - Future _updateLastChangeTime(DateTime time) async { + Future _finalizeLog(DateTime time, FlowIntensity flow, + {required SupplyItem supply, + required int? supplyIndex, + required bool deductInventory}) async { final user = ref.read(userProfileProvider); if (user != null) { - final updatedProfile = user.copyWith( + UserProfile updatedProfile = user; + + // Deduct inventory if needed + if (deductInventory && + user.isAutoInventoryEnabled && + supplyIndex != null) { + // Clone the supplies list + List newSupplies = List.from(user.padSupplies ?? []); + if (supplyIndex < newSupplies.length) { + final oldItem = newSupplies[supplyIndex]; + if (oldItem.count > 0) { + newSupplies[supplyIndex] = + oldItem.copyWith(count: oldItem.count - 1); + } + } + updatedProfile = updatedProfile.copyWith(padSupplies: newSupplies); + } + + // Update Last Change Time + updatedProfile = updatedProfile.copyWith( lastPadChangeTime: time, + lastInventoryUpdate: + deductInventory ? DateTime.now() : user.lastInventoryUpdate, ); + await ref .read(userProfileProvider.notifier) .updateProfile(updatedProfile); + + setState(() { + _activeSupplyIndex = supplyIndex; + if (supplyIndex == null) { + _manualSupply = supply; + } else { + _manualSupply = null; + } + }); + _updateTimeSinceChange(); _scheduleReminders(time); + + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Logged! Timer started.')), + ); + } } } @@ -210,6 +222,8 @@ class _PadTrackerScreenState extends ConsumerState { } SupplyItem? get _activeSupply { + if (_manualSupply != null) return _manualSupply; + final user = ref.watch(userProfileProvider); if (user == null || user.padSupplies == null || user.padSupplies!.isEmpty) { return null; @@ -943,3 +957,307 @@ class _SupplyManagementPopupState ); } } + +class _PadLogResult { + final DateTime time; + final FlowIntensity flow; + final SupplyItem supply; + final int? supplyIndex; + final bool deductInventory; + final bool skipped; + + _PadLogResult({ + required this.time, + required this.flow, + required this.supply, + required this.supplyIndex, + required this.deductInventory, + this.skipped = false, + }); + + factory _PadLogResult.skipped() { + return _PadLogResult( + time: DateTime.now(), + flow: FlowIntensity.medium, + supply: + SupplyItem(brand: '', type: PadType.regular, absorbency: 0, count: 0), + supplyIndex: null, + deductInventory: false, + skipped: true, + ); + } +} + +class _PadCheckInDialog extends ConsumerStatefulWidget { + const _PadCheckInDialog(); + + @override + ConsumerState<_PadCheckInDialog> createState() => _PadCheckInDialogState(); +} + +class _PadCheckInDialogState extends ConsumerState<_PadCheckInDialog> { + DateTime _selectedTime = DateTime.now(); // "Just Now" by default + FlowIntensity _selectedFlow = FlowIntensity.medium; + int? _selectedSupplyIndex; + SupplyItem? _selectedSupply; + bool _useBorrowed = false; + PadType _borrowedType = PadType.regular; + + @override + void initState() { + super.initState(); + // Default to first available supply if exists + WidgetsBinding.instance.addPostFrameCallback((_) { + final user = ref.read(userProfileProvider); + if (user != null && + user.padSupplies != null && + user.padSupplies!.isNotEmpty) { + setState(() { + _selectedSupplyIndex = 0; + _selectedSupply = user.padSupplies![0]; + }); + } else { + setState(() { + _useBorrowed = true; // Fallback if no inventory + }); + } + }); + } + + Future _onTimePressed() async { + final TimeOfDay? picked = await showTimePicker( + context: context, + initialTime: TimeOfDay.fromDateTime(_selectedTime), + ); + if (picked != null) { + final now = DateTime.now(); + setState(() { + _selectedTime = DateTime( + now.year, + now.month, + now.day, + picked.hour, + picked.minute, + ); + }); + } + } + + String _formatTimeDisplay() { + final diff = DateTime.now().difference(_selectedTime).abs(); + if (diff.inMinutes < 1) return 'Just Now'; + // Format H:mm AM/PM would be better, but keeping simple 24h or simple format + // Just using H:mm for now as in code snippet + final hour = _selectedTime.hour > 12 + ? _selectedTime.hour - 12 + : (_selectedTime.hour == 0 ? 12 : _selectedTime.hour); + final period = _selectedTime.hour >= 12 ? 'PM' : 'AM'; + return '$hour:${_selectedTime.minute.toString().padLeft(2, '0')} $period'; + } + + @override + Widget build(BuildContext context) { + final user = ref.watch(userProfileProvider); + final supplies = user?.padSupplies ?? []; + + return AlertDialog( + title: Text('Track Your Change', + style: GoogleFonts.outfit(fontWeight: FontWeight.bold)), + content: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // 1. Time Selection + Row( + children: [ + const Icon(Icons.access_time, + size: 20, color: AppColors.warmGray), + const SizedBox(width: 8), + Text('Time: ', + style: GoogleFonts.outfit(fontWeight: FontWeight.w600)), + Text(_formatTimeDisplay(), style: GoogleFonts.outfit()), + const Spacer(), + TextButton( + onPressed: _onTimePressed, + child: const Text('Edit'), + ), + ], + ), + const Divider(), + + // 2. Flow Intensity + Text('Flow Intensity:', + style: GoogleFonts.outfit(fontWeight: FontWeight.w600)), + const SizedBox(height: 8), + Wrap( + spacing: 8, + children: FlowIntensity.values.map((f) { + return ChoiceChip( + label: Text(f.label), + selected: _selectedFlow == f, + onSelected: (selected) { + if (selected) setState(() => _selectedFlow = f); + }, + ); + }).toList(), + ), + const SizedBox(height: 16), + + // 3. Supply Selection + Text('Supply:', + style: GoogleFonts.outfit(fontWeight: FontWeight.w600)), + const SizedBox(height: 8), + + if (supplies.isNotEmpty) ...[ + // Inventory Dropdown + RadioListTile( + title: const Text('Use Inventory'), + value: false, // _useBorrowed = false + groupValue: _useBorrowed, + onChanged: (val) => setState(() => _useBorrowed = false), + contentPadding: EdgeInsets.zero, + ), + if (!_useBorrowed) + Padding( + padding: const EdgeInsets.only(left: 16.0, bottom: 8), + child: DropdownButtonFormField( + isExpanded: true, + initialValue: _selectedSupplyIndex, + items: supplies.asMap().entries.map((entry) { + final s = entry.value; + return DropdownMenuItem( + value: entry.key, + child: Text('${s.brand} ${s.type.label} (x${s.count})', + overflow: TextOverflow.ellipsis), + ); + }).toList(), + onChanged: (val) { + setState(() { + _selectedSupplyIndex = val; + if (val != null) _selectedSupply = supplies[val]; + }); + }, + decoration: InputDecoration( + contentPadding: const EdgeInsets.symmetric( + horizontal: 12, vertical: 8), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8)), + ), + ), + ), + ], + + // Borrowed / Other + RadioListTile( + title: const Text('Borrowed / Other'), + value: true, // _useBorrowed = true + groupValue: _useBorrowed, + onChanged: (val) => setState(() => _useBorrowed = true), + contentPadding: EdgeInsets.zero, + ), + + if (_useBorrowed) + Padding( + padding: const EdgeInsets.only(left: 16.0), + child: DropdownButtonFormField( + isExpanded: true, + value: _borrowedType, + items: PadType.values.map((t) { + return DropdownMenuItem( + value: t, + child: Text(t.label), + ); + }).toList(), + onChanged: (val) { + if (val != null) setState(() => _borrowedType = val); + }, + decoration: InputDecoration( + contentPadding: + const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8)), + ), + ), + ), + ], + ), + ), + actions: [ + TextButton( + onPressed: () { + Navigator.pop(context, _PadLogResult.skipped()); + }, + child: const Text('Skip'), + ), + ElevatedButton( + onPressed: () { + SupplyItem supplyToUse; + bool deduct = false; + int? index; + + if (_useBorrowed) { + // Create temp supply + int absorbency = 3; + switch (_borrowedType) { + case PadType.pantyLiner: + absorbency = 1; + break; + case PadType.regular: + case PadType.tamponRegular: + absorbency = 3; + break; + case PadType.superPad: + case PadType.tamponSuper: + case PadType.overnight: + absorbency = 5; + break; + default: + absorbency = 4; + } + supplyToUse = SupplyItem( + brand: 'Borrowed', + type: _borrowedType, + absorbency: absorbency, + count: 0); + deduct = false; + index = null; + } else { + if (_selectedSupply == null) { + // Auto-select first if available as fallback + if (supplies.isNotEmpty) { + supplyToUse = supplies.first; + index = 0; + deduct = true; + } else { + supplyToUse = SupplyItem( + brand: 'Generic', + type: PadType.regular, + absorbency: 3, + count: 0); + index = null; + deduct = false; + } + } else { + supplyToUse = _selectedSupply!; + deduct = true; + index = _selectedSupplyIndex; + } + } + + Navigator.pop( + context, + _PadLogResult( + time: _selectedTime, + flow: _selectedFlow, + supply: supplyToUse, + supplyIndex: index, + deductInventory: deduct, + )); + }, + child: const Text('Log Change'), + ), + ], + ); + } +} diff --git a/lib/screens/onboarding/onboarding_screen.dart b/lib/screens/onboarding/onboarding_screen.dart index b740fad..463a628 100644 --- a/lib/screens/onboarding/onboarding_screen.dart +++ b/lib/screens/onboarding/onboarding_screen.dart @@ -142,12 +142,20 @@ class _OnboardingScreenState extends ConsumerState { isIrregularCycle: _isIrregularCycle, hasCompletedOnboarding: true, useExampleData: _useExampleData, + isPadTrackingEnabled: _isPadTrackingEnabled, createdAt: DateTime.now(), updatedAt: DateTime.now(), ); await ref.read(userProfileProvider.notifier).updateProfile(userProfile); + // Generate example data if requested + if (_useExampleData) { + await ref + .read(cycleEntriesProvider.notifier) + .generateExampleData(userProfile.id); + } + // Trigger partner connection notification if applicable if (!_skipPartnerConnection && !_useExampleData) { await NotificationService().showPartnerUpdateNotification( diff --git a/lib/screens/settings/supplies_settings_screen.dart b/lib/screens/settings/supplies_settings_screen.dart index 5a45426..860632f 100644 --- a/lib/screens/settings/supplies_settings_screen.dart +++ b/lib/screens/settings/supplies_settings_screen.dart @@ -133,7 +133,7 @@ class _SuppliesSettingsScreenState style: GoogleFonts.outfit( fontSize: 18, fontWeight: FontWeight.w600, - color: AppColors.charcoal, + color: Theme.of(context).colorScheme.onSurface, ), ), ), @@ -278,7 +278,8 @@ class _SuppliesSettingsScreenState title: Text( 'Auto-deduct on Log', style: GoogleFonts.outfit( - fontWeight: FontWeight.w500, color: AppColors.charcoal), + fontWeight: FontWeight.w500, + color: Theme.of(context).colorScheme.onSurface), ), subtitle: Text( 'Reduce count when you log a pad', @@ -308,7 +309,8 @@ class _SuppliesSettingsScreenState title: Text( 'Show Minutes', style: GoogleFonts.outfit( - fontWeight: FontWeight.w500, color: AppColors.charcoal), + fontWeight: FontWeight.w500, + color: Theme.of(context).colorScheme.onSurface), ), value: _showPadTimerMinutes, onChanged: (val) { diff --git a/lib/services/cycle_service.dart b/lib/services/cycle_service.dart index bb1b412..158a27c 100644 --- a/lib/services/cycle_service.dart +++ b/lib/services/cycle_service.dart @@ -157,26 +157,41 @@ class CycleService { } // Handle cases where last period was long ago (more than one cycle) - final dayOfCycle = ((daysSinceLastPeriod - 1) % cycleLength) + 1; - final daysUntilPeriod = cycleLength - dayOfCycle; + final int calculatedDayOfCycle = + ((daysSinceLastPeriod - 1) % cycleLength) + 1; + + // Check if we are in the predicted menstrual phase but no period is logged + bool isPeriodLoggedToday = + entries.any((e) => DateUtils.isSameDay(e.date, now) && e.isPeriodDay); CyclePhase phase; - if (dayOfCycle <= user.averagePeriodLength) { - // Use variable period length - phase = CyclePhase.menstrual; - } else if (dayOfCycle <= 13) { + int dayOfCycle = calculatedDayOfCycle; + + if (calculatedDayOfCycle <= user.averagePeriodLength) { + if (isPeriodLoggedToday) { + phase = CyclePhase.menstrual; + } else { + // No period logged today, but we are in the predicted window. + // Stay in Luteal and extend the day count. + phase = CyclePhase.luteal; + dayOfCycle = daysSinceLastPeriod; + } + } else if (calculatedDayOfCycle <= 13) { phase = CyclePhase.follicular; - } else if (dayOfCycle <= 16) { + } else if (calculatedDayOfCycle <= 16) { phase = CyclePhase.ovulation; } else { phase = CyclePhase.luteal; } + final daysUntilPeriod = + dayOfCycle >= cycleLength ? 0 : cycleLength - dayOfCycle; + return CycleInfo( phase: phase, dayOfCycle: dayOfCycle, daysUntilPeriod: daysUntilPeriod, - isPeriodExpected: daysUntilPeriod <= 0 || dayOfCycle <= 5, + isPeriodExpected: daysUntilPeriod <= 0 || calculatedDayOfCycle <= 5, ); } diff --git a/lib/widgets/pad_settings_dialog.dart b/lib/widgets/pad_settings_dialog.dart index d11b901..464aa84 100644 --- a/lib/widgets/pad_settings_dialog.dart +++ b/lib/widgets/pad_settings_dialog.dart @@ -106,7 +106,7 @@ class _PadSettingsDialogState extends ConsumerState { 'Enable Pad Tracking', style: GoogleFonts.outfit( fontSize: 16, - color: AppColors.charcoal, + color: Theme.of(context).colorScheme.onSurface, ), ), ), @@ -359,7 +359,8 @@ class _PadSettingsDialogState extends ConsumerState { title: Text( 'Auto-deduct on Log', style: GoogleFonts.outfit( - fontWeight: FontWeight.w500, color: AppColors.charcoal), + fontWeight: FontWeight.w500, + color: Theme.of(context).colorScheme.onSurface), ), subtitle: Text( 'Reduce count when you log a pad', diff --git a/lib/widgets/pad_tracker_card.dart b/lib/widgets/pad_tracker_card.dart index 1e5c9d3..1c26453 100644 --- a/lib/widgets/pad_tracker_card.dart +++ b/lib/widgets/pad_tracker_card.dart @@ -140,6 +140,8 @@ class _PadTrackerCardState extends ConsumerState { return const SizedBox.shrink(); } + final isDark = Theme.of(context).brightness == Brightness.dark; + return GestureDetector( onTap: () { Navigator.push( @@ -150,7 +152,7 @@ class _PadTrackerCardState extends ConsumerState { child: Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( - color: Colors.white, + color: isDark ? const Color(0xFF1E1E1E) : Colors.white, borderRadius: BorderRadius.circular(16), border: Border.all(color: _statusColor.withValues(alpha: 0.3)), boxShadow: [ @@ -187,7 +189,7 @@ class _PadTrackerCardState extends ConsumerState { style: GoogleFonts.outfit( fontSize: 16, fontWeight: FontWeight.w600, - color: AppColors.charcoal, + color: isDark ? Colors.white : AppColors.charcoal, ), ), InkWell( @@ -202,7 +204,9 @@ class _PadTrackerCardState extends ConsumerState { ? Icons.arrow_downward : Icons.arrow_upward, size: 16, - color: AppColors.warmGray), + color: isDark + ? Colors.white70 + : AppColors.warmGray), ) ], ), @@ -217,7 +221,8 @@ class _PadTrackerCardState extends ConsumerState { ], ), ), - const Icon(Icons.chevron_right, color: AppColors.lightGray), + Icon(Icons.chevron_right, + color: isDark ? Colors.white24 : AppColors.lightGray), ], ), const SizedBox(height: 12), diff --git a/lib/widgets/quick_log_buttons.dart b/lib/widgets/quick_log_buttons.dart index d0cfebd..607d739 100644 --- a/lib/widgets/quick_log_buttons.dart +++ b/lib/widgets/quick_log_buttons.dart @@ -91,8 +91,8 @@ class QuickLogButtons extends ConsumerWidget { final isDark = Theme.of(context).brightness == Brightness.dark; return SizedBox( - height: 80, width: 75, + // Removed fixed height to prevent overflow on larger text scalings child: Material( color: Colors.transparent, child: InkWell( @@ -118,7 +118,9 @@ class QuickLogButtons extends ConsumerWidget { style: GoogleFonts.outfit( fontSize: 12, // Slightly larger text fontWeight: FontWeight.w600, - color: isDark ? Colors.white.withValues(alpha: 0.9) : color, + color: isDark + ? Colors.white.withValues(alpha: 0.9) + : AppColors.charcoal, ), ), ], diff --git a/lib/widgets/quick_log_dialog.dart b/lib/widgets/quick_log_dialog.dart index 9e81a3f..83d5340 100644 --- a/lib/widgets/quick_log_dialog.dart +++ b/lib/widgets/quick_log_dialog.dart @@ -4,10 +4,12 @@ import 'package:google_fonts/google_fonts.dart'; import 'package:uuid/uuid.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../models/cycle_entry.dart'; +import '../models/user_profile.dart'; import '../providers/user_provider.dart'; import '../providers/navigation_provider.dart'; import '../screens/log/pad_tracker_screen.dart'; import '../services/notification_service.dart'; +import '../theme/app_theme.dart'; class QuickLogDialog extends ConsumerStatefulWidget { final String logType; @@ -23,6 +25,8 @@ class _QuickLogDialogState extends ConsumerState { FlowIntensity? _flowIntensity; MoodLevel? _mood; int? _energyLevel; + PadType? _selectedPadType; + int _padAbsorbency = 3; // Symptoms & Cravings final Map _symptoms = { @@ -274,18 +278,138 @@ class _QuickLogDialogState extends ConsumerState { } Widget _buildPadsLog() { - // This can be a simple button to navigate to the PadTrackerScreen - return ElevatedButton( - onPressed: () { - Navigator.of(context).pop(); - Navigator.of(context).push(MaterialPageRoute( - builder: (context) => const PadTrackerScreen(), - )); - }, - child: const Text('Track Pad Change'), + final theme = Theme.of(context); + // ignore: unused_local_variable + final user = ref.watch(userProfileProvider); + + return SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Flow Intensity:', + style: GoogleFonts.outfit(fontWeight: FontWeight.w600)), + const SizedBox(height: 8), + Wrap( + spacing: 8, + children: FlowIntensity.values.map((flow) { + return ChoiceChip( + label: Text(flow.label), + selected: _flowIntensity == flow, + onSelected: (selected) { + if (selected) setState(() => _flowIntensity = flow); + }, + ); + }).toList(), + ), + const SizedBox(height: 16), + Text('Protection Type:', + style: GoogleFonts.outfit(fontWeight: FontWeight.w600)), + const SizedBox(height: 8), + DropdownButtonFormField( + initialValue: _selectedPadType, + decoration: InputDecoration( + border: + OutlineInputBorder(borderRadius: BorderRadius.circular(12)), + contentPadding: const EdgeInsets.symmetric(horizontal: 12), + ), + items: PadType.values.map((type) { + return DropdownMenuItem( + value: type, + child: Text(type.label), + ); + }).toList(), + onChanged: (value) => setState(() => _selectedPadType = value), + hint: const Text('Select type'), + ), + const SizedBox(height: 16), + Text('Absorbency (1-5):', + style: GoogleFonts.outfit(fontWeight: FontWeight.w600)), + Slider( + value: _padAbsorbency.toDouble(), + min: 1, + max: 5, + divisions: 4, + label: _padAbsorbency.toString(), + onChanged: (val) => setState(() => _padAbsorbency = val.round()), + ), + const SizedBox(height: 16), + if (_flowIntensity != null && _selectedPadType != null) + Center( + child: Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: AppColors.sageGreen.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(12), + ), + child: Text( + 'Estimated duration: ${_calculateRecommendedHours()} hours', + style: GoogleFonts.outfit( + color: AppColors.sageGreen, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + const SizedBox(height: 16), + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: () { + Navigator.of(context).pop(); + ref.read(navigationProvider.notifier).setIndex(2); + Navigator.of(context).push(MaterialPageRoute( + builder: (context) => const PadTrackerScreen(), + )); + }, + style: ElevatedButton.styleFrom( + backgroundColor: theme.colorScheme.surfaceContainerHighest, + foregroundColor: theme.colorScheme.onSurface, + ), + child: const Text('More Options (Inventory, etc.)'), + ), + ), + ], + ), ); } + int _calculateRecommendedHours() { + if (_selectedPadType == null || _flowIntensity == null) return 6; + + final type = _selectedPadType!; + if (type == PadType.menstrualCup || + type == PadType.menstrualDisc || + type == PadType.periodUnderwear) { + return 12; + } + + int baseHours; + switch (_flowIntensity!) { + case FlowIntensity.heavy: + baseHours = (type == PadType.superPad || + type == PadType.overnight || + type == PadType.tamponSuper) + ? 4 + : 3; + break; + case FlowIntensity.medium: + baseHours = 6; + break; + case FlowIntensity.light: + baseHours = 8; + break; + case FlowIntensity.spotting: + baseHours = 10; + break; + case FlowIntensity.none: + baseHours = 8; + break; + } + + return baseHours; + } + Widget _buildPrayerLog() { return Column( mainAxisSize: MainAxisSize.min, @@ -397,8 +521,33 @@ class _QuickLogDialogState extends ConsumerState { return; // Don't save empty prayer } break; + case 'pads': + final userProfile = ref.read(userProfileProvider); + if (userProfile != null) { + final hours = _calculateRecommendedHours(); + await ref.read(userProfileProvider.notifier).updateProfile( + userProfile.copyWith( + lastPadChangeTime: DateTime.now(), + // Auto-inventory deduction could go here, but omitted for "Quick" simplicity + // unless we want to match PadTrackerScreen exactly. + ), + ); + + await NotificationService().scheduleNotification( + id: 100, + title: 'Time to change!', + body: 'It\'s been $hours hours since you logged your protection.', + scheduledDate: DateTime.now().add(Duration(hours: hours)), + ); + } + updatedEntry = entry.copyWith( + isPeriodDay: _flowIntensity != FlowIntensity.none && + _flowIntensity != FlowIntensity.spotting, + flowIntensity: _flowIntensity, + ); + break; default: - // pads handled separately + // Already handled or invalid return; } diff --git a/lib/widgets/tip_card.dart b/lib/widgets/tip_card.dart index 73c8b11..d473382 100644 --- a/lib/widgets/tip_card.dart +++ b/lib/widgets/tip_card.dart @@ -128,10 +128,10 @@ class TipCard extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildDetailSection( - 'Nutrition', details['nutrition']!, Icons.restaurant), + context, 'Nutrition', details['nutrition']!, Icons.restaurant), const SizedBox(height: 16), - _buildDetailSection( - 'Movement', details['movement']!, Icons.fitness_center), + _buildDetailSection(context, 'Movement', details['movement']!, + Icons.fitness_center), const SizedBox(height: 16), Text( 'Note: While these are general trends, your body is unique. Always listen to your own energy levels.', @@ -154,7 +154,8 @@ class TipCard extends StatelessWidget { ); } - Widget _buildDetailSection(String title, String content, IconData icon) { + Widget _buildDetailSection( + BuildContext context, String title, String content, IconData icon) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -176,7 +177,7 @@ class TipCard extends StatelessWidget { content, style: GoogleFonts.outfit( fontSize: 13, - color: AppColors.charcoal, + color: Theme.of(context).colorScheme.onSurface, ), ), ],