import 'dart:async'; import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../theme/app_theme.dart'; import '../../models/cycle_entry.dart'; import '../../models/user_profile.dart'; import '../../services/notification_service.dart'; import '../../providers/user_provider.dart'; class PadTrackerScreen extends ConsumerStatefulWidget { const PadTrackerScreen({super.key}); @override ConsumerState createState() => _PadTrackerScreenState(); } class _PadTrackerScreenState extends ConsumerState { FlowIntensity _selectedFlow = FlowIntensity.medium; bool _notificationScheduled = false; Timer? _timer; Duration _timeSinceLastChange = Duration.zero; int? _activeSupplyIndex; @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { _checkInitialPrompt(); }); _startTimer(); } @override void dispose() { _timer?.cancel(); super.dispose(); } void _startTimer() { _timer = Timer.periodic(const Duration(seconds: 1), (timer) { if (mounted) { setState(() { _updateTimeSinceChange(); }); } }); _updateTimeSinceChange(); } void _updateTimeSinceChange() { final user = ref.read(userProfileProvider); if (user?.lastPadChangeTime != null) { _timeSinceLastChange = DateTime.now().difference(user!.lastPadChangeTime!); } else { _timeSinceLastChange = Duration.zero; } } Future _checkInitialPrompt() async { final user = ref.read(userProfileProvider); if (user == null) return; final lastChange = user.lastPadChangeTime; final now = DateTime.now(); final bool changedToday = lastChange != null && lastChange.year == now.year && lastChange.month == now.month && lastChange.day == now.day; if (!changedToday) { await showDialog( 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 && 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'), ), ], ), ); } } Future _updateLastChangeTime(DateTime time) async { final user = ref.read(userProfileProvider); if (user != null) { final updatedProfile = user.copyWith( lastPadChangeTime: time, ); await ref.read(userProfileProvider.notifier).updateProfile(updatedProfile); _updateTimeSinceChange(); } } SupplyItem? get _activeSupply { final user = ref.watch(userProfileProvider); if (user == null || user.padSupplies == null || user.padSupplies!.isEmpty) return null; if (_activeSupplyIndex == null || _activeSupplyIndex! >= user.padSupplies!.length) { return user.padSupplies!.first; } return user.padSupplies![_activeSupplyIndex!]; } bool get _shouldShowMismatchWarning { final supply = _activeSupply; if (supply == null) return false; int flowValue = 1; switch (_selectedFlow) { case FlowIntensity.spotting: flowValue = 1; break; case FlowIntensity.light: flowValue = 2; break; case FlowIntensity.medium: flowValue = 3; break; case FlowIntensity.heavy: flowValue = 5; break; } return flowValue > supply.absorbency; } int get _recommendedHours { final supply = _activeSupply; if (supply == null) return 6; // Default final type = supply.type; if (type == PadType.menstrualCup || type == PadType.menstrualDisc || type == PadType.periodUnderwear) { return 12; } int baseHours; switch (_selectedFlow) { case FlowIntensity.heavy: baseHours = (type == PadType.super_pad || type == PadType.overnight || type == PadType.tampon_super) ? 4 : 3; break; case FlowIntensity.medium: baseHours = 6; break; case FlowIntensity.light: baseHours = 8; break; case FlowIntensity.spotting: baseHours = 8; break; } int flowValue = 1; switch (_selectedFlow) { case FlowIntensity.spotting: flowValue = 1; break; case FlowIntensity.light: flowValue = 2; break; case FlowIntensity.medium: flowValue = 3; break; case FlowIntensity.heavy: flowValue = 5; break; } final absorbency = supply.absorbency; final ratio = absorbency / flowValue; double adjusted = baseHours * ratio; int maxHours = (type == PadType.tampon_regular || type == PadType.tampon_super) ? 8 : 12; if (adjusted < 1) adjusted = 1; if (adjusted > maxHours) adjusted = maxHours.toDouble(); return adjusted.round(); } void _showSupplyPicker() { showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: Colors.transparent, builder: (context) => const _SupplyManagementPopup(), ); } @override Widget build(BuildContext context) { final remainingHours = _recommendedHours - _timeSinceLastChange.inHours; final isOverdue = remainingHours < 0; final supply = _activeSupply; final user = ref.watch(userProfileProvider); return Scaffold( appBar: AppBar( title: const Text('Pad Tracker'), centerTitle: true, ), body: SingleChildScrollView( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Supply Selection at the top as requested _buildSectionHeader('Current Protection'), const SizedBox(height: 12), GestureDetector( onTap: _showSupplyPicker, child: Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Theme.of(context).cardTheme.color, borderRadius: BorderRadius.circular(16), border: Border.all(color: AppColors.menstrualPhase.withOpacity(0.3)), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 10, offset: const Offset(0, 4), ), ], ), child: Row( children: [ Container( padding: const EdgeInsets.all(10), decoration: BoxDecoration( color: AppColors.menstrualPhase.withOpacity(0.1), shape: BoxShape.circle, ), child: const Icon(Icons.inventory_2_outlined, color: AppColors.menstrualPhase), ), const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( supply != null ? '${supply.brand} ${supply.type.label}' : 'No Supply Selected', style: GoogleFonts.outfit(fontWeight: FontWeight.bold, fontSize: 16), ), if (supply != null) Text( 'Absorbency: ${supply.absorbency}/5 • Stock: ${supply.count}', style: GoogleFonts.outfit(fontSize: 12, color: AppColors.warmGray), ) else Text( 'Tap to manage your supplies', style: GoogleFonts.outfit(fontSize: 12, color: AppColors.warmGray), ), ], ), ), const Icon(Icons.edit_outlined, size: 20, color: AppColors.warmGray), ], ), ), ), const SizedBox(height: 32), _buildSectionHeader('Current Flow Intensity'), const SizedBox(height: 12), Wrap( spacing: 8, runSpacing: 8, children: FlowIntensity.values.map((flow) { return ChoiceChip( label: Text(flow.label), selected: _selectedFlow == flow, onSelected: (selected) { if (selected) setState(() => _selectedFlow = flow); }, selectedColor: AppColors.menstrualPhase.withOpacity(0.3), labelStyle: GoogleFonts.outfit( color: _selectedFlow == flow ? AppColors.navyBlue : AppColors.charcoal, fontWeight: _selectedFlow == flow ? FontWeight.w600 : FontWeight.w400, ), ); }).toList(), ), const SizedBox(height: 48), // Recommendation Card / Timer Center( child: Container( width: double.infinity, padding: const EdgeInsets.all(24), decoration: BoxDecoration( color: isOverdue ? AppColors.rose.withOpacity(0.15) : AppColors.sageGreen.withOpacity(0.15), borderRadius: BorderRadius.circular(20), border: Border.all( color: isOverdue ? AppColors.rose.withOpacity(0.3) : AppColors.sageGreen.withOpacity(0.3) ), ), child: Column( children: [ Icon( isOverdue ? Icons.warning_amber_rounded : Icons.timer_outlined, size: 48, color: isOverdue ? AppColors.rose : AppColors.sageGreen ), const SizedBox(height: 16), Text( isOverdue ? 'Change Overdue!' : 'Next Change In:', style: GoogleFonts.outfit( fontSize: 16, color: AppColors.warmGray, ), ), const SizedBox(height: 8), if (_timeSinceLastChange != Duration.zero) ...[ Text( _formatRemainingTime( Duration(hours: _recommendedHours) - _timeSinceLastChange, user! ), style: GoogleFonts.outfit( fontSize: 32, fontWeight: FontWeight.bold, color: isOverdue ? AppColors.rose : AppColors.navyBlue, ), textAlign: TextAlign.center, ), Text( 'Last changed: ${_formatDuration(_timeSinceLastChange, user)} ago', style: GoogleFonts.outfit(fontSize: 12, color: AppColors.warmGray), ), ] else ...[ Text( '~$_recommendedHours Hours', style: GoogleFonts.outfit( fontSize: 32, fontWeight: FontWeight.bold, color: AppColors.navyBlue, ), ), ], ], ), ), ), const SizedBox(height: 32), if (_shouldShowMismatchWarning) Container( margin: const EdgeInsets.only(bottom: 24), padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: AppColors.rose.withOpacity(0.1), borderRadius: BorderRadius.circular(12), border: Border.all(color: AppColors.rose.withOpacity(0.3)), ), child: Row( children: [ const Icon(Icons.warning_amber_rounded, color: AppColors.rose), const SizedBox(width: 12), Expanded( child: Text( 'Your flow is heavier than your protection capacity. Change sooner to avoid leaks!', style: GoogleFonts.outfit( fontSize: 14, color: AppColors.charcoal, fontWeight: FontWeight.w500 ), ), ), ], ), ), const SizedBox(height: 16), SizedBox( width: double.infinity, height: 56, child: ElevatedButton.icon( onPressed: supply == null ? null : () async { final hours = _recommendedHours; // 1. Auto-deduct inventory if (user != null && user.isAutoInventoryEnabled) { // Deduct from the active supply final List updatedSupplies = user.padSupplies!.map((s) { if (s == supply && s.count > 0) { return s.copyWith(count: s.count - 1); } return s; }).toList(); final updatedProfile = user.copyWith( padSupplies: updatedSupplies, lastInventoryUpdate: DateTime.now(), lastPadChangeTime: DateTime.now(), ); await ref.read(userProfileProvider.notifier).updateProfile(updatedProfile); } else if (user != null) { final updatedProfile = user.copyWith( lastPadChangeTime: DateTime.now(), ); await ref.read(userProfileProvider.notifier).updateProfile(updatedProfile); } 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)), ); setState(() { _notificationScheduled = true; _updateTimeSinceChange(); }); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Logged! Timer reset & Inventory updated.')), ); } }, icon: Icon(_notificationScheduled ? Icons.check : Icons.restart_alt), label: Text( 'Changed / Remind Me', style: GoogleFonts.outfit(fontSize: 18, fontWeight: FontWeight.w600), ), style: ElevatedButton.styleFrom( backgroundColor: AppColors.menstrualPhase, foregroundColor: Colors.white, disabledBackgroundColor: AppColors.warmGray.withOpacity(0.2), ), ), ), ], ), ), ); } String _formatDuration(Duration d, UserProfile user) { final hours = d.inHours; final minutes = d.inMinutes % 60; final seconds = d.inSeconds % 60; final bool showMins = user.showPadTimerMinutes; final bool showSecs = user.showPadTimerSeconds && showMins; // Enforce minutes must be shown to show seconds List parts = []; if (hours > 0) parts.add('${hours}h'); if (showMins) parts.add('${minutes}m'); if (showSecs) parts.add('${seconds}s'); if (parts.isEmpty) { if (hours == 0 && minutes == 0 && seconds == 0) return 'Just now'; return '${d.inMinutes}m'; // Fallback } return parts.join(' '); } String _formatRemainingTime(Duration remaining, UserProfile user) { final isOverdue = remaining.isNegative; final absRemaining = remaining.abs(); final hours = absRemaining.inHours; final minutes = absRemaining.inMinutes % 60; final seconds = absRemaining.inSeconds % 60; final bool showMins = user.showPadTimerMinutes; final bool showSecs = user.showPadTimerSeconds && showMins; // Enforce minutes must be shown to show seconds List parts = []; if (hours > 0) parts.add('${hours}h'); if (showMins) { parts.add('${minutes}m'); } if (showSecs) { parts.add('${seconds}s'); } if (parts.isEmpty) { return isOverdue ? 'Overdue' : 'Change Now'; } String timeStr = parts.join(' '); return isOverdue ? '$timeStr overdue' : timeStr; } Widget _buildSectionHeader(String title) { return Text( title, style: GoogleFonts.outfit( fontSize: 18, fontWeight: FontWeight.w600, color: AppColors.navyBlue, ), ); } } class _SupplyManagementPopup extends ConsumerStatefulWidget { const _SupplyManagementPopup(); @override ConsumerState<_SupplyManagementPopup> createState() => _SupplyManagementPopupState(); } class _SupplyManagementPopupState extends ConsumerState<_SupplyManagementPopup> { final _brandController = TextEditingController(); PadType _selectedType = PadType.regular; int _absorbency = 3; int _count = 20; @override void dispose() { _brandController.dispose(); super.dispose(); } void _addSupply() async { final brand = _brandController.text.trim(); if (brand.isEmpty) return; final user = ref.read(userProfileProvider); if (user == null) return; final newSupply = SupplyItem( brand: brand, type: _selectedType, absorbency: _absorbency, count: _count, ); final List updatedSupplies = [...(user.padSupplies ?? []), newSupply]; final updatedProfile = user.copyWith(padSupplies: updatedSupplies); await ref.read(userProfileProvider.notifier).updateProfile(updatedProfile); _brandController.clear(); setState(() { _count = 20; _absorbency = 3; }); } @override Widget build(BuildContext context) { final user = ref.watch(userProfileProvider); final supplies = user?.padSupplies ?? []; return Container( padding: EdgeInsets.only( bottom: MediaQuery.of(context).viewInsets.bottom, top: 20, left: 20, right: 20, ), decoration: BoxDecoration( color: Theme.of(context).scaffoldBackgroundColor, borderRadius: const BorderRadius.vertical(top: Radius.circular(24)), ), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'Manage Supplies', style: GoogleFonts.outfit(fontSize: 20, fontWeight: FontWeight.bold), ), IconButton( icon: const Icon(Icons.close), onPressed: () => Navigator.pop(context), ), ], ), const Divider(), const SizedBox(height: 16), if (supplies.isNotEmpty) ...[ Text('Current Stock', style: GoogleFonts.outfit(fontWeight: FontWeight.w600, color: AppColors.navyBlue)), const SizedBox(height: 12), SizedBox( height: 120, child: ListView.builder( scrollDirection: Axis.horizontal, itemCount: supplies.length, itemBuilder: (context, index) { final item = supplies[index]; return Container( width: 160, margin: const EdgeInsets.only(right: 12), padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: AppColors.warmCream.withOpacity(0.3), borderRadius: BorderRadius.circular(16), border: Border.all(color: AppColors.warmGray.withOpacity(0.2)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: Text(item.brand, style: GoogleFonts.outfit(fontWeight: FontWeight.bold, fontSize: 13), overflow: TextOverflow.ellipsis), ), GestureDetector( onTap: () async { final updatedSupplies = List.from(supplies)..removeAt(index); await ref.read(userProfileProvider.notifier).updateProfile(user!.copyWith(padSupplies: updatedSupplies)); }, child: const Icon(Icons.delete_outline, size: 16, color: Colors.red), ), ], ), Text(item.type.label, style: GoogleFonts.outfit(fontSize: 11, color: AppColors.warmGray)), const Spacer(), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text('Qty: ${item.count}', style: GoogleFonts.outfit(fontSize: 12, fontWeight: FontWeight.w600)), Text('Abs: ${item.absorbency}', style: GoogleFonts.outfit(fontSize: 11, color: AppColors.menstrualPhase)), ], ), ], ), ); }, ), ), const SizedBox(height: 24), ], Text('Add New Pack', style: GoogleFonts.outfit(fontWeight: FontWeight.w600, color: AppColors.navyBlue)), const SizedBox(height: 12), TextField( controller: _brandController, decoration: InputDecoration( hintText: 'Brand Name (e.g. Always)', filled: true, fillColor: AppColors.warmCream.withOpacity(0.2), border: OutlineInputBorder(borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none), contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), ), ), const SizedBox(height: 12), Row( children: [ Expanded( child: DropdownButtonFormField( value: _selectedType, decoration: InputDecoration( contentPadding: const EdgeInsets.symmetric(horizontal: 12), border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)), ), items: PadType.values.map((t) => DropdownMenuItem(value: t, child: Text(t.label, style: const TextStyle(fontSize: 13)))).toList(), onChanged: (val) => setState(() => _selectedType = val!), ), ), const SizedBox(width: 12), Container( width: 100, padding: const EdgeInsets.symmetric(horizontal: 8), decoration: BoxDecoration( border: Border.all(color: Colors.grey), borderRadius: BorderRadius.circular(12), ), child: Row( children: [ IconButton(icon: const Icon(Icons.remove, size: 16), onPressed: () => setState(() => _count = (_count > 0 ? _count - 1 : 0))), Text('$_count', style: const TextStyle(fontWeight: FontWeight.bold)), IconButton(icon: const Icon(Icons.add, size: 16), onPressed: () => setState(() => _count++)), ], ), ), ], ), const SizedBox(height: 16), Text('Absorbency: $_absorbency/5', style: GoogleFonts.outfit(fontSize: 13, color: AppColors.warmGray)), Slider( value: _absorbency.toDouble(), min: 1, max: 5, divisions: 4, activeColor: AppColors.menstrualPhase, onChanged: (val) => setState(() => _absorbency = val.round()), ), const SizedBox(height: 16), SizedBox( width: double.infinity, height: 50, child: ElevatedButton( onPressed: _addSupply, style: ElevatedButton.styleFrom(backgroundColor: AppColors.navyBlue, foregroundColor: Colors.white), child: const Text('Add to Inventory'), ), ), const SizedBox(height: 24), ], ), ); } }