feat: Implement husband features and fix iOS Safari web startup
Implement initial features for husband's companion app, including mock data service and husband notes screen. Refactor scripture and cycle services for improved stability and testability. Address iOS Safari web app startup issue by removing deprecated initialization. - Implemented MockDataService and HusbandNotesScreen. - Converted _DashboardTab and DevotionalScreen to StatefulWidgets for robust scripture provider initialization. - Refactored CycleService to use immutable CycleInfo class, reducing UI rebuilds. - Removed deprecated window.flutterConfiguration from index.html, resolving Flutter web app startup failure on iOS Safari. - Updated and fixed related tests.
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
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';
|
||||
@@ -5,6 +6,10 @@ 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 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
/// Husband's companion app main screen
|
||||
@@ -28,10 +33,8 @@ class _HusbandHomeScreenState extends ConsumerState<HusbandHomeScreen> {
|
||||
index: _selectedIndex,
|
||||
children: [
|
||||
const _HusbandDashboard(),
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
const _HusbandWifeStatus(),
|
||||
>>>>>>> 6742220 (Your commit message here)
|
||||
const CalendarScreen(readOnly: true), // Reused Calendar
|
||||
const HusbandNotesScreen(), // Notes Screen
|
||||
const _HusbandTipsScreen(),
|
||||
const _HusbandLearnScreen(),
|
||||
const _HusbandSettingsScreen(),
|
||||
@@ -62,14 +65,16 @@ class _HusbandHomeScreenState extends ConsumerState<HusbandHomeScreen> {
|
||||
label: 'Home',
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
icon: Icon(Icons.favorite_border),
|
||||
activeIcon: Icon(Icons.favorite),
|
||||
label: 'Status',
|
||||
icon: Icon(Icons.calendar_month_outlined),
|
||||
activeIcon: Icon(Icons.calendar_month),
|
||||
label: 'Calendar',
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.note_alt_outlined),
|
||||
activeIcon: Icon(Icons.note_alt),
|
||||
label: 'Notes',
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
>>>>>>> 6742220 (Your commit message here)
|
||||
icon: Icon(Icons.lightbulb_outline),
|
||||
activeIcon: Icon(Icons.lightbulb),
|
||||
label: 'Tips',
|
||||
@@ -121,20 +126,39 @@ class _HusbandHomeScreenState extends ConsumerState<HusbandHomeScreen> {
|
||||
}
|
||||
}
|
||||
|
||||
class _HusbandDashboard extends ConsumerWidget {
|
||||
class _HusbandDashboard extends ConsumerStatefulWidget {
|
||||
const _HusbandDashboard();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
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'] as CyclePhase;
|
||||
final dayOfCycle = cycleInfo['dayOfCycle'] as int;
|
||||
final daysUntilPeriod = cycleInfo['daysUntilPeriod'] as int;
|
||||
|
||||
final scripture = ScriptureDatabase.getHusbandScripture();
|
||||
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(
|
||||
@@ -311,6 +335,87 @@ class _HusbandDashboard extends ConsumerWidget {
|
||||
),
|
||||
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,
|
||||
@@ -340,19 +445,47 @@ class _HusbandDashboard extends ConsumerWidget {
|
||||
size: 20,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'Scripture for Husbands',
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.warmGray,
|
||||
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.verse}"',
|
||||
'"${scripture.getVerse(user?.bibleTranslation ?? BibleTranslation.esv)}"',
|
||||
style: GoogleFonts.lora(
|
||||
fontSize: 15,
|
||||
fontStyle: FontStyle.italic,
|
||||
@@ -361,13 +494,43 @@ class _HusbandDashboard extends ConsumerWidget {
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'— ${scripture.reference}',
|
||||
style: GoogleFonts.outfit(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.warmGray,
|
||||
),
|
||||
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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -428,6 +591,51 @@ class _HusbandDashboard extends ConsumerWidget {
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
@@ -527,7 +735,6 @@ class _HusbandTipsScreen extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
_buildTipCategory('During Her Period', [
|
||||
'🏠 Help with household tasks without being asked',
|
||||
'🍵 Bring her favorite comfort drink',
|
||||
@@ -535,7 +742,6 @@ class _HusbandTipsScreen extends StatelessWidget {
|
||||
'🙏 Pray for her physical comfort',
|
||||
]),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
_buildTipCategory('Follicular Phase', [
|
||||
'🎉 Plan dates or activities—her energy is returning',
|
||||
'💬 She may be more talkative and social',
|
||||
@@ -543,7 +749,6 @@ class _HusbandTipsScreen extends StatelessWidget {
|
||||
'❤️ Affirm her strengths and beauty',
|
||||
]),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
_buildTipCategory('Luteal Phase (PMS)', [
|
||||
'😌 Be patient—PMS may affect her mood',
|
||||
'🍫 Surprise with comfort foods',
|
||||
@@ -551,6 +756,24 @@ class _HusbandTipsScreen extends StatelessWidget {
|
||||
'👂 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',
|
||||
@@ -621,55 +844,60 @@ class _HusbandLearnScreen extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
_buildSection('Understanding Her', [
|
||||
_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('Biblical Manhood', [
|
||||
_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('NFP for Husbands', [
|
||||
_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',
|
||||
),
|
||||
]),
|
||||
],
|
||||
@@ -678,7 +906,7 @@ class _HusbandLearnScreen extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSection(String title, List<_LearnItem> items) {
|
||||
Widget _buildSection(BuildContext context, String title, List<_LearnItem> items) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
@@ -732,7 +960,14 @@ class _HusbandLearnScreen extends StatelessWidget {
|
||||
Icons.chevron_right,
|
||||
color: AppColors.lightGray,
|
||||
),
|
||||
onTap: () {},
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => LearnArticleScreen(articleId: item.articleId),
|
||||
),
|
||||
);
|
||||
},
|
||||
))
|
||||
.toList(),
|
||||
),
|
||||
@@ -746,11 +981,13 @@ 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,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -762,11 +999,14 @@ class _HusbandSettingsScreen extends ConsumerWidget {
|
||||
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?'),
|
||||
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),
|
||||
onPressed: () => Navigator.pop(context, false),
|
||||
child: const Text('Cancel')),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
child: const Text('Reset', style: TextStyle(color: Colors.red)),
|
||||
),
|
||||
],
|
||||
@@ -776,13 +1016,191 @@ class _HusbandSettingsScreen extends ConsumerWidget {
|
||||
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();
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => 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),
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: const Text('Cancel'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () async {
|
||||
final code = codeController.text.trim();
|
||||
if (code.isEmpty) return;
|
||||
|
||||
// In a real app, this would validate the code against a backend
|
||||
// For now, we'll just show a success message and simulate pairing
|
||||
Navigator.pop(context);
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Connected! Loading wife\'s data...'),
|
||||
backgroundColor: AppColors.sageGreen,
|
||||
),
|
||||
);
|
||||
|
||||
// Load demo data as simulation of pairing
|
||||
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(
|
||||
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(
|
||||
@@ -808,30 +1226,69 @@ class _HusbandSettingsScreen extends ConsumerWidget {
|
||||
child: Column(
|
||||
children: [
|
||||
ListTile(
|
||||
leading: const Icon(Icons.notifications_outlined),
|
||||
title: Text('Notifications', style: GoogleFonts.outfit()),
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
onTap: () {},
|
||||
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_outlined),
|
||||
title: Text('Connection', style: GoogleFonts.outfit()),
|
||||
subtitle: Text('Linked with wife\'s app', style: GoogleFonts.outfit(fontSize: 12)),
|
||||
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: () {},
|
||||
onTap: () => _showConnectDialog(context, ref),
|
||||
),
|
||||
const Divider(height: 1),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.logout),
|
||||
title: Text('Reset App / Logout', style: GoogleFonts.outfit()),
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
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),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.help_outline),
|
||||
title: Text('Help & Support', style: GoogleFonts.outfit()),
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
onTap: () {},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -841,8 +1298,7 @@ class _HusbandSettingsScreen extends ConsumerWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
|
||||
class _HusbandWifeStatus extends ConsumerWidget {
|
||||
const _HusbandWifeStatus();
|
||||
|
||||
@@ -851,11 +1307,11 @@ class _HusbandWifeStatus extends ConsumerWidget {
|
||||
final user = ref.watch(userProfileProvider);
|
||||
final cycleInfo = ref.watch(currentCycleInfoProvider);
|
||||
final entries = ref.watch(cycleEntriesProvider);
|
||||
|
||||
|
||||
final wifeName = user?.partnerName ?? "Wife";
|
||||
final phase = cycleInfo['phase'] as CyclePhase;
|
||||
final dayOfCycle = cycleInfo['dayOfCycle'] as int;
|
||||
|
||||
final phase = cycleInfo.phase;
|
||||
final dayOfCycle = cycleInfo.dayOfCycle;
|
||||
|
||||
// Find today's entry
|
||||
final todayEntry = entries.firstWhere(
|
||||
(e) => DateUtils.isSameDay(e.date, DateTime.now()),
|
||||
@@ -960,16 +1416,20 @@ class _HusbandWifeStatus extends ConsumerWidget {
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.navyBlue.withOpacity(0.03),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(color: AppColors.navyBlue.withOpacity(0.05)),
|
||||
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}'),
|
||||
_buildLogTile(Icons.emoji_emotions_outlined, 'Mood',
|
||||
'${todayEntry.mood!.emoji} ${todayEntry.mood!.label}'),
|
||||
if (todayEntry.hasSymptoms)
|
||||
_buildLogTile(Icons.healing_outlined, 'Symptoms', _getSymptomsSummary(todayEntry)),
|
||||
_buildLogTile(Icons.healing_outlined, 'Symptoms',
|
||||
_getSymptomsSummary(todayEntry)),
|
||||
if (todayEntry.energyLevel != null)
|
||||
_buildLogTile(Icons.flash_on, 'Energy', '${todayEntry.energyLevel}/5'),
|
||||
_buildLogTile(Icons.flash_on, 'Energy',
|
||||
'${todayEntry.energyLevel}/5'),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -986,8 +1446,9 @@ class _HusbandWifeStatus extends ConsumerWidget {
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
..._generateChecklist(todayEntry, phase).map((item) => _buildCheckItem(item)),
|
||||
|
||||
..._generateChecklist(todayEntry, phase)
|
||||
.map((item) => _buildCheckItem(item)),
|
||||
|
||||
const SizedBox(height: 40),
|
||||
],
|
||||
),
|
||||
@@ -1050,7 +1511,8 @@ class _HusbandWifeStatus extends ConsumerWidget {
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.check_circle_outline, color: AppColors.sageGreen, size: 20),
|
||||
Icon(Icons.check_circle_outline,
|
||||
color: AppColors.sageGreen, size: 20),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
@@ -1065,7 +1527,8 @@ class _HusbandWifeStatus extends ConsumerWidget {
|
||||
|
||||
String _getSymptomsSummary(CycleEntry entry) {
|
||||
List<String> s = [];
|
||||
if (entry.crampIntensity != null && entry.crampIntensity! > 0) s.add('Cramps');
|
||||
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');
|
||||
@@ -1075,7 +1538,7 @@ class _HusbandWifeStatus extends ConsumerWidget {
|
||||
|
||||
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.');
|
||||
@@ -1083,7 +1546,8 @@ class _HusbandWifeStatus extends ConsumerWidget {
|
||||
if (entry.hasHeadache) {
|
||||
list.add('Suggest some quiet time with dimmed lights.');
|
||||
}
|
||||
if (entry.hasFatigue || (entry.energyLevel != null && entry.energyLevel! <= 2)) {
|
||||
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) {
|
||||
@@ -1111,8 +1575,8 @@ class _HusbandWifeStatus extends ConsumerWidget {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return list.take(4).toList();
|
||||
}
|
||||
}
|
||||
>>>>>>> 6742220 (Your commit message here)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user