1764 lines
64 KiB
Dart
1764 lines
64 KiB
Dart
import 'package:christian_period_tracker/models/user_profile.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:google_fonts/google_fonts.dart';
|
|
import '../../theme/app_theme.dart';
|
|
import '../../models/cycle_entry.dart';
|
|
import '../../models/scripture.dart';
|
|
import '../../providers/user_provider.dart';
|
|
import '../../services/cycle_service.dart';
|
|
import '../../services/mock_data_service.dart'; // Import mock service
|
|
import '../calendar/calendar_screen.dart'; // Import calendar
|
|
import 'husband_notes_screen.dart'; // Import notes screen
|
|
import 'learn_article_screen.dart'; // Import learn article screen
|
|
import 'husband_devotional_screen.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
|
|
/// Husband's companion app main screen
|
|
class HusbandHomeScreen extends ConsumerStatefulWidget {
|
|
const HusbandHomeScreen({super.key});
|
|
|
|
@override
|
|
ConsumerState<HusbandHomeScreen> createState() => _HusbandHomeScreenState();
|
|
}
|
|
|
|
class _HusbandHomeScreenState extends ConsumerState<HusbandHomeScreen> {
|
|
int _selectedIndex = 0;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Theme(
|
|
data: _husbandTheme,
|
|
child: Scaffold(
|
|
backgroundColor: AppColors.warmCream,
|
|
body: IndexedStack(
|
|
index: _selectedIndex,
|
|
children: [
|
|
const _HusbandDashboard(),
|
|
const CalendarScreen(readOnly: true), // Reused Calendar
|
|
const HusbandDevotionalScreen(), // Devotional & Planning
|
|
const _HusbandTipsScreen(),
|
|
const _HusbandLearnScreen(),
|
|
const _HusbandSettingsScreen(),
|
|
],
|
|
),
|
|
bottomNavigationBar: Container(
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: AppColors.navyBlue.withOpacity(0.1),
|
|
blurRadius: 10,
|
|
offset: const Offset(0, -2),
|
|
),
|
|
],
|
|
),
|
|
child: BottomNavigationBar(
|
|
currentIndex: _selectedIndex,
|
|
onTap: (index) => setState(() => _selectedIndex = index),
|
|
backgroundColor: Colors.white,
|
|
selectedItemColor: AppColors.navyBlue,
|
|
unselectedItemColor: AppColors.warmGray,
|
|
type: BottomNavigationBarType.fixed,
|
|
items: const [
|
|
BottomNavigationBarItem(
|
|
icon: Icon(Icons.home_outlined),
|
|
activeIcon: Icon(Icons.home),
|
|
label: 'Home',
|
|
),
|
|
BottomNavigationBarItem(
|
|
icon: Icon(Icons.calendar_month_outlined),
|
|
activeIcon: Icon(Icons.calendar_month),
|
|
label: 'Calendar',
|
|
),
|
|
BottomNavigationBarItem(
|
|
icon: Icon(Icons.menu_book_outlined),
|
|
activeIcon: Icon(Icons.menu_book),
|
|
label: 'Devotion',
|
|
),
|
|
BottomNavigationBarItem(
|
|
icon: Icon(Icons.lightbulb_outline),
|
|
activeIcon: Icon(Icons.lightbulb),
|
|
label: 'Tips',
|
|
),
|
|
BottomNavigationBarItem(
|
|
icon: Icon(Icons.school_outlined),
|
|
activeIcon: Icon(Icons.school),
|
|
label: 'Learn',
|
|
),
|
|
BottomNavigationBarItem(
|
|
icon: Icon(Icons.settings_outlined),
|
|
activeIcon: Icon(Icons.settings),
|
|
label: 'Settings',
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
ThemeData get _husbandTheme {
|
|
return ThemeData(
|
|
useMaterial3: true,
|
|
brightness: Brightness.light,
|
|
scaffoldBackgroundColor: AppColors.warmCream,
|
|
colorScheme: const ColorScheme.light(
|
|
primary: AppColors.navyBlue,
|
|
secondary: AppColors.gold,
|
|
surface: AppColors.warmCream,
|
|
),
|
|
appBarTheme: AppBarTheme(
|
|
backgroundColor: AppColors.warmCream,
|
|
foregroundColor: AppColors.navyBlue,
|
|
elevation: 0,
|
|
titleTextStyle: GoogleFonts.outfit(
|
|
fontSize: 20,
|
|
fontWeight: FontWeight.w600,
|
|
color: AppColors.navyBlue,
|
|
),
|
|
),
|
|
elevatedButtonTheme: ElevatedButtonThemeData(
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: AppColors.navyBlue,
|
|
foregroundColor: Colors.white,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _HusbandDashboard extends ConsumerStatefulWidget {
|
|
const _HusbandDashboard();
|
|
|
|
@override
|
|
ConsumerState<_HusbandDashboard> createState() => _HusbandDashboardState();
|
|
}
|
|
|
|
class _HusbandDashboardState extends ConsumerState<_HusbandDashboard> {
|
|
Scripture? _currentScripture;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_loadNewVerse();
|
|
}
|
|
|
|
void _loadNewVerse() {
|
|
setState(() {
|
|
_currentScripture = ScriptureDatabase().getHusbandScripture();
|
|
});
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final user = ref.watch(userProfileProvider);
|
|
final cycleInfo = ref.watch(currentCycleInfoProvider);
|
|
|
|
final wifeName = user?.partnerName ?? "Wife";
|
|
final phase = cycleInfo.phase;
|
|
final dayOfCycle = cycleInfo.dayOfCycle;
|
|
final daysUntilPeriod = cycleInfo.daysUntilPeriod;
|
|
|
|
final scripture = _currentScripture ?? ScriptureDatabase().getHusbandScripture();
|
|
|
|
return SafeArea(
|
|
child: SingleChildScrollView(
|
|
padding: const EdgeInsets.all(20),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// Greeting
|
|
Text(
|
|
'Hey there,',
|
|
style: GoogleFonts.outfit(
|
|
fontSize: 16,
|
|
color: AppColors.warmGray,
|
|
),
|
|
),
|
|
Text(
|
|
'Husband',
|
|
style: GoogleFonts.outfit(
|
|
fontSize: 28,
|
|
fontWeight: FontWeight.w600,
|
|
color: AppColors.navyBlue,
|
|
),
|
|
),
|
|
const SizedBox(height: 24),
|
|
|
|
// Wife's Cycle Status
|
|
Container(
|
|
width: double.infinity,
|
|
padding: const EdgeInsets.all(20),
|
|
decoration: BoxDecoration(
|
|
gradient: LinearGradient(
|
|
colors: [
|
|
AppColors.navyBlue,
|
|
AppColors.steelBlue,
|
|
],
|
|
begin: Alignment.topLeft,
|
|
end: Alignment.bottomRight,
|
|
),
|
|
borderRadius: BorderRadius.circular(20),
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Container(
|
|
width: 40,
|
|
height: 40,
|
|
decoration: BoxDecoration(
|
|
color: Colors.white.withOpacity(0.2),
|
|
borderRadius: BorderRadius.circular(10),
|
|
),
|
|
child: const Icon(
|
|
Icons.favorite,
|
|
color: Colors.white,
|
|
size: 22,
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
Text(
|
|
'$wifeName\'s Cycle',
|
|
style: GoogleFonts.outfit(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.w500,
|
|
color: Colors.white.withOpacity(0.9),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 16),
|
|
Text(
|
|
'Day $dayOfCycle • ${phase.label}',
|
|
style: GoogleFonts.outfit(
|
|
fontSize: 24,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.white,
|
|
),
|
|
),
|
|
const SizedBox(height: 4),
|
|
Text(
|
|
daysUntilPeriod > 0
|
|
? '~$daysUntilPeriod days until period'
|
|
: 'Period expected soon',
|
|
style: GoogleFonts.outfit(
|
|
fontSize: 14,
|
|
color: Colors.white.withOpacity(0.8),
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
Container(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 12,
|
|
vertical: 6,
|
|
),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white.withOpacity(0.2),
|
|
borderRadius: BorderRadius.circular(20),
|
|
),
|
|
child: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Text(phase.emoji),
|
|
const SizedBox(width: 6),
|
|
Text(
|
|
_getPhaseHint(phase),
|
|
style: GoogleFonts.outfit(
|
|
fontSize: 12,
|
|
color: Colors.white,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: 20),
|
|
|
|
// Support Tip
|
|
Container(
|
|
width: double.infinity,
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(16),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: AppColors.navyBlue.withOpacity(0.05),
|
|
blurRadius: 10,
|
|
offset: const Offset(0, 4),
|
|
),
|
|
],
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Container(
|
|
width: 36,
|
|
height: 36,
|
|
decoration: BoxDecoration(
|
|
color: AppColors.gold.withOpacity(0.2),
|
|
borderRadius: BorderRadius.circular(10),
|
|
),
|
|
child: const Icon(
|
|
Icons.lightbulb_outline,
|
|
color: AppColors.gold,
|
|
size: 20,
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
Text(
|
|
'How to Support Her',
|
|
style: GoogleFonts.outfit(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.w600,
|
|
color: AppColors.navyBlue,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 12),
|
|
Text(
|
|
_getSupportTip(phase),
|
|
style: GoogleFonts.outfit(
|
|
fontSize: 14,
|
|
color: AppColors.charcoal,
|
|
height: 1.5,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: 20),
|
|
|
|
// Recent Cravings (Dynamic)
|
|
Builder(
|
|
builder: (context) {
|
|
// Get recent cravings from the last 3 days
|
|
final allEntries = ref.read(cycleEntriesProvider);
|
|
// Sort by date desc
|
|
final sortedEntries = List<CycleEntry>.from(allEntries)..sort((a,b) => b.date.compareTo(a.date));
|
|
|
|
final recentCravings = <String>{};
|
|
final now = DateTime.now();
|
|
for (var entry in sortedEntries) {
|
|
if (now.difference(entry.date).inDays > 3) break;
|
|
if (entry.cravings != null) {
|
|
recentCravings.addAll(entry.cravings!);
|
|
}
|
|
}
|
|
|
|
if (recentCravings.isEmpty) return const SizedBox.shrink();
|
|
|
|
return Container(
|
|
width: double.infinity,
|
|
margin: const EdgeInsets.only(bottom: 20),
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(16),
|
|
border: Border.all(color: AppColors.rose.withOpacity(0.3)),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: AppColors.rose.withOpacity(0.05),
|
|
blurRadius: 10,
|
|
offset: const Offset(0, 4),
|
|
),
|
|
],
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Container(
|
|
width: 36,
|
|
height: 36,
|
|
decoration: BoxDecoration(
|
|
color: AppColors.rose.withOpacity(0.1),
|
|
borderRadius: BorderRadius.circular(10),
|
|
),
|
|
child: Icon(
|
|
Icons.fastfood,
|
|
color: AppColors.rose,
|
|
size: 20,
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
Text(
|
|
'She is Craving...',
|
|
style: GoogleFonts.outfit(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.w600,
|
|
color: AppColors.navyBlue,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 12),
|
|
Wrap(
|
|
spacing: 8,
|
|
runSpacing: 8,
|
|
children: recentCravings.map((craving) => Chip(
|
|
label: Text(craving),
|
|
backgroundColor: AppColors.rose.withOpacity(0.1),
|
|
labelStyle: GoogleFonts.outfit(color: AppColors.navyBlue, fontWeight: FontWeight.w500),
|
|
side: BorderSide.none,
|
|
)).toList(),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
},
|
|
),
|
|
|
|
// Scripture for Husbands
|
|
Container(
|
|
width: double.infinity,
|
|
padding: const EdgeInsets.all(20),
|
|
decoration: BoxDecoration(
|
|
gradient: LinearGradient(
|
|
colors: [
|
|
AppColors.gold.withOpacity(0.15),
|
|
AppColors.warmCream,
|
|
],
|
|
begin: Alignment.topLeft,
|
|
end: Alignment.bottomRight,
|
|
),
|
|
borderRadius: BorderRadius.circular(16),
|
|
border: Border.all(
|
|
color: AppColors.gold.withOpacity(0.3),
|
|
),
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Icon(
|
|
Icons.menu_book,
|
|
color: AppColors.gold,
|
|
size: 20,
|
|
),
|
|
const SizedBox(width: 8),
|
|
Expanded(
|
|
child: Text(
|
|
'Scripture for Husbands',
|
|
style: GoogleFonts.outfit(
|
|
fontSize: 14,
|
|
fontWeight: FontWeight.w500,
|
|
color: AppColors.warmGray,
|
|
),
|
|
),
|
|
),
|
|
// Quick version toggle
|
|
GestureDetector(
|
|
onTap: () => _showVersionPicker(context, ref),
|
|
child: Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
|
decoration: BoxDecoration(
|
|
color: AppColors.gold.withOpacity(0.15),
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
child: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Text(
|
|
user?.bibleTranslation.label ?? 'ESV',
|
|
style: GoogleFonts.outfit(
|
|
fontSize: 11,
|
|
fontWeight: FontWeight.w600,
|
|
color: AppColors.gold,
|
|
),
|
|
),
|
|
const SizedBox(width: 2),
|
|
Icon(Icons.arrow_drop_down, color: AppColors.gold, size: 16),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 12),
|
|
Text(
|
|
'"${scripture.getVerse(user?.bibleTranslation ?? BibleTranslation.esv)}"',
|
|
style: GoogleFonts.lora(
|
|
fontSize: 15,
|
|
fontStyle: FontStyle.italic,
|
|
color: AppColors.navyBlue,
|
|
height: 1.6,
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Text(
|
|
'— ${scripture.reference}',
|
|
style: GoogleFonts.outfit(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w500,
|
|
color: AppColors.warmGray,
|
|
),
|
|
),
|
|
GestureDetector(
|
|
onTap: _loadNewVerse,
|
|
child: Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
|
decoration: BoxDecoration(
|
|
color: AppColors.gold.withOpacity(0.1),
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
child: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Icon(Icons.refresh, color: AppColors.gold, size: 14),
|
|
const SizedBox(width: 4),
|
|
Text(
|
|
'New Verse',
|
|
style: GoogleFonts.outfit(
|
|
fontSize: 11,
|
|
fontWeight: FontWeight.w500,
|
|
color: AppColors.gold,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: 20),
|
|
|
|
// Prayer Button
|
|
SizedBox(
|
|
width: double.infinity,
|
|
child: OutlinedButton.icon(
|
|
onPressed: () => _showPrayerPrompt(context, phase),
|
|
icon: const Text('🙏', style: TextStyle(fontSize: 18)),
|
|
label: Text(
|
|
'Pray for ${wifeName}',
|
|
style: GoogleFonts.outfit(fontWeight: FontWeight.w500),
|
|
),
|
|
style: OutlinedButton.styleFrom(
|
|
foregroundColor: AppColors.navyBlue,
|
|
side: const BorderSide(color: AppColors.navyBlue),
|
|
padding: const EdgeInsets.symmetric(vertical: 14),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: 40),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
String _getPhaseHint(CyclePhase phase) {
|
|
switch (phase) {
|
|
case CyclePhase.menstrual:
|
|
return 'She may need extra rest';
|
|
case CyclePhase.follicular:
|
|
return 'Energy is returning';
|
|
case CyclePhase.ovulation:
|
|
return 'Fertile window';
|
|
case CyclePhase.luteal:
|
|
return 'PMS may occur';
|
|
}
|
|
}
|
|
|
|
String _getSupportTip(CyclePhase phase) {
|
|
switch (phase) {
|
|
case CyclePhase.menstrual:
|
|
return 'This is a time when she needs extra care. Help with household tasks without being asked. '
|
|
'Bring her favorite warm drink, suggest low-key activities, and be extra patient.';
|
|
case CyclePhase.follicular:
|
|
return 'Her energy is returning! This is a great time to plan dates, work on projects together, '
|
|
'and affirm her strengths. She may be more talkative and social.';
|
|
case CyclePhase.ovulation:
|
|
return 'Prioritize connection time. Romance and quality time matter. '
|
|
'If you\'re trying to conceive, this is your fertile window.';
|
|
case CyclePhase.luteal:
|
|
return 'Be patient—PMS may affect her mood. Listen more, "fix" less. '
|
|
'Take initiative on responsibilities and surprise her with comfort foods.';
|
|
}
|
|
}
|
|
|
|
void _showVersionPicker(BuildContext context, WidgetRef ref) {
|
|
showModalBottomSheet(
|
|
context: context,
|
|
shape: const RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
|
|
),
|
|
builder: (context) => Container(
|
|
padding: const EdgeInsets.all(20),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Choose Translation',
|
|
style: GoogleFonts.outfit(
|
|
fontSize: 20,
|
|
fontWeight: FontWeight.w600,
|
|
color: AppColors.navyBlue,
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
...BibleTranslation.values.map((translation) => ListTile(
|
|
title: Text(
|
|
translation.label,
|
|
style: GoogleFonts.outfit(fontWeight: FontWeight.w500),
|
|
),
|
|
trailing: ref.watch(userProfileProvider)?.bibleTranslation == translation
|
|
? const Icon(Icons.check, color: AppColors.sageGreen)
|
|
: null,
|
|
onTap: () async {
|
|
final profile = ref.read(userProfileProvider);
|
|
if (profile != null) {
|
|
await ref.read(userProfileProvider.notifier).updateProfile(
|
|
profile.copyWith(bibleTranslation: translation),
|
|
);
|
|
}
|
|
if (context.mounted) Navigator.pop(context);
|
|
},
|
|
)),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
void _showPrayerPrompt(BuildContext context, CyclePhase phase) {
|
|
showModalBottomSheet(
|
|
context: context,
|
|
shape: const RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
|
|
),
|
|
builder: (context) => Container(
|
|
padding: const EdgeInsets.all(24),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Text(
|
|
'🙏 Prayer for Your Wife',
|
|
style: GoogleFonts.outfit(
|
|
fontSize: 20,
|
|
fontWeight: FontWeight.w600,
|
|
color: AppColors.navyBlue,
|
|
),
|
|
),
|
|
const SizedBox(height: 20),
|
|
Text(
|
|
_getPrayer(phase),
|
|
textAlign: TextAlign.center,
|
|
style: GoogleFonts.lora(
|
|
fontSize: 16,
|
|
fontStyle: FontStyle.italic,
|
|
color: AppColors.charcoal,
|
|
height: 1.6,
|
|
),
|
|
),
|
|
const SizedBox(height: 24),
|
|
SizedBox(
|
|
width: double.infinity,
|
|
child: ElevatedButton(
|
|
onPressed: () => Navigator.pop(context),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: AppColors.navyBlue,
|
|
foregroundColor: Colors.white,
|
|
),
|
|
child: const Text('Amen'),
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
String _getPrayer(CyclePhase phase) {
|
|
switch (phase) {
|
|
case CyclePhase.menstrual:
|
|
return '"Lord, I lift up my wife during this time of rest. '
|
|
'Give her body the renewal it needs and grant her Your peace. '
|
|
'Help me to serve her with patience and love. Amen."';
|
|
case CyclePhase.follicular:
|
|
return '"Father, thank You for my wife\'s renewed energy. '
|
|
'Bless her endeavors and help me to encourage and support her. '
|
|
'May our partnership glorify You. Amen."';
|
|
case CyclePhase.ovulation:
|
|
return '"Creator God, You have designed my wife fearfully and wonderfully. '
|
|
'Whatever Your plans for our family, help us trust Your timing. '
|
|
'Bless our marriage and intimacy. Amen."';
|
|
case CyclePhase.luteal:
|
|
return '"Lord, be near to my wife during this phase. '
|
|
'When emotions are difficult, grant her Your peace that passes understanding. '
|
|
'Help me to be patient, kind, and understanding. Amen."';
|
|
}
|
|
}
|
|
}
|
|
|
|
class _HusbandTipsScreen extends StatelessWidget {
|
|
const _HusbandTipsScreen();
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return SafeArea(
|
|
child: SingleChildScrollView(
|
|
padding: const EdgeInsets.all(20),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Supporting Her',
|
|
style: GoogleFonts.outfit(
|
|
fontSize: 28,
|
|
fontWeight: FontWeight.w600,
|
|
color: AppColors.navyBlue,
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
'Practical ways to love your wife well',
|
|
style: GoogleFonts.outfit(
|
|
fontSize: 14,
|
|
color: AppColors.warmGray,
|
|
),
|
|
),
|
|
const SizedBox(height: 24),
|
|
_buildTipCategory('During Her Period', [
|
|
'🏠 Help with household tasks without being asked',
|
|
'🍵 Bring her favorite comfort drink',
|
|
'📺 Suggest low-key activities (movies, quiet time)',
|
|
'🙏 Pray for her physical comfort',
|
|
]),
|
|
const SizedBox(height: 16),
|
|
|
|
// Period Supplies (Dynamic)
|
|
Consumer(
|
|
builder: (context, ref, child) {
|
|
final user = ref.watch(userProfileProvider);
|
|
if (user == null || !user.isPadTrackingEnabled) return const SizedBox.shrink();
|
|
|
|
final brand = user.padBrand ?? 'Not specified';
|
|
final flow = user.typicalFlowIntensity;
|
|
|
|
return Column(
|
|
children: [
|
|
Container(
|
|
width: double.infinity,
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: AppColors.menstrualPhase.withOpacity(0.15),
|
|
borderRadius: BorderRadius.circular(16),
|
|
border: Border.all(color: AppColors.menstrualPhase.withOpacity(0.3)),
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Container(
|
|
padding: const EdgeInsets.all(8),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
child: const Icon(Icons.shopping_bag_outlined, color: AppColors.menstrualPhase, size: 20),
|
|
),
|
|
const SizedBox(width: 12),
|
|
Text(
|
|
'Period Supplies',
|
|
style: GoogleFonts.outfit(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.w600,
|
|
color: AppColors.navyBlue,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 12),
|
|
Text(
|
|
'She uses:',
|
|
style: GoogleFonts.outfit(fontSize: 14, color: AppColors.warmGray),
|
|
),
|
|
const SizedBox(height: 4),
|
|
Text(
|
|
brand,
|
|
style: GoogleFonts.outfit(fontSize: 18, fontWeight: FontWeight.bold, color: AppColors.navyBlue),
|
|
),
|
|
if (flow != null) ...[
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
'Typical Flow: $flow/5',
|
|
style: GoogleFonts.outfit(fontSize: 14, color: AppColors.charcoal),
|
|
),
|
|
],
|
|
if (user.padAbsorbency != null) ...[
|
|
const SizedBox(height: 4),
|
|
Text(
|
|
'Absorbency: ${user.padAbsorbency}/5',
|
|
style: GoogleFonts.outfit(fontSize: 14, color: AppColors.charcoal),
|
|
),
|
|
],
|
|
|
|
// Low Stock Warning
|
|
if (user.padInventoryCount <= user.lowInventoryThreshold) ...[
|
|
const SizedBox(height: 12),
|
|
Container(
|
|
width: double.infinity,
|
|
padding: const EdgeInsets.all(12),
|
|
decoration: BoxDecoration(
|
|
color: AppColors.rose.withOpacity(0.1),
|
|
borderRadius: BorderRadius.circular(12),
|
|
border: Border.all(color: AppColors.rose),
|
|
),
|
|
child: Column(
|
|
children: [
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
const Icon(Icons.warning_amber_rounded, color: AppColors.rose, size: 20),
|
|
const SizedBox(width: 8),
|
|
Text(
|
|
'LOW STOCK! (${user.padInventoryCount} left)',
|
|
style: GoogleFonts.outfit(
|
|
fontSize: 14,
|
|
fontWeight: FontWeight.bold,
|
|
color: AppColors.rose
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 4),
|
|
GestureDetector(
|
|
onTap: () {
|
|
// Navigate to settings
|
|
final parentState = context.findAncestorStateOfType<_HusbandHomeScreenState>();
|
|
if (parentState != null) {
|
|
parentState.setState(() {
|
|
parentState._selectedIndex = 5; // Settings tab
|
|
});
|
|
}
|
|
},
|
|
child: Text(
|
|
'Check Settings to Sync',
|
|
style: GoogleFonts.outfit(
|
|
fontSize: 12,
|
|
decoration: TextDecoration.underline,
|
|
color: AppColors.rose,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
],
|
|
);
|
|
},
|
|
),
|
|
|
|
_buildTipCategory('Follicular Phase', [
|
|
'🎉 Plan dates or activities—her energy is returning',
|
|
'💬 She may be more talkative and social',
|
|
'💪 Great time for projects together',
|
|
'❤️ Affirm her strengths and beauty',
|
|
]),
|
|
const SizedBox(height: 16),
|
|
_buildTipCategory('Luteal Phase (PMS)', [
|
|
'😌 Be patient—PMS may affect her mood',
|
|
'🍫 Surprise with comfort foods',
|
|
'🧹 Take initiative on responsibilities',
|
|
'👂 Listen more, "fix" less',
|
|
]),
|
|
const SizedBox(height: 16),
|
|
const SizedBox(height: 16),
|
|
|
|
// Her Favorites Section
|
|
Consumer(
|
|
builder: (context, ref, child) {
|
|
final user = ref.watch(userProfileProvider);
|
|
final favorites = user?.favoriteFoods;
|
|
|
|
if (favorites == null || favorites.isEmpty) return const SizedBox.shrink();
|
|
|
|
return Column(
|
|
children: [
|
|
_buildTipCategory('❤️ Her Favorites (Cheat Sheet)', favorites),
|
|
const SizedBox(height: 16),
|
|
],
|
|
);
|
|
},
|
|
),
|
|
|
|
_buildTipCategory('General Wisdom', [
|
|
'🗣️ Ask how she\'s feeling—and actually listen',
|
|
'📱 Put your phone down when she\'s talking',
|
|
'🌹 Small gestures matter more than grand ones',
|
|
'🙏 Pray for her daily',
|
|
]),
|
|
const SizedBox(height: 16),
|
|
_buildTipCategory("Men's Health", [
|
|
'💪 Exercise regularly to boost energy and mood',
|
|
'🥗 Eat a balanced diet rich in protein and vegetables',
|
|
'😴 Prioritize 7-8 hours of sleep for recovery',
|
|
'💧 Stay hydrated throughout the day',
|
|
'🧠 Practice stress management techniques',
|
|
]),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildTipCategory(String title, List<String> tips) {
|
|
return Container(
|
|
width: double.infinity,
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(16),
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
title,
|
|
style: GoogleFonts.outfit(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.w600,
|
|
color: AppColors.navyBlue,
|
|
),
|
|
),
|
|
const SizedBox(height: 12),
|
|
...tips.map((tip) => Padding(
|
|
padding: const EdgeInsets.only(bottom: 8),
|
|
child: Text(
|
|
tip,
|
|
style: GoogleFonts.outfit(
|
|
fontSize: 14,
|
|
color: AppColors.charcoal,
|
|
height: 1.4,
|
|
),
|
|
),
|
|
)),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _HusbandLearnScreen extends StatelessWidget {
|
|
const _HusbandLearnScreen();
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return SafeArea(
|
|
child: SingleChildScrollView(
|
|
padding: const EdgeInsets.all(20),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Learn',
|
|
style: GoogleFonts.outfit(
|
|
fontSize: 28,
|
|
fontWeight: FontWeight.w600,
|
|
color: AppColors.navyBlue,
|
|
),
|
|
),
|
|
const SizedBox(height: 24),
|
|
_buildSection(context, 'Understanding Her', [
|
|
_LearnItem(
|
|
icon: Icons.loop,
|
|
title: 'The 4 Phases of Her Cycle',
|
|
subtitle: 'What\'s happening in her body each month',
|
|
articleId: 'four_phases',
|
|
),
|
|
_LearnItem(
|
|
icon: Icons.psychology_outlined,
|
|
title: 'Why Does Her Mood Change?',
|
|
subtitle: 'Hormones explained simply',
|
|
articleId: 'mood_changes',
|
|
),
|
|
_LearnItem(
|
|
icon: Icons.medical_information_outlined,
|
|
title: 'PMS is Real',
|
|
subtitle: 'Medical facts for supportive husbands',
|
|
articleId: 'pms_is_real',
|
|
),
|
|
]),
|
|
const SizedBox(height: 24),
|
|
_buildSection(context, 'Biblical Manhood', [
|
|
_LearnItem(
|
|
icon: Icons.favorite,
|
|
title: 'Loving Like Christ',
|
|
subtitle: 'Ephesians 5 in daily practice',
|
|
articleId: 'loving_like_christ',
|
|
),
|
|
_LearnItem(
|
|
icon: Icons.handshake,
|
|
title: 'Servant Leadership at Home',
|
|
subtitle: 'What it really means',
|
|
articleId: 'servant_leadership',
|
|
),
|
|
_LearnItem(
|
|
icon: Icons.auto_awesome,
|
|
title: 'Praying for Your Wife',
|
|
subtitle: 'Practical guide',
|
|
articleId: 'praying_for_wife',
|
|
),
|
|
]),
|
|
const SizedBox(height: 24),
|
|
_buildSection(context, 'NFP for Husbands', [
|
|
_LearnItem(
|
|
icon: Icons.show_chart,
|
|
title: 'Reading the Charts Together',
|
|
subtitle: 'Understanding fertility signs',
|
|
articleId: 'reading_charts',
|
|
),
|
|
_LearnItem(
|
|
icon: Icons.schedule,
|
|
title: 'Abstinence as Spiritual Discipline',
|
|
subtitle: 'Growing together during fertile days',
|
|
articleId: 'abstinence_discipline',
|
|
),
|
|
]),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildSection(BuildContext context, String title, List<_LearnItem> items) {
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
title,
|
|
style: GoogleFonts.outfit(
|
|
fontSize: 14,
|
|
fontWeight: FontWeight.w500,
|
|
color: AppColors.warmGray,
|
|
letterSpacing: 0.5,
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Container(
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
child: Column(
|
|
children: items
|
|
.map((item) => ListTile(
|
|
leading: Container(
|
|
width: 40,
|
|
height: 40,
|
|
decoration: BoxDecoration(
|
|
color: AppColors.navyBlue.withOpacity(0.1),
|
|
borderRadius: BorderRadius.circular(10),
|
|
),
|
|
child: Icon(
|
|
item.icon,
|
|
color: AppColors.navyBlue,
|
|
size: 20,
|
|
),
|
|
),
|
|
title: Text(
|
|
item.title,
|
|
style: GoogleFonts.outfit(
|
|
fontSize: 15,
|
|
fontWeight: FontWeight.w500,
|
|
color: AppColors.charcoal,
|
|
),
|
|
),
|
|
subtitle: Text(
|
|
item.subtitle,
|
|
style: GoogleFonts.outfit(
|
|
fontSize: 13,
|
|
color: AppColors.warmGray,
|
|
),
|
|
),
|
|
trailing: const Icon(
|
|
Icons.chevron_right,
|
|
color: AppColors.lightGray,
|
|
),
|
|
onTap: () {
|
|
Navigator.push(
|
|
context,
|
|
MaterialPageRoute(
|
|
builder: (context) => LearnArticleScreen(articleId: item.articleId),
|
|
),
|
|
);
|
|
},
|
|
))
|
|
.toList(),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
class _LearnItem {
|
|
final IconData icon;
|
|
final String title;
|
|
final String subtitle;
|
|
final String articleId;
|
|
|
|
const _LearnItem({
|
|
required this.icon,
|
|
required this.title,
|
|
required this.subtitle,
|
|
required this.articleId,
|
|
});
|
|
}
|
|
|
|
class _HusbandSettingsScreen extends ConsumerWidget {
|
|
const _HusbandSettingsScreen();
|
|
|
|
Future<void> _resetApp(BuildContext context, WidgetRef ref) async {
|
|
final confirmed = await showDialog<bool>(
|
|
context: context,
|
|
builder: (context) => AlertDialog(
|
|
title: const Text('Reset App?'),
|
|
content: const Text(
|
|
'This will clear all data and return you to onboarding. Are you sure?'),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(context, false),
|
|
child: const Text('Cancel')),
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(context, true),
|
|
child: const Text('Reset', style: TextStyle(color: Colors.red)),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
|
|
if (confirmed == true) {
|
|
await ref.read(userProfileProvider.notifier).clearProfile();
|
|
await ref.read(cycleEntriesProvider.notifier).clearEntries();
|
|
|
|
if (context.mounted) {
|
|
Navigator.of(context).pushNamedAndRemoveUntil('/', (route) => false);
|
|
}
|
|
}
|
|
}
|
|
|
|
Future<void> _loadDemoData(BuildContext context, WidgetRef ref) async {
|
|
final confirmed = await showDialog<bool>(
|
|
context: context,
|
|
builder: (context) => AlertDialog(
|
|
title: const Text('Load Demo Data?'),
|
|
content: const Text(
|
|
'This will populate the app with mock cycle entries and a wife profile.'),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(context, false),
|
|
child: const Text('Cancel')),
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(context, true),
|
|
child:
|
|
const Text('Load Data', style: TextStyle(color: Colors.blue)),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
|
|
if (confirmed == true) {
|
|
final mockService = MockDataService();
|
|
// Load mock entries
|
|
final entries = mockService.generateMockCycleEntries();
|
|
for (var entry in entries) {
|
|
await ref.read(cycleEntriesProvider.notifier).addEntry(entry);
|
|
}
|
|
|
|
// Update mock profile
|
|
final mockWife = mockService.generateMockWifeProfile();
|
|
// Need to preserve current Husband ID and Role but take other data
|
|
final currentProfile = ref.read(userProfileProvider);
|
|
if (currentProfile != null) {
|
|
final updatedProfile = currentProfile.copyWith(
|
|
partnerName: mockWife.name,
|
|
averageCycleLength: mockWife.averageCycleLength,
|
|
averagePeriodLength: mockWife.averagePeriodLength,
|
|
lastPeriodStartDate: mockWife.lastPeriodStartDate,
|
|
favoriteFoods: mockWife.favoriteFoods,
|
|
);
|
|
await ref.read(userProfileProvider.notifier).updateProfile(updatedProfile);
|
|
}
|
|
}
|
|
}
|
|
|
|
void _showTranslationPicker(BuildContext context, WidgetRef ref) {
|
|
showModalBottomSheet(
|
|
context: context,
|
|
shape: const RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
|
|
),
|
|
builder: (context) => Container(
|
|
padding: const EdgeInsets.all(20),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Choose Translation',
|
|
style: GoogleFonts.outfit(
|
|
fontSize: 20,
|
|
fontWeight: FontWeight.w600,
|
|
color: AppColors.navyBlue,
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
...BibleTranslation.values.map((translation) => ListTile(
|
|
title: Text(
|
|
translation.label,
|
|
style: GoogleFonts.outfit(fontWeight: FontWeight.w500),
|
|
),
|
|
trailing: ref.watch(userProfileProvider)?.bibleTranslation == translation
|
|
? const Icon(Icons.check, color: AppColors.sageGreen)
|
|
: null,
|
|
onTap: () async {
|
|
final profile = ref.read(userProfileProvider);
|
|
if (profile != null) {
|
|
await ref.read(userProfileProvider.notifier).updateProfile(
|
|
profile.copyWith(bibleTranslation: translation),
|
|
);
|
|
}
|
|
if (context.mounted) Navigator.pop(context);
|
|
},
|
|
)),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
void _showConnectDialog(BuildContext context, WidgetRef ref) {
|
|
final codeController = TextEditingController();
|
|
bool shareDevotional = true;
|
|
|
|
showDialog(
|
|
context: context,
|
|
builder: (context) => StatefulBuilder(
|
|
builder: (context, setState) => AlertDialog(
|
|
title: Row(
|
|
children: [
|
|
const Icon(Icons.link, color: AppColors.navyBlue),
|
|
const SizedBox(width: 8),
|
|
const Text('Connect with Wife'),
|
|
],
|
|
),
|
|
content: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Enter the pairing code from your wife\'s app:',
|
|
style: GoogleFonts.outfit(fontSize: 14, color: AppColors.warmGray),
|
|
),
|
|
const SizedBox(height: 16),
|
|
TextField(
|
|
controller: codeController,
|
|
decoration: const InputDecoration(
|
|
hintText: 'e.g., ABC123',
|
|
border: OutlineInputBorder(),
|
|
),
|
|
textCapitalization: TextCapitalization.characters,
|
|
),
|
|
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),
|
|
),
|
|
const SizedBox(height: 24),
|
|
Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
SizedBox(
|
|
height: 24,
|
|
width: 24,
|
|
child: Checkbox(
|
|
value: shareDevotional,
|
|
onChanged: (val) => setState(() => shareDevotional = val ?? true),
|
|
activeColor: AppColors.navyBlue,
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Share Devotional Plans',
|
|
style: GoogleFonts.outfit(fontWeight: FontWeight.bold, fontSize: 14, color: AppColors.charcoal),
|
|
),
|
|
Text(
|
|
'Allow her to see the teaching plans you create.',
|
|
style: GoogleFonts.outfit(fontSize: 12, color: AppColors.warmGray),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(context),
|
|
child: const Text('Cancel'),
|
|
),
|
|
ElevatedButton(
|
|
onPressed: () async {
|
|
final code = codeController.text.trim();
|
|
|
|
Navigator.pop(context);
|
|
|
|
// Update preference
|
|
final user = ref.read(userProfileProvider);
|
|
if (user != null) {
|
|
await ref.read(userProfileProvider.notifier).updateProfile(
|
|
user.copyWith(isDataShared: shareDevotional)
|
|
);
|
|
}
|
|
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(
|
|
content: Text('Settings updated & Connected!'),
|
|
backgroundColor: AppColors.sageGreen,
|
|
),
|
|
);
|
|
|
|
if (code.isNotEmpty) {
|
|
// Load demo data as simulation
|
|
final mockService = MockDataService();
|
|
final entries = mockService.generateMockCycleEntries();
|
|
for (var entry in entries) {
|
|
await ref.read(cycleEntriesProvider.notifier).addEntry(entry);
|
|
}
|
|
final mockWife = mockService.generateMockWifeProfile();
|
|
final currentProfile = ref.read(userProfileProvider);
|
|
if (currentProfile != null) {
|
|
final updatedProfile = currentProfile.copyWith(
|
|
isDataShared: shareDevotional,
|
|
partnerName: mockWife.name,
|
|
averageCycleLength: mockWife.averageCycleLength,
|
|
averagePeriodLength: mockWife.averagePeriodLength,
|
|
lastPeriodStartDate: mockWife.lastPeriodStartDate,
|
|
favoriteFoods: mockWife.favoriteFoods,
|
|
);
|
|
await ref.read(userProfileProvider.notifier).updateProfile(updatedProfile);
|
|
}
|
|
}
|
|
},
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: AppColors.navyBlue,
|
|
foregroundColor: Colors.white,
|
|
),
|
|
child: const Text('Connect'),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
return SafeArea(
|
|
child: SingleChildScrollView(
|
|
padding: const EdgeInsets.all(20),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Settings',
|
|
style: GoogleFonts.outfit(
|
|
fontSize: 28,
|
|
fontWeight: FontWeight.w600,
|
|
color: AppColors.navyBlue,
|
|
),
|
|
),
|
|
const SizedBox(height: 24),
|
|
Container(
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
child: Column(
|
|
children: [
|
|
ListTile(
|
|
leading: const Icon(Icons.notifications_outlined,
|
|
color: AppColors.navyBlue),
|
|
title: Text('Notifications',
|
|
style: GoogleFonts.outfit(fontWeight: FontWeight.w500)),
|
|
trailing: Switch(value: true, onChanged: (val) {}),
|
|
),
|
|
const Divider(height: 1),
|
|
ListTile(
|
|
leading: const Icon(Icons.link,
|
|
color: AppColors.navyBlue),
|
|
title: Text('Connect with Wife',
|
|
style: GoogleFonts.outfit(fontWeight: FontWeight.w500)),
|
|
trailing: const Icon(Icons.chevron_right),
|
|
onTap: () => _showConnectDialog(context, ref),
|
|
),
|
|
const Divider(height: 1),
|
|
ListTile(
|
|
leading: const Icon(Icons.menu_book_outlined,
|
|
color: AppColors.navyBlue),
|
|
title: Text('Bible Translation',
|
|
style: GoogleFonts.outfit(fontWeight: FontWeight.w500)),
|
|
trailing: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Text(
|
|
ref.watch(userProfileProvider.select((u) => u?.bibleTranslation.label)) ?? 'ESV',
|
|
style: GoogleFonts.outfit(
|
|
fontSize: 14,
|
|
color: AppColors.warmGray,
|
|
),
|
|
),
|
|
const Icon(Icons.chevron_right),
|
|
],
|
|
),
|
|
onTap: () => _showTranslationPicker(context, ref),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: 20),
|
|
Container(
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
child: Column(
|
|
children: [
|
|
ListTile(
|
|
leading: const Icon(Icons.cloud_download_outlined,
|
|
color: Colors.blue),
|
|
title: Text('Load Demo Data',
|
|
style: GoogleFonts.outfit(
|
|
fontWeight: FontWeight.w500, color: Colors.blue)),
|
|
onTap: () => _loadDemoData(context, ref),
|
|
),
|
|
const Divider(height: 1),
|
|
ListTile(
|
|
leading: const Icon(Icons.logout, color: Colors.red),
|
|
title: Text('Reset App',
|
|
style: GoogleFonts.outfit(
|
|
fontWeight: FontWeight.w500, color: Colors.red)),
|
|
onTap: () => _resetApp(context, ref),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _HusbandWifeStatus extends ConsumerWidget {
|
|
const _HusbandWifeStatus();
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
final user = ref.watch(userProfileProvider);
|
|
final cycleInfo = ref.watch(currentCycleInfoProvider);
|
|
final entries = ref.watch(cycleEntriesProvider);
|
|
|
|
final wifeName = user?.partnerName ?? "Wife";
|
|
final phase = cycleInfo.phase;
|
|
final dayOfCycle = cycleInfo.dayOfCycle;
|
|
|
|
// Find today's entry
|
|
final todayEntry = entries.firstWhere(
|
|
(e) => DateUtils.isSameDay(e.date, DateTime.now()),
|
|
orElse: () => CycleEntry(
|
|
id: '',
|
|
date: DateTime.now(),
|
|
createdAt: DateTime.now(),
|
|
updatedAt: DateTime.now(),
|
|
),
|
|
);
|
|
|
|
return SafeArea(
|
|
child: SingleChildScrollView(
|
|
padding: const EdgeInsets.all(20),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Wife\'s Status',
|
|
style: GoogleFonts.outfit(
|
|
fontSize: 28,
|
|
fontWeight: FontWeight.w600,
|
|
color: AppColors.navyBlue,
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
'Real-time updates on how $wifeName is doing',
|
|
style: GoogleFonts.outfit(
|
|
fontSize: 14,
|
|
color: AppColors.warmGray,
|
|
),
|
|
),
|
|
const SizedBox(height: 24),
|
|
|
|
// Phase and Day summary
|
|
Container(
|
|
padding: const EdgeInsets.all(20),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(20),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: AppColors.navyBlue.withOpacity(0.05),
|
|
blurRadius: 10,
|
|
offset: const Offset(0, 4),
|
|
),
|
|
],
|
|
),
|
|
child: Row(
|
|
children: [
|
|
_buildStatusCircle(dayOfCycle, phase),
|
|
const SizedBox(width: 20),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
phase.label,
|
|
style: GoogleFonts.outfit(
|
|
fontSize: 20,
|
|
fontWeight: FontWeight.w600,
|
|
color: AppColors.navyBlue,
|
|
),
|
|
),
|
|
Text(
|
|
'Cycle Day $dayOfCycle',
|
|
style: GoogleFonts.outfit(
|
|
fontSize: 14,
|
|
color: AppColors.warmGray,
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
phase.description,
|
|
style: GoogleFonts.outfit(
|
|
fontSize: 13,
|
|
color: AppColors.charcoal.withOpacity(0.8),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: 24),
|
|
|
|
// Symptoms for Today
|
|
if (todayEntry.hasSymptoms || todayEntry.mood != null) ...[
|
|
Text(
|
|
'Today\'s Logs',
|
|
style: GoogleFonts.outfit(
|
|
fontSize: 18,
|
|
fontWeight: FontWeight.w600,
|
|
color: AppColors.navyBlue,
|
|
),
|
|
),
|
|
const SizedBox(height: 12),
|
|
Container(
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: AppColors.navyBlue.withOpacity(0.03),
|
|
borderRadius: BorderRadius.circular(16),
|
|
border:
|
|
Border.all(color: AppColors.navyBlue.withOpacity(0.05)),
|
|
),
|
|
child: Column(
|
|
children: [
|
|
if (todayEntry.mood != null)
|
|
_buildLogTile(Icons.emoji_emotions_outlined, 'Mood',
|
|
'${todayEntry.mood!.emoji} ${todayEntry.mood!.label}'),
|
|
if (todayEntry.hasSymptoms)
|
|
_buildLogTile(Icons.healing_outlined, 'Symptoms',
|
|
_getSymptomsSummary(todayEntry)),
|
|
if (todayEntry.energyLevel != null)
|
|
_buildLogTile(Icons.flash_on, 'Energy',
|
|
'${todayEntry.energyLevel}/5'),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: 24),
|
|
],
|
|
|
|
// Support Checklist
|
|
Text(
|
|
'Support Checklist',
|
|
style: GoogleFonts.outfit(
|
|
fontSize: 18,
|
|
fontWeight: FontWeight.w600,
|
|
color: AppColors.navyBlue,
|
|
),
|
|
),
|
|
const SizedBox(height: 12),
|
|
..._generateChecklist(todayEntry, phase)
|
|
.map((item) => _buildCheckItem(item)),
|
|
|
|
const SizedBox(height: 40),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildStatusCircle(int day, CyclePhase phase) {
|
|
return Container(
|
|
width: 70,
|
|
height: 70,
|
|
decoration: BoxDecoration(
|
|
color: phase.color.withOpacity(0.15),
|
|
shape: BoxShape.circle,
|
|
border: Border.all(color: phase.color.withOpacity(0.3), width: 2),
|
|
),
|
|
child: Center(
|
|
child: Text(
|
|
day.toString(),
|
|
style: GoogleFonts.outfit(
|
|
fontSize: 24,
|
|
fontWeight: FontWeight.w700,
|
|
color: phase.color,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildLogTile(IconData icon, String label, String value) {
|
|
return Padding(
|
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
|
child: Row(
|
|
children: [
|
|
Icon(icon, size: 20, color: AppColors.steelBlue),
|
|
const SizedBox(width: 12),
|
|
Text(
|
|
'$label: ',
|
|
style: GoogleFonts.outfit(fontWeight: FontWeight.w500, fontSize: 14),
|
|
),
|
|
Expanded(
|
|
child: Text(
|
|
value,
|
|
style: GoogleFonts.outfit(fontSize: 14, color: AppColors.charcoal),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildCheckItem(String text) {
|
|
return Container(
|
|
margin: const EdgeInsets.only(bottom: 12),
|
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(12),
|
|
border: Border.all(color: AppColors.navyBlue.withOpacity(0.05)),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Icon(Icons.check_circle_outline,
|
|
color: AppColors.sageGreen, size: 20),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: Text(
|
|
text,
|
|
style: GoogleFonts.outfit(fontSize: 14, color: AppColors.navyBlue),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
String _getSymptomsSummary(CycleEntry entry) {
|
|
List<String> s = [];
|
|
if (entry.crampIntensity != null && entry.crampIntensity! > 0)
|
|
s.add('Cramps');
|
|
if (entry.hasHeadache) s.add('Headache');
|
|
if (entry.hasBloating) s.add('Bloating');
|
|
if (entry.hasFatigue) s.add('Fatigue');
|
|
if (entry.hasLowerBackPain) s.add('Back Pain');
|
|
return s.isNotEmpty ? s.join(', ') : 'None';
|
|
}
|
|
|
|
List<String> _generateChecklist(CycleEntry entry, CyclePhase phase) {
|
|
List<String> list = [];
|
|
|
|
// Symptom-based tips
|
|
if (entry.crampIntensity != null && entry.crampIntensity! >= 3) {
|
|
list.add('Bring her a heating pad or hot water bottle.');
|
|
}
|
|
if (entry.hasHeadache) {
|
|
list.add('Suggest some quiet time with dimmed lights.');
|
|
}
|
|
if (entry.hasFatigue ||
|
|
(entry.energyLevel != null && entry.energyLevel! <= 2)) {
|
|
list.add('Take over dinner or household chores tonight.');
|
|
}
|
|
if (entry.mood == MoodLevel.sad || entry.mood == MoodLevel.verySad) {
|
|
list.add('Offer a listening ear and extra comfort.');
|
|
}
|
|
|
|
// Phase-based fallback tips
|
|
if (list.length < 3) {
|
|
switch (phase) {
|
|
case CyclePhase.menstrual:
|
|
list.add('Suggest a relaxing movie night.');
|
|
list.add('Bring her a warm tea or cocoa.');
|
|
break;
|
|
case CyclePhase.follicular:
|
|
list.add('Plan a fun outdoor activity.');
|
|
list.add('Compliment her renewed energy.');
|
|
break;
|
|
case CyclePhase.ovulation:
|
|
list.add('Plan a romantic date night.');
|
|
list.add('Focus on quality connection time.');
|
|
break;
|
|
case CyclePhase.luteal:
|
|
list.add('Surprise her with her favorite comfort snack.');
|
|
list.add('Be extra patient if she\'s easily frustrated.');
|
|
break;
|
|
}
|
|
}
|
|
|
|
return list.take(4).toList();
|
|
}
|
|
}
|
|
|