Implement husband-wife connection dialogue and theme support for learn articles
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -7,9 +7,16 @@ 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 {
|
||||
const PadTrackerScreen({super.key});
|
||||
final FlowIntensity? initialFlow;
|
||||
final bool isSpotting;
|
||||
const PadTrackerScreen({
|
||||
super.key,
|
||||
this.initialFlow,
|
||||
this.isSpotting = false,
|
||||
});
|
||||
|
||||
@override
|
||||
ConsumerState<PadTrackerScreen> createState() => _PadTrackerScreenState();
|
||||
@@ -25,6 +32,10 @@ class _PadTrackerScreenState extends ConsumerState<PadTrackerScreen> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_selectedFlow = widget.isSpotting
|
||||
? FlowIntensity.spotting
|
||||
: widget.initialFlow ?? FlowIntensity.medium;
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_checkInitialPrompt();
|
||||
});
|
||||
@@ -125,6 +136,75 @@ class _PadTrackerScreenState extends ConsumerState<PadTrackerScreen> {
|
||||
);
|
||||
await ref.read(userProfileProvider.notifier).updateProfile(updatedProfile);
|
||||
_updateTimeSinceChange();
|
||||
_scheduleReminders(time);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,50 +217,53 @@ class _PadTrackerScreenState extends ConsumerState<PadTrackerScreen> {
|
||||
return user.padSupplies![_activeSupplyIndex!];
|
||||
}
|
||||
|
||||
bool get _shouldShowMismatchWarning {
|
||||
bool get _shouldShowMismatchWarning {
|
||||
final supply = _activeSupply;
|
||||
if (supply == null) 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 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;
|
||||
if (supply == null) return 6; // Default
|
||||
|
||||
final type = supply.type;
|
||||
|
||||
if (type == PadType.menstrualCup ||
|
||||
type == PadType.menstrualDisc ||
|
||||
type == PadType.periodUnderwear) {
|
||||
return 12;
|
||||
}
|
||||
|
||||
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 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 = 10; // More generous for spotting
|
||||
break;
|
||||
}
|
||||
|
||||
int flowValue = 1;
|
||||
switch (_selectedFlow) {
|
||||
@@ -221,18 +304,22 @@ class _PadTrackerScreenState extends ConsumerState<PadTrackerScreen> {
|
||||
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'),
|
||||
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,
|
||||
@@ -467,7 +554,7 @@ class _PadTrackerScreenState extends ConsumerState<PadTrackerScreen> {
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
));
|
||||
}
|
||||
|
||||
String _formatDuration(Duration d, UserProfile user) {
|
||||
|
||||
Reference in New Issue
Block a user