Enhance Pad Tracking with new Flow and Supply logic
This commit is contained in:
@@ -106,7 +106,7 @@ class _PadSettingsDialogState extends ConsumerState<PadSettingsDialog> {
|
||||
'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<PadSettingsDialog> {
|
||||
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',
|
||||
|
||||
@@ -140,6 +140,8 @@ class _PadTrackerCardState extends ConsumerState<PadTrackerCard> {
|
||||
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<PadTrackerCard> {
|
||||
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<PadTrackerCard> {
|
||||
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<PadTrackerCard> {
|
||||
? 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<PadTrackerCard> {
|
||||
],
|
||||
),
|
||||
),
|
||||
const Icon(Icons.chevron_right, color: AppColors.lightGray),
|
||||
Icon(Icons.chevron_right,
|
||||
color: isDark ? Colors.white24 : AppColors.lightGray),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -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<QuickLogDialog> {
|
||||
FlowIntensity? _flowIntensity;
|
||||
MoodLevel? _mood;
|
||||
int? _energyLevel;
|
||||
PadType? _selectedPadType;
|
||||
int _padAbsorbency = 3;
|
||||
|
||||
// Symptoms & Cravings
|
||||
final Map<String, bool> _symptoms = {
|
||||
@@ -274,18 +278,138 @@ class _QuickLogDialogState extends ConsumerState<QuickLogDialog> {
|
||||
}
|
||||
|
||||
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<PadType>(
|
||||
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<QuickLogDialog> {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user