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:
2025-12-26 22:40:52 -06:00
parent 464692ce56
commit b4b2bfe749
47 changed files with 240110 additions and 2578 deletions

View File

@@ -1,10 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:google_fonts/google_fonts.dart';
<<<<<<< HEAD
import 'package:hive_flutter/hive_flutter.dart';
=======
>>>>>>> 6742220 (Your commit message here)
import '../../theme/app_theme.dart';
import '../../models/user_profile.dart';
import '../../models/cycle_entry.dart';
@@ -17,28 +13,10 @@ import '../../widgets/cycle_ring.dart';
import '../../widgets/scripture_card.dart';
import '../../widgets/quick_log_buttons.dart';
import '../../providers/user_provider.dart';
<<<<<<< HEAD
import '../../services/cycle_service.dart';
class HomeScreen extends ConsumerStatefulWidget {
const HomeScreen({super.key});
@override
ConsumerState<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends ConsumerState<HomeScreen> {
int _selectedIndex = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
body: IndexedStack(
index: _selectedIndex,
=======
import '../../providers/navigation_provider.dart';
import '../../services/cycle_service.dart';
import '../../services/bible_utils.dart';
import '../../providers/scripture_provider.dart'; // Import the new provider
class HomeScreen extends ConsumerWidget {
const HomeScreen({super.key});
@@ -50,45 +28,34 @@ class HomeScreen extends ConsumerWidget {
return Scaffold(
body: IndexedStack(
index: selectedIndex,
>>>>>>> 6742220 (Your commit message here)
children: [
const _DashboardTab(),
const CalendarScreen(),
const LogScreen(),
const DevotionalScreen(),
<<<<<<< HEAD
_SettingsTab(onReset: () => setState(() => _selectedIndex = 0)),
=======
_SettingsTab(onReset: () => ref.read(navigationProvider.notifier).setIndex(0)),
>>>>>>> 6742220 (Your commit message here)
_SettingsTab(
onReset: () =>
ref.read(navigationProvider.notifier).setIndex(0)),
],
),
bottomNavigationBar: Container(
decoration: BoxDecoration(
<<<<<<< HEAD
color: Colors.white,
boxShadow: [
BoxShadow(
color: AppColors.charcoal.withOpacity(0.1),
=======
color: Theme.of(context).bottomNavigationBarTheme.backgroundColor,
boxShadow: [
BoxShadow(
color: (Theme.of(context).brightness == Brightness.dark ? Colors.black : AppColors.charcoal).withOpacity(0.1),
>>>>>>> 6742220 (Your commit message here)
color: (Theme.of(context).brightness == Brightness.dark
? Colors.black
: AppColors.charcoal)
.withOpacity(0.1),
blurRadius: 10,
offset: const Offset(0, -2),
),
],
),
child: BottomNavigationBar(
<<<<<<< HEAD
currentIndex: _selectedIndex,
onTap: (index) => setState(() => _selectedIndex = index),
=======
currentIndex: selectedIndex,
onTap: (index) => ref.read(navigationProvider.notifier).setIndex(index),
>>>>>>> 6742220 (Your commit message here)
onTap: (index) =>
ref.read(navigationProvider.notifier).setIndex(index),
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.home_outlined),
@@ -122,34 +89,61 @@ class HomeScreen extends ConsumerWidget {
}
}
class _DashboardTab extends ConsumerWidget {
const _DashboardTab();
class _DashboardTab extends ConsumerStatefulWidget {
const _DashboardTab({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
<<<<<<< HEAD
final user = ref.watch(userProfileProvider);
ConsumerState<_DashboardTab> createState() => _DashboardTabState();
}
class _DashboardTabState extends ConsumerState<_DashboardTab> {
@override
void initState() {
super.initState();
_initializeScripture();
}
// This method initializes the scripture and can react to phase changes.
// It's called from initState and also when currentCycleInfoProvider changes.
Future<void> _initializeScripture() async {
final phase = ref.read(currentCycleInfoProvider).phase;
await ref.read(scriptureProvider.notifier).initializeScripture(phase);
}
@override
Widget build(BuildContext context) {
// Listen for changes in the cycle info to re-initialize scripture if needed
ref.listen<CycleInfo>(currentCycleInfoProvider, (previousCycleInfo, newCycleInfo) {
if (previousCycleInfo?.phase != newCycleInfo.phase) {
_initializeScripture();
}
});
final name =
ref.watch(userProfileProvider.select((u) => u?.name)) ?? 'Friend';
final translation =
ref.watch(userProfileProvider.select((u) => u?.bibleTranslation)) ??
BibleTranslation.esv;
final role = ref.watch(userProfileProvider.select((u) => u?.role)) ??
UserRole.wife;
final isMarried =
ref.watch(userProfileProvider.select((u) => u?.isMarried)) ?? false;
final averageCycleLength =
ref.watch(userProfileProvider.select((u) => u?.averageCycleLength)) ??
28;
final cycleInfo = ref.watch(currentCycleInfoProvider);
final name = user?.name ?? 'Friend';
final phase = cycleInfo['phase'] as CyclePhase;
final dayOfCycle = cycleInfo['dayOfCycle'] as int;
final cycleLength = user?.averageCycleLength ?? 28;
// Get scripture for current phase
=======
final name = ref.watch(userProfileProvider.select((u) => u?.name)) ?? 'Friend';
final translation = ref.watch(userProfileProvider.select((u) => u?.bibleTranslation)) ?? BibleTranslation.esv;
final role = ref.watch(userProfileProvider.select((u) => u?.role)) ?? UserRole.wife;
final isMarried = ref.watch(userProfileProvider.select((u) => u?.isMarried)) ?? false;
final averageCycleLength = ref.watch(userProfileProvider.select((u) => u?.averageCycleLength)) ?? 28;
final cycleInfo = ref.watch(currentCycleInfoProvider);
final phase = cycleInfo['phase'] as CyclePhase;
final dayOfCycle = cycleInfo['dayOfCycle'] as int;
>>>>>>> 6742220 (Your commit message here)
final scripture = ScriptureDatabase.getScriptureForPhase(phase.name);
final phase = cycleInfo.phase;
final dayOfCycle = cycleInfo.dayOfCycle;
// Watch the scripture provider for the current scripture
final scriptureState = ref.watch(scriptureProvider);
final scripture = scriptureState.currentScripture;
final maxIndex = scriptureState.maxIndex;
if (scripture == null) {
return const Center(child: CircularProgressIndicator()); // Or some error message
}
return SafeArea(
child: SingleChildScrollView(
@@ -157,40 +151,8 @@ class _DashboardTab extends ConsumerWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
<<<<<<< HEAD
// Greeting
_buildGreeting(name),
const SizedBox(height: 24),
// Cycle Ring
Center(
child: CycleRing(
dayOfCycle: dayOfCycle,
totalDays: cycleLength,
phase: phase,
),
),
const SizedBox(height: 24),
// Scripture Card
ScriptureCard(
verse: scripture.verse,
reference: scripture.reference,
phase: phase,
),
const SizedBox(height: 20),
// Quick Log Buttons
Text(
'Quick Log',
style: GoogleFonts.outfit(
fontSize: 18,
fontWeight: FontWeight.w600,
color: AppColors.charcoal,
=======
_buildGreeting(context, name),
const SizedBox(height: 24),
Center(
child: CycleRing(
dayOfCycle: dayOfCycle,
@@ -199,38 +161,65 @@ class _DashboardTab extends ConsumerWidget {
),
),
const SizedBox(height: 32),
ScriptureCard(
verse: scripture.getVerse(translation),
reference: scripture.reference,
translation: translation.label,
phase: phase,
onTranslationTap: () => BibleUtils.showTranslationPicker(context, ref),
// Main Scripture Card with Navigation
Stack(
alignment: Alignment.center,
children: [
ScriptureCard(
verse: scripture.getVerse(translation),
reference: scripture.reference,
translation: translation.label,
phase: phase,
onTranslationTap: () =>
BibleUtils.showTranslationPicker(context, ref),
),
if (maxIndex != null && maxIndex > 1) ...[
Positioned(
left: 0,
child: IconButton(
icon: Icon(Icons.arrow_back_ios),
onPressed: () =>
ref.read(scriptureProvider.notifier).getPreviousScripture(),
color: AppColors.charcoal,
),
),
Positioned(
right: 0,
child: IconButton(
icon: Icon(Icons.arrow_forward_ios),
onPressed: () =>
ref.read(scriptureProvider.notifier).getNextScripture(),
color: AppColors.charcoal,
),
),
],
],
),
const SizedBox(height: 16),
if (maxIndex != null && maxIndex > 1)
Center(
child: TextButton.icon(
onPressed: () => ref.read(scriptureProvider.notifier).getRandomScripture(),
icon: const Icon(Icons.shuffle),
label: const Text('Random Verse'),
style: TextButton.styleFrom(
foregroundColor: AppColors.sageGreen,
),
),
),
const SizedBox(height: 24),
Text(
'Quick Log',
style: Theme.of(context).textTheme.titleLarge?.copyWith(
fontSize: 18,
fontWeight: FontWeight.w600,
>>>>>>> 6742220 (Your commit message here)
),
fontSize: 18,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 12),
const QuickLogButtons(),
<<<<<<< HEAD
const SizedBox(height: 20),
// Today's Tip - Only show if not just tracking or husband (though husband has own screen)
if (user?.role == UserRole.wife)
TipCard(phase: phase, isMarried: user?.isMarried ?? false),
=======
const SizedBox(height: 24),
if (role == UserRole.wife)
TipCard(phase: phase, isMarried: isMarried),
>>>>>>> 6742220 (Your commit message here)
const SizedBox(height: 20),
],
),
@@ -238,12 +227,8 @@ class _DashboardTab extends ConsumerWidget {
);
}
<<<<<<< HEAD
Widget _buildGreeting(String name) {
=======
Widget _buildGreeting(BuildContext context, String name) {
final theme = Theme.of(context);
>>>>>>> 6742220 (Your commit message here)
final hour = DateTime.now().hour;
String greeting;
if (hour < 12) {
@@ -264,26 +249,15 @@ class _DashboardTab extends ConsumerWidget {
'$greeting,',
style: GoogleFonts.outfit(
fontSize: 16,
<<<<<<< HEAD
color: AppColors.warmGray,
=======
color: theme.colorScheme.onSurfaceVariant,
>>>>>>> 6742220 (Your commit message here)
),
),
Text(
name,
<<<<<<< HEAD
style: GoogleFonts.outfit(
fontSize: 28,
fontWeight: FontWeight.w600,
color: AppColors.charcoal,
=======
style: theme.textTheme.displaySmall?.copyWith(
fontSize: 28,
fontWeight: FontWeight.w600,
color: theme.colorScheme.onSurface,
>>>>>>> 6742220 (Your commit message here)
),
),
],
@@ -293,64 +267,41 @@ class _DashboardTab extends ConsumerWidget {
width: 48,
height: 48,
decoration: BoxDecoration(
<<<<<<< HEAD
color: AppColors.blushPink,
borderRadius: BorderRadius.circular(12),
),
child: const Icon(
Icons.notifications_outlined,
color: AppColors.rose,
=======
color: theme.colorScheme.primaryContainer.withOpacity(0.5),
borderRadius: BorderRadius.circular(12),
),
child: Icon(
Icons.notifications_outlined,
color: theme.colorScheme.primary,
>>>>>>> 6742220 (Your commit message here)
),
),
],
);
}
<<<<<<< HEAD
// Placeholder _calculateCycleInfo removed as it's now in CycleService
=======
>>>>>>> 6742220 (Your commit message here)
}
class _SettingsTab extends ConsumerWidget {
final VoidCallback? onReset;
const _SettingsTab({this.onReset});
Widget _buildSettingsTile(BuildContext context, IconData icon, String title, {VoidCallback? onTap}) {
Widget _buildSettingsTile(BuildContext context, IconData icon, String title,
{VoidCallback? onTap}) {
return ListTile(
<<<<<<< HEAD
leading: Icon(icon, color: AppColors.charcoal),
title: Text(
title,
style: GoogleFonts.outfit(
fontSize: 16,
color: AppColors.charcoal,
),
),
trailing: Icon(Icons.chevron_right, color: AppColors.lightGray),
=======
leading: Icon(icon, color: Theme.of(context).colorScheme.onSurface.withOpacity(0.8)),
leading: Icon(icon,
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.8)),
title: Text(
title,
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
fontSize: 16,
),
fontSize: 16,
),
),
trailing: const Icon(Icons.chevron_right, color: AppColors.lightGray),
>>>>>>> 6742220 (Your commit message here)
onTap: onTap ?? () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Settings coming soon!')),
);
},
onTap: onTap ??
() {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Settings coming soon!')),
);
},
);
}
@@ -359,11 +310,14 @@ class _SettingsTab 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)),
),
],
@@ -373,7 +327,7 @@ class _SettingsTab extends ConsumerWidget {
if (confirmed == true) {
await ref.read(userProfileProvider.notifier).clearProfile();
await ref.read(cycleEntriesProvider.notifier).clearEntries();
if (context.mounted) {
onReset?.call();
Navigator.of(context).pushNamedAndRemoveUntil('/', (route) => false);
@@ -383,14 +337,19 @@ class _SettingsTab extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
<<<<<<< HEAD
final user = ref.watch(userProfileProvider);
=======
final name = ref.watch(userProfileProvider.select((u) => u?.name)) ?? 'Guest';
final roleSymbol = ref.watch(userProfileProvider.select((u) => u?.role)) == UserRole.husband ? 'HUSBAND' : null;
final relationshipStatus = ref.watch(userProfileProvider.select((u) => u?.relationshipStatus.name.toUpperCase())) ?? 'SINGLE';
final translationLabel = ref.watch(userProfileProvider.select((u) => u?.bibleTranslation.label)) ?? 'ESV';
>>>>>>> 6742220 (Your commit message here)
final name =
ref.watch(userProfileProvider.select((u) => u?.name)) ?? 'Guest';
final roleSymbol =
ref.watch(userProfileProvider.select((u) => u?.role)) ==
UserRole.husband
? 'HUSBAND'
: null;
final relationshipStatus = ref.watch(userProfileProvider
.select((u) => u?.relationshipStatus.name.toUpperCase())) ??
'SINGLE';
final translationLabel =
ref.watch(userProfileProvider.select((u) => u?.bibleTranslation.label)) ??
'ESV';
return SafeArea(
child: SingleChildScrollView(
@@ -400,43 +359,21 @@ class _SettingsTab extends ConsumerWidget {
children: [
Text(
'Settings',
<<<<<<< HEAD
style: GoogleFonts.outfit(
fontSize: 28,
fontWeight: FontWeight.w600,
color: AppColors.charcoal,
=======
style: Theme.of(context).textTheme.displayMedium?.copyWith(
fontSize: 28,
fontWeight: FontWeight.w600,
color: Theme.of(context).colorScheme.onSurface,
>>>>>>> 6742220 (Your commit message here)
),
fontSize: 28,
fontWeight: FontWeight.w600,
color: Theme.of(context).colorScheme.onSurface,
),
),
const SizedBox(height: 24),
<<<<<<< HEAD
// Profile Card
Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: AppColors.charcoal.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
=======
Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Theme.of(context).cardTheme.color,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: Theme.of(context).colorScheme.outline.withOpacity(0.05)),
>>>>>>> 6742220 (Your commit message here)
border: Border.all(
color:
Theme.of(context).colorScheme.outline.withOpacity(0.05)),
),
child: Row(
children: [
@@ -445,7 +382,10 @@ class _SettingsTab extends ConsumerWidget {
height: 60,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [AppColors.blushPink, AppColors.rose.withOpacity(0.7)],
colors: [
AppColors.blushPink,
AppColors.rose.withOpacity(0.7)
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
@@ -453,11 +393,7 @@ class _SettingsTab extends ConsumerWidget {
),
child: Center(
child: Text(
<<<<<<< HEAD
user?.name.isNotEmpty == true ? user!.name[0].toUpperCase() : '?',
=======
name.isNotEmpty ? name[0].toUpperCase() : '?',
>>>>>>> 6742220 (Your commit message here)
style: GoogleFonts.outfit(
fontSize: 24,
fontWeight: FontWeight.w600,
@@ -472,28 +408,15 @@ class _SettingsTab extends ConsumerWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
<<<<<<< HEAD
user?.name ?? 'Guest',
style: GoogleFonts.outfit(
fontSize: 18,
fontWeight: FontWeight.w600,
color: AppColors.charcoal,
),
),
Text(
user?.role == UserRole.husband
? 'HUSBAND'
: (user?.relationshipStatus.name.toUpperCase() ?? 'SINGLE'),
=======
name,
style: Theme.of(context).textTheme.titleLarge?.copyWith(
fontSize: 18,
fontWeight: FontWeight.w600,
),
style:
Theme.of(context).textTheme.titleLarge?.copyWith(
fontSize: 18,
fontWeight: FontWeight.w600,
),
),
Text(
roleSymbol ?? relationshipStatus,
>>>>>>> 6742220 (Your commit message here)
style: GoogleFonts.outfit(
fontSize: 12,
letterSpacing: 1,
@@ -503,56 +426,48 @@ class _SettingsTab extends ConsumerWidget {
],
),
),
<<<<<<< HEAD
Icon(Icons.chevron_right, color: AppColors.warmGray),
=======
const Icon(Icons.chevron_right, color: AppColors.warmGray),
>>>>>>> 6742220 (Your commit message here)
],
),
),
const SizedBox(height: 24),
<<<<<<< HEAD
// Settings Groups
_buildSettingsGroup('Preferences', [
_buildSettingsTile(context, Icons.notifications_outlined, 'Notifications'),
=======
_buildSettingsGroup(context, 'Preferences', [
_buildSettingsTile(context, Icons.notifications_outlined, 'Notifications'),
_buildSettingsTile(
context,
Icons.book_outlined,
context, Icons.notifications_outlined, 'Notifications'),
_buildSettingsTile(
context,
Icons.book_outlined,
'Bible Version ($translationLabel)',
onTap: () => BibleUtils.showTranslationPicker(context, ref),
),
>>>>>>> 6742220 (Your commit message here)
_buildSettingsTile(context, Icons.palette_outlined, 'Appearance'),
_buildSettingsTile(context, Icons.lock_outline, 'Privacy'),
]),
const SizedBox(height: 16),
<<<<<<< HEAD
_buildSettingsGroup('Cycle', [
=======
_buildSettingsGroup(context, 'Cycle', [
>>>>>>> 6742220 (Your commit message here)
_buildSettingsTile(context, Icons.calendar_today_outlined, 'Cycle Settings'),
_buildSettingsTile(context, Icons.trending_up_outlined, 'Cycle History'),
_buildSettingsTile(context, Icons.download_outlined, 'Export Data'),
]),
const SizedBox(height: 16),
<<<<<<< HEAD
_buildSettingsGroup('Account', [
=======
_buildSettingsGroup(context, 'Account', [
>>>>>>> 6742220 (Your commit message here)
_buildSettingsTile(
context,
Icons.logout,
'Reset App / Logout',
onTap: () => _resetApp(context, ref)
context,
Icons.favorite_border,
'My Favorites',
onTap: () => _showFavoritesDialog(context, ref),
),
_buildSettingsTile(context, Icons.lock_outline, 'Privacy'),
_buildSettingsTile(
context,
Icons.share_outlined,
'Share with Husband',
onTap: () => _showShareDialog(context, ref),
),
]),
const SizedBox(height: 16),
_buildSettingsGroup(context, 'Cycle', [
_buildSettingsTile(
context, Icons.calendar_today_outlined, 'Cycle Settings'),
_buildSettingsTile(
context, Icons.trending_up_outlined, 'Cycle History'),
_buildSettingsTile(
context, Icons.download_outlined, 'Export Data'),
]),
const SizedBox(height: 16),
_buildSettingsGroup(context, 'Account', [
_buildSettingsTile(context, Icons.logout, 'Reset App / Logout',
onTap: () => _resetApp(context, ref)),
]),
const SizedBox(height: 16),
],
@@ -561,11 +476,8 @@ class _SettingsTab extends ConsumerWidget {
);
}
<<<<<<< HEAD
Widget _buildSettingsGroup(String title, List<Widget> tiles) {
=======
Widget _buildSettingsGroup(BuildContext context, String title, List<Widget> tiles) {
>>>>>>> 6742220 (Your commit message here)
Widget _buildSettingsGroup(
BuildContext context, String title, List<Widget> tiles) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@@ -581,14 +493,10 @@ class _SettingsTab extends ConsumerWidget {
const SizedBox(height: 8),
Container(
decoration: BoxDecoration(
<<<<<<< HEAD
color: Colors.white,
borderRadius: BorderRadius.circular(12),
=======
color: Theme.of(context).cardTheme.color,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Theme.of(context).colorScheme.outline.withOpacity(0.05)),
>>>>>>> 6742220 (Your commit message here)
border: Border.all(
color: Theme.of(context).colorScheme.outline.withOpacity(0.05)),
),
child: Column(
children: tiles,
@@ -597,8 +505,121 @@ class _SettingsTab extends ConsumerWidget {
],
);
}
}
<<<<<<< HEAD
=======
>>>>>>> 6742220 (Your commit message here)
void _showFavoritesDialog(BuildContext context, WidgetRef ref) {
final userProfile = ref.read(userProfileProvider);
if (userProfile == null) return;
final controller = TextEditingController(
text: userProfile.favoriteFoods?.join(', ') ?? '',
);
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('My Favorites', style: GoogleFonts.outfit(fontWeight: FontWeight.bold)),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'List your favorite comfort foods, snacks, or flowers so your husband knows what to get you!',
style: GoogleFonts.outfit(fontSize: 13, color: AppColors.warmGray),
),
const SizedBox(height: 16),
TextField(
controller: controller,
maxLines: 3,
decoration: const InputDecoration(
hintText: 'e.g., Dark Chocolate, Sushi, Sunflowers...',
border: OutlineInputBorder(),
),
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Cancel'),
),
ElevatedButton(
onPressed: () {
final favorites = controller.text
.split(',')
.map((e) => e.trim())
.where((e) => e.isNotEmpty)
.toList();
final updatedProfile = userProfile.copyWith(favoriteFoods: favorites);
ref.read(userProfileProvider.notifier).updateProfile(updatedProfile);
Navigator.pop(context);
},
child: const Text('Save'),
),
],
),
);
}
void _showShareDialog(BuildContext context, WidgetRef ref) {
// Generate a simple pairing code (in a real app, this would be stored/validated)
final userProfile = ref.read(userProfileProvider);
final pairingCode = userProfile?.id?.substring(0, 6).toUpperCase() ?? 'ABC123';
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Row(
children: [
const Icon(Icons.share_outlined, color: AppColors.sageGreen),
const SizedBox(width: 8),
const Text('Share with Husband'),
],
),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'Share this code with your husband so he can connect to your cycle data:',
style: GoogleFonts.outfit(fontSize: 14, color: AppColors.warmGray),
),
const SizedBox(height: 24),
Container(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
decoration: BoxDecoration(
color: AppColors.sageGreen.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: AppColors.sageGreen.withOpacity(0.3)),
),
child: SelectableText(
pairingCode,
style: GoogleFonts.outfit(
fontSize: 32,
fontWeight: FontWeight.bold,
letterSpacing: 4,
color: AppColors.sageGreen,
),
),
),
const SizedBox(height: 16),
Text(
'He can enter this in his app under Settings > Connect with Wife.',
style: GoogleFonts.outfit(fontSize: 12, color: AppColors.warmGray),
textAlign: TextAlign.center,
),
],
),
actions: [
ElevatedButton(
onPressed: () => Navigator.pop(context),
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.sageGreen,
foregroundColor: Colors.white,
),
child: const Text('Done'),
),
],
),
);
}
}