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'; import '../../widgets/protected_wrapper.dart'; class PadTrackerScreen extends ConsumerStatefulWidget { final FlowIntensity? initialFlow; final bool isSpotting; const PadTrackerScreen({ super.key, this.initialFlow, this.isSpotting = false, }); @override ConsumerState createState() => _PadTrackerScreenState(); } class _PadTrackerScreenState extends ConsumerState { FlowIntensity _selectedFlow = FlowIntensity.medium; bool _notificationScheduled = false; Timer? _timer; Duration _timeSinceLastChange = Duration.zero; int? _activeSupplyIndex; SupplyItem? _manualSupply; @override void initState() { super.initState(); _selectedFlow = widget.isSpotting ? FlowIntensity.spotting : widget.initialFlow ?? FlowIntensity.medium; 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) { final result = await showDialog<_PadLogResult>( context: context, barrierDismissible: false, 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 _finalizeLog(DateTime time, FlowIntensity flow, {required SupplyItem supply, required int? supplyIndex, required bool deductInventory}) async { final user = ref.read(userProfileProvider); if (user != null) { 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.')), ); } } } Future _scheduleReminders(DateTime lastChangeTime) async { final user = ref.read(userProfileProvider); if (user == null || !user.isPadTrackingEnabled) return; final service = NotificationService(); // Cancel previous await service.cancelNotification(200); await service.cancelNotification(201); await service.cancelNotification(202); await service.cancelNotification(203); // Calculate target final hours = _recommendedHours; final changeTime = lastChangeTime.add(Duration(hours: hours)); final now = DateTime.now(); // 2 Hours Before if (user.notifyPad2Hours) { final notifyTime = changeTime.subtract(const Duration(hours: 2)); if (notifyTime.isAfter(now)) { await service.scheduleNotification( id: 200, title: 'Upcoming Pad Change', body: 'Recommended change in 2 hours.', scheduledDate: notifyTime); } } // 1 Hour Before if (user.notifyPad1Hour) { final notifyTime = changeTime.subtract(const Duration(hours: 1)); if (notifyTime.isAfter(now)) { await service.scheduleNotification( id: 201, title: 'Upcoming Pad Change', body: 'Recommended change in 1 hour.', scheduledDate: notifyTime); } } // 30 Mins Before if (user.notifyPad30Mins) { final notifyTime = changeTime.subtract(const Duration(minutes: 30)); if (notifyTime.isAfter(now)) { await service.scheduleNotification( id: 202, title: 'Upcoming Pad Change', body: 'Recommended change in 30 minutes.', scheduledDate: notifyTime); } } // Change Now if (user.notifyPadNow) { if (changeTime.isAfter(now)) { await service.scheduleNotification( id: 203, title: 'Time to Change!', body: 'It has been $hours hours since your last change.', scheduledDate: changeTime); } } } SupplyItem? get _activeSupply { if (_manualSupply != null) return _manualSupply; 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; // No flow is fine with any protection if (_selectedFlow == FlowIntensity.none) return false; // Spotting is fine with any protection if (_selectedFlow == FlowIntensity.spotting) return false; int flowValue = 1; switch (_selectedFlow) { case FlowIntensity.light: flowValue = 2; break; case FlowIntensity.medium: flowValue = 3; break; case FlowIntensity.heavy: flowValue = 5; break; default: 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.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; // More generous for spotting break; case FlowIntensity.none: baseHours = 8; // Health guideline for precautionary wear break; } int flowValue = 1; switch (_selectedFlow) { case FlowIntensity.none: flowValue = 0; break; // Health-only, no absorbency needed 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; // Avoid division by zero for precautionary (no flow) case if (flowValue == 0) { return baseHours; } final ratio = absorbency / flowValue; double adjusted = baseHours * ratio; int maxHours = (type == PadType.tamponRegular || type == PadType.tamponSuper) ? 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 ProtectedContentWrapper( title: 'Pad Tracker', isProtected: user?.isSuppliesProtected ?? false, userProfile: user, child: 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.withValues(alpha: 0.3)), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.05), blurRadius: 10, offset: const Offset(0, 4), ), ], ), child: Row( children: [ Container( padding: const EdgeInsets.all(10), decoration: BoxDecoration( color: AppColors.menstrualPhase.withValues(alpha: 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.withValues(alpha: 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.withValues(alpha: 0.15) : AppColors.sageGreen.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(20), border: Border.all( color: isOverdue ? AppColors.rose.withValues(alpha: 0.3) : AppColors.sageGreen.withValues(alpha: 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.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(12), border: Border.all( color: AppColors.rose.withValues(alpha: 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 (context.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.withValues(alpha: 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.withValues(alpha: 0.3), borderRadius: BorderRadius.circular(16), border: Border.all( color: AppColors.warmGray.withValues(alpha: 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.withValues(alpha: 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( initialValue: _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), ], ), ); } } 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'), ), ], ); } }