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.
435 lines
16 KiB
Dart
435 lines
16 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:google_fonts/google_fonts.dart';
|
|
import '../../models/scripture.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import '../../providers/user_provider.dart';
|
|
import '../../services/cycle_service.dart';
|
|
import '../../models/cycle_entry.dart';
|
|
import '../../theme/app_theme.dart';
|
|
import '../../widgets/scripture_card.dart';
|
|
import '../../models/user_profile.dart';
|
|
import '../../providers/scripture_provider.dart'; // Import the new provider
|
|
|
|
class DevotionalScreen extends ConsumerStatefulWidget {
|
|
const DevotionalScreen({super.key});
|
|
|
|
@override
|
|
ConsumerState<DevotionalScreen> createState() => _DevotionalScreenState();
|
|
}
|
|
|
|
class _DevotionalScreenState extends ConsumerState<DevotionalScreen> {
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_initializeScripture();
|
|
}
|
|
|
|
Future<void> _initializeScripture() async {
|
|
final phase = ref.read(currentCycleInfoProvider).phase;
|
|
await ref.read(scriptureProvider.notifier).initializeScripture(phase);
|
|
}
|
|
|
|
Future<void> _showTranslationPicker(
|
|
BuildContext context, WidgetRef ref, UserProfile? user) async {
|
|
if (user == null) return;
|
|
|
|
final selected = await showModalBottomSheet<BibleTranslation>(
|
|
context: context,
|
|
shape: const RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
|
|
),
|
|
builder: (context) => Container(
|
|
padding: const EdgeInsets.symmetric(vertical: 20),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
|
child: Text(
|
|
'Select Bible Translation',
|
|
style: Theme.of(context).textTheme.titleLarge,
|
|
),
|
|
),
|
|
...BibleTranslation.values.map((t) => ListTile(
|
|
title: Text(t.label),
|
|
trailing: user.bibleTranslation == t
|
|
? Icon(Icons.check, color: AppColors.sageGreen)
|
|
: null,
|
|
onTap: () => Navigator.pop(context, t),
|
|
)),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
|
|
if (selected != null) {
|
|
await ref
|
|
.read(userProfileProvider.notifier)
|
|
.updateProfile(user.copyWith(bibleTranslation: selected));
|
|
}
|
|
}
|
|
|
|
@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 user = ref.watch(userProfileProvider);
|
|
final cycleInfo = ref.watch(currentCycleInfoProvider);
|
|
|
|
final phase = cycleInfo.phase;
|
|
|
|
// 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(
|
|
padding: const EdgeInsets.all(20),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// Header
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: Text(
|
|
'Today\'s Devotional',
|
|
style: GoogleFonts.outfit(
|
|
fontSize: 28,
|
|
fontWeight: FontWeight.w600,
|
|
color: AppColors.charcoal,
|
|
),
|
|
),
|
|
),
|
|
Container(
|
|
padding:
|
|
const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
|
decoration: BoxDecoration(
|
|
color: _getPhaseColor(phase).withOpacity(0.15),
|
|
borderRadius: BorderRadius.circular(20),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Text(phase.emoji),
|
|
const SizedBox(width: 4),
|
|
Text(
|
|
phase.label,
|
|
style: GoogleFonts.outfit(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w500,
|
|
color: _getPhaseColor(phase),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
phase.description,
|
|
style: GoogleFonts.outfit(
|
|
fontSize: 14,
|
|
color: AppColors.warmGray,
|
|
),
|
|
),
|
|
const SizedBox(height: 32),
|
|
|
|
// Main Scripture Card with Navigation
|
|
Stack(
|
|
alignment: Alignment.center,
|
|
children: [
|
|
ScriptureCard(
|
|
verse: scripture
|
|
.getVerse(user?.bibleTranslation ?? BibleTranslation.esv),
|
|
reference: scripture.reference,
|
|
translation:
|
|
(user?.bibleTranslation ?? BibleTranslation.esv).label,
|
|
phase: phase,
|
|
onTranslationTap: () =>
|
|
_showTranslationPicker(context, ref, user),
|
|
),
|
|
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),
|
|
|
|
// Reflection
|
|
if (scripture.reflection != null) ...[
|
|
Container(
|
|
width: double.infinity,
|
|
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),
|
|
),
|
|
],
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Icon(
|
|
Icons.lightbulb_outline,
|
|
color: AppColors.softGold,
|
|
size: 20,
|
|
),
|
|
const SizedBox(width: 8),
|
|
Text(
|
|
'Reflection',
|
|
style: GoogleFonts.outfit(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.w600,
|
|
color: AppColors.charcoal,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 12),
|
|
Text(
|
|
scripture.reflection!,
|
|
style: GoogleFonts.outfit(
|
|
fontSize: 15,
|
|
color: AppColors.charcoal,
|
|
height: 1.6,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
],
|
|
|
|
// Phase-specific encouragement
|
|
Container(
|
|
width: double.infinity,
|
|
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),
|
|
),
|
|
],
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Icon(
|
|
Icons.favorite_outline,
|
|
color: AppColors.rose,
|
|
size: 20,
|
|
),
|
|
const SizedBox(width: 8),
|
|
Text(
|
|
'For Your ${phase.label} Phase',
|
|
style: GoogleFonts.outfit(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.w600,
|
|
color: AppColors.charcoal,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 12),
|
|
Text(
|
|
_getPhaseEncouragement(phase, user?.isMarried ?? false),
|
|
style: GoogleFonts.outfit(
|
|
fontSize: 15,
|
|
color: AppColors.charcoal,
|
|
height: 1.6,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
|
|
// Prayer Prompt
|
|
Container(
|
|
width: double.infinity,
|
|
padding: const EdgeInsets.all(20),
|
|
decoration: BoxDecoration(
|
|
gradient: LinearGradient(
|
|
colors: [
|
|
AppColors.lavender.withOpacity(0.2),
|
|
AppColors.blushPink.withOpacity(0.2),
|
|
],
|
|
begin: Alignment.topLeft,
|
|
end: Alignment.bottomRight,
|
|
),
|
|
borderRadius: BorderRadius.circular(16),
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Text('🙏', style: TextStyle(fontSize: 20)),
|
|
const SizedBox(width: 8),
|
|
Text(
|
|
'Prayer Prompt',
|
|
style: GoogleFonts.outfit(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.w600,
|
|
color: AppColors.charcoal,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 12),
|
|
Text(
|
|
_getPrayerPrompt(phase),
|
|
style: GoogleFonts.lora(
|
|
fontSize: 14,
|
|
fontStyle: FontStyle.italic,
|
|
color: AppColors.charcoal,
|
|
height: 1.6,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: 24),
|
|
|
|
// Action buttons
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: OutlinedButton.icon(
|
|
onPressed: () {},
|
|
icon: const Icon(Icons.share_outlined),
|
|
label: const Text('Share'),
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: ElevatedButton.icon(
|
|
onPressed: () {},
|
|
icon: const Icon(Icons.edit_note),
|
|
label: const Text('Journal'),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 40),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// Placeholder _getCurrentPhase removed as it's now in CycleService
|
|
|
|
Color _getPhaseColor(CyclePhase phase) {
|
|
switch (phase) {
|
|
case CyclePhase.menstrual:
|
|
return AppColors.menstrualPhase;
|
|
case CyclePhase.follicular:
|
|
return AppColors.follicularPhase;
|
|
case CyclePhase.ovulation:
|
|
return AppColors.ovulationPhase;
|
|
case CyclePhase.luteal:
|
|
return AppColors.lutealPhase;
|
|
}
|
|
}
|
|
|
|
String _getPhaseEncouragement(CyclePhase phase, bool isMarried) {
|
|
switch (phase) {
|
|
case CyclePhase.menstrual:
|
|
return 'Your body is renewing itself. This is a sacred time for rest and reflection. '
|
|
'Don\'t push yourself too hard—God designed this phase for slowing down. '
|
|
'Use this time to draw near to Him in quietness.';
|
|
case CyclePhase.follicular:
|
|
return 'Energy is returning! Your body is preparing for the days ahead. '
|
|
'This is a wonderful time to tackle projects, connect with friends, and serve others. '
|
|
'Let your renewed strength be used for His purposes.';
|
|
case CyclePhase.ovulation:
|
|
if (isMarried) {
|
|
return 'You are in your most fertile window. Whether you\'re hoping to conceive or practicing NFP, '
|
|
'remember that God is sovereign over the womb. Trust His timing and purposes for your family.';
|
|
}
|
|
return 'You may feel more confident and social during this phase. '
|
|
'It\'s a great time for important conversations, presentations, or stepping out in faith. '
|
|
'Let your light shine before others.';
|
|
case CyclePhase.luteal:
|
|
return 'The luteal phase can bring challenging emotions and PMS symptoms. '
|
|
'Be patient with yourself. This is not weakness—it\'s your body doing what God designed. '
|
|
'Lean into His peace that surpasses understanding.';
|
|
}
|
|
}
|
|
|
|
String _getPrayerPrompt(CyclePhase phase) {
|
|
switch (phase) {
|
|
case CyclePhase.menstrual:
|
|
return '"Lord, thank You for designing my body with such wisdom. '
|
|
'Help me to rest in You during this time and to trust that You are renewing me. '
|
|
'May I find my strength in Your presence. Amen."';
|
|
case CyclePhase.follicular:
|
|
return '"Father, thank You for this season of renewed energy. '
|
|
'Guide me to use this strength for Your glory and the good of others. '
|
|
'Help me to serve with joy and purpose. Amen."';
|
|
case CyclePhase.ovulation:
|
|
return '"Creator God, I am fearfully and wonderfully made. '
|
|
'Thank You for the gift of womanhood. '
|
|
'Help me to honor You in all I do today. Amen."';
|
|
case CyclePhase.luteal:
|
|
return '"Lord, I bring my anxious thoughts to You. '
|
|
'When my emotions feel overwhelming, remind me of Your peace. '
|
|
'Help me to be gentle with myself as You are gentle with me. Amen."';
|
|
}
|
|
}
|
|
}
|