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.
791 lines
26 KiB
Dart
791 lines
26 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:google_fonts/google_fonts.dart';
|
|
import 'package:smooth_page_indicator/smooth_page_indicator.dart';
|
|
import 'package:hive_flutter/hive_flutter.dart';
|
|
import 'package:uuid/uuid.dart';
|
|
import '../../theme/app_theme.dart';
|
|
import 'package:christian_period_tracker/models/user_profile.dart';
|
|
import 'package:christian_period_tracker/models/cycle_entry.dart';
|
|
import '../home/home_screen.dart';
|
|
import '../husband/husband_home_screen.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import '../../providers/user_provider.dart';
|
|
|
|
class OnboardingScreen extends ConsumerStatefulWidget {
|
|
const OnboardingScreen({super.key});
|
|
|
|
@override
|
|
ConsumerState<OnboardingScreen> createState() => _OnboardingScreenState();
|
|
}
|
|
|
|
class _OnboardingScreenState extends ConsumerState<OnboardingScreen> {
|
|
final PageController _pageController = PageController();
|
|
int _currentPage = 0;
|
|
bool _isNavigating = false; // Debounce flag
|
|
|
|
// Form data
|
|
UserRole _role = UserRole.wife;
|
|
String _name = '';
|
|
RelationshipStatus _relationshipStatus = RelationshipStatus.single;
|
|
FertilityGoal? _fertilityGoal;
|
|
int _averageCycleLength = 28;
|
|
DateTime? _lastPeriodStart;
|
|
bool _isIrregularCycle = false;
|
|
|
|
@override
|
|
void dispose() {
|
|
_pageController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
void _nextPage() async {
|
|
if (_isNavigating) return;
|
|
_isNavigating = true;
|
|
|
|
// Husband Flow: Role (0) -> Name (1) -> Finish
|
|
// Wife Flow: Role (0) -> Name (1) -> Relationship (2) -> [Fertility (3)] -> Cycle (4)
|
|
|
|
int nextPage = _currentPage + 1;
|
|
|
|
// Logic for skipping pages
|
|
if (_role == UserRole.husband) {
|
|
if (_currentPage == 1) {
|
|
await _completeOnboarding();
|
|
return;
|
|
}
|
|
} else {
|
|
// Wife flow
|
|
if (_currentPage == 2 &&
|
|
_relationshipStatus != RelationshipStatus.married) {
|
|
// Skip fertility goal (page 3) if not married
|
|
nextPage = 4;
|
|
}
|
|
}
|
|
|
|
if (nextPage <= 4) {
|
|
// Max pages
|
|
await _pageController.animateToPage(
|
|
nextPage,
|
|
duration: const Duration(milliseconds: 400),
|
|
curve: Curves.easeInOut,
|
|
);
|
|
} else {
|
|
await _completeOnboarding();
|
|
}
|
|
|
|
// Reset debounce after animation
|
|
Future.delayed(const Duration(milliseconds: 500), () {
|
|
if (mounted) setState(() => _isNavigating = false);
|
|
});
|
|
}
|
|
|
|
void _previousPage() async {
|
|
if (_isNavigating) return;
|
|
_isNavigating = true;
|
|
|
|
int prevPage = _currentPage - 1;
|
|
|
|
// Logic for reverse skipping
|
|
if (_role == UserRole.wife) {
|
|
if (_currentPage == 4 &&
|
|
_relationshipStatus != RelationshipStatus.married) {
|
|
// Skip back over fertility goal (page 3)
|
|
prevPage = 2;
|
|
}
|
|
}
|
|
|
|
if (prevPage >= 0) {
|
|
await _pageController.animateToPage(
|
|
prevPage,
|
|
duration: const Duration(milliseconds: 400),
|
|
curve: Curves.easeInOut,
|
|
);
|
|
}
|
|
|
|
// Reset debounce after animation
|
|
Future.delayed(const Duration(milliseconds: 500), () {
|
|
if (mounted) setState(() => _isNavigating = false);
|
|
});
|
|
}
|
|
|
|
Future<void> _completeOnboarding() async {
|
|
final userProfile = UserProfile(
|
|
id: const Uuid().v4(),
|
|
name: _name,
|
|
role: _role,
|
|
relationshipStatus: _role == UserRole.husband
|
|
? RelationshipStatus.married
|
|
: _relationshipStatus,
|
|
fertilityGoal: (_role == UserRole.wife &&
|
|
_relationshipStatus == RelationshipStatus.married)
|
|
? _fertilityGoal
|
|
: null,
|
|
averageCycleLength: _averageCycleLength,
|
|
lastPeriodStartDate: _lastPeriodStart,
|
|
isIrregularCycle: _isIrregularCycle,
|
|
hasCompletedOnboarding: true,
|
|
createdAt: DateTime.now(),
|
|
updatedAt: DateTime.now(),
|
|
);
|
|
|
|
await ref.read(userProfileProvider.notifier).updateProfile(userProfile);
|
|
|
|
if (mounted) {
|
|
// Navigate to appropriate home screen
|
|
if (_role == UserRole.husband) {
|
|
Navigator.of(context).pushReplacement(
|
|
MaterialPageRoute(
|
|
builder: (_) => const HusbandHomeScreen(),
|
|
),
|
|
);
|
|
} else {
|
|
Navigator.of(context).pushReplacement(
|
|
MaterialPageRoute(builder: (_) => const HomeScreen()),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final theme = Theme.of(context);
|
|
final isDark = theme.brightness == Brightness.dark;
|
|
|
|
// Different background color for husband flow
|
|
final isHusband = _role == UserRole.husband;
|
|
final bgColor = isHusband
|
|
? (isDark ? const Color(0xFF1A1C1E) : AppColors.warmCream)
|
|
: theme.scaffoldBackgroundColor;
|
|
|
|
return Scaffold(
|
|
backgroundColor: bgColor,
|
|
body: SafeArea(
|
|
child: Column(
|
|
children: [
|
|
// Progress indicator (hide on role page 0)
|
|
if (_currentPage > 0)
|
|
Padding(
|
|
padding: const EdgeInsets.all(24),
|
|
child: SmoothPageIndicator(
|
|
controller: _pageController,
|
|
count: isHusband ? 2 : 5,
|
|
effect: WormEffect(
|
|
dotHeight: 8,
|
|
dotWidth: 8,
|
|
spacing: 12,
|
|
activeDotColor:
|
|
isHusband ? AppColors.navyBlue : AppColors.sageGreen,
|
|
dotColor: theme.colorScheme.outline.withOpacity(0.2),
|
|
),
|
|
),
|
|
),
|
|
|
|
// Pages
|
|
Expanded(
|
|
child: PageView(
|
|
controller: _pageController,
|
|
physics:
|
|
const NeverScrollableScrollPhysics(), // Disable swipe
|
|
onPageChanged: (index) {
|
|
setState(() => _currentPage = index);
|
|
},
|
|
children: [
|
|
_buildRolePage(), // Page 0
|
|
_buildNamePage(), // Page 1
|
|
_buildRelationshipPage(), // Page 2 (Wife only)
|
|
_buildFertilityGoalPage(), // Page 3 (Wife married only)
|
|
_buildCyclePage(), // Page 4 (Wife only)
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildRolePage() {
|
|
final theme = Theme.of(context);
|
|
|
|
return Padding(
|
|
padding: const EdgeInsets.all(32),
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Container(
|
|
width: 80,
|
|
height: 80,
|
|
decoration: BoxDecoration(
|
|
gradient: LinearGradient(
|
|
colors: [
|
|
AppColors.blushPink,
|
|
AppColors.rose.withOpacity(0.7)
|
|
],
|
|
begin: Alignment.topLeft,
|
|
end: Alignment.bottomRight,
|
|
),
|
|
borderRadius: BorderRadius.circular(20),
|
|
),
|
|
child: const Icon(
|
|
Icons.favorite_rounded,
|
|
size: 40,
|
|
color: Colors.white,
|
|
),
|
|
),
|
|
const SizedBox(height: 32),
|
|
Text(
|
|
'Who is this app for?',
|
|
textAlign: TextAlign.center,
|
|
style: theme.textTheme.displayMedium?.copyWith(
|
|
fontSize: 28,
|
|
fontWeight: FontWeight.w600,
|
|
color: theme.colorScheme.onSurface,
|
|
),
|
|
),
|
|
const SizedBox(height: 48),
|
|
_buildRoleOption(UserRole.wife, 'For Her',
|
|
'Track cycle, health, and faith', Icons.female),
|
|
const SizedBox(height: 16),
|
|
_buildRoleOption(UserRole.husband, 'For Him',
|
|
'Support your wife and grow together', Icons.male),
|
|
const Spacer(),
|
|
SizedBox(
|
|
width: double.infinity,
|
|
height: 54,
|
|
child: ElevatedButton(
|
|
onPressed: _nextPage,
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: _role == UserRole.husband
|
|
? AppColors.navyBlue
|
|
: AppColors.sageGreen,
|
|
),
|
|
child: const Text('Continue'),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildRoleOption(
|
|
UserRole role, String title, String subtitle, IconData icon) {
|
|
final theme = Theme.of(context);
|
|
final isDark = theme.brightness == Brightness.dark;
|
|
final isSelected = _role == role;
|
|
|
|
// Dynamic colors based on role selection
|
|
final activeColor =
|
|
role == UserRole.wife ? AppColors.sageGreen : AppColors.navyBlue;
|
|
final activeBg = activeColor.withOpacity(isDark ? 0.3 : 0.1);
|
|
|
|
return GestureDetector(
|
|
onTap: () => setState(() => _role = role),
|
|
child: AnimatedContainer(
|
|
duration: const Duration(milliseconds: 200),
|
|
padding: const EdgeInsets.all(20),
|
|
decoration: BoxDecoration(
|
|
color: isSelected ? activeBg : theme.cardTheme.color,
|
|
borderRadius: BorderRadius.circular(16),
|
|
border: Border.all(
|
|
color: isSelected
|
|
? activeColor
|
|
: theme.colorScheme.outline.withOpacity(0.1),
|
|
width: isSelected ? 2 : 1,
|
|
),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Container(
|
|
padding: const EdgeInsets.all(12),
|
|
decoration: BoxDecoration(
|
|
color: isSelected
|
|
? activeColor
|
|
: theme.colorScheme.surfaceVariant,
|
|
shape: BoxShape.circle,
|
|
),
|
|
child: Icon(
|
|
icon,
|
|
color: isSelected
|
|
? Colors.white
|
|
: theme.colorScheme.onSurfaceVariant,
|
|
size: 24,
|
|
),
|
|
),
|
|
const SizedBox(width: 16),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
title,
|
|
style: theme.textTheme.titleLarge?.copyWith(
|
|
fontSize: 18,
|
|
fontWeight: FontWeight.w600,
|
|
color: theme.colorScheme.onSurface,
|
|
),
|
|
),
|
|
const SizedBox(height: 4),
|
|
Text(
|
|
subtitle,
|
|
style: theme.textTheme.bodyMedium?.copyWith(
|
|
color: theme.colorScheme.onSurfaceVariant,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
if (isSelected)
|
|
Icon(Icons.check_circle, color: activeColor),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildNamePage() {
|
|
final theme = Theme.of(context);
|
|
final isHusband = _role == UserRole.husband;
|
|
final activeColor =
|
|
isHusband ? AppColors.navyBlue : AppColors.sageGreen;
|
|
|
|
return Padding(
|
|
padding: const EdgeInsets.all(32),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
const SizedBox(height: 40),
|
|
Text(
|
|
isHusband ? 'What\'s your name, sir?' : 'What\'s your name?',
|
|
style: theme.textTheme.displaySmall?.copyWith(
|
|
fontSize: 28,
|
|
fontWeight: FontWeight.w600,
|
|
color: theme.colorScheme.onSurface,
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
'We\'ll use this to personalize the app.',
|
|
style: theme.textTheme.bodyMedium?.copyWith(
|
|
color: theme.colorScheme.onSurfaceVariant,
|
|
),
|
|
),
|
|
const SizedBox(height: 32),
|
|
TextField(
|
|
onChanged: (value) => setState(() => _name = value),
|
|
decoration: InputDecoration(
|
|
hintText: 'Enter your name',
|
|
prefixIcon: Icon(
|
|
Icons.person_outline,
|
|
color: theme.colorScheme.onSurfaceVariant,
|
|
),
|
|
focusedBorder: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
borderSide: BorderSide(color: activeColor, width: 2),
|
|
),
|
|
),
|
|
style: theme.textTheme.bodyLarge,
|
|
textCapitalization: TextCapitalization.words,
|
|
),
|
|
const Spacer(),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: SizedBox(
|
|
height: 54,
|
|
child: OutlinedButton(
|
|
onPressed: _previousPage,
|
|
style: OutlinedButton.styleFrom(
|
|
foregroundColor: activeColor,
|
|
side: BorderSide(color: activeColor),
|
|
),
|
|
child: const Text('Back'),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: 16),
|
|
Expanded(
|
|
child: SizedBox(
|
|
height: 54,
|
|
child: ElevatedButton(
|
|
onPressed: (_name.isNotEmpty && !_isNavigating)
|
|
? _nextPage
|
|
: null,
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: activeColor,
|
|
),
|
|
child: Text(isHusband ? 'Finish Setup' : 'Continue'),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildRelationshipPage() {
|
|
final theme = Theme.of(context);
|
|
|
|
return Padding(
|
|
padding: const EdgeInsets.all(32),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
const SizedBox(height: 40),
|
|
Text(
|
|
'Tell us about yourself',
|
|
style: theme.textTheme.displaySmall?.copyWith(
|
|
fontSize: 28,
|
|
fontWeight: FontWeight.w600,
|
|
color: theme.colorScheme.onSurface,
|
|
),
|
|
),
|
|
const SizedBox(height: 32),
|
|
_buildRelationshipOption(RelationshipStatus.single, 'Single',
|
|
'Wellness focus', Icons.person_outline),
|
|
const SizedBox(height: 12),
|
|
_buildRelationshipOption(RelationshipStatus.engaged, 'Engaged',
|
|
'Prepare for marriage', Icons.favorite_border),
|
|
const SizedBox(height: 12),
|
|
_buildRelationshipOption(RelationshipStatus.married, 'Married',
|
|
'Fertility & intimacy', Icons.favorite),
|
|
const Spacer(),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: SizedBox(
|
|
height: 54,
|
|
child: OutlinedButton(
|
|
onPressed: _previousPage,
|
|
style: OutlinedButton.styleFrom(
|
|
foregroundColor: AppColors.sageGreen,
|
|
side: const BorderSide(color: AppColors.sageGreen)),
|
|
child: const Text('Back'),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: 16),
|
|
Expanded(
|
|
child: SizedBox(
|
|
height: 54,
|
|
child: ElevatedButton(
|
|
onPressed: (_relationshipStatus != null && !_isNavigating)
|
|
? _nextPage
|
|
: null,
|
|
child: const Text('Continue'),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildRelationshipOption(
|
|
RelationshipStatus status, String title, String subtitle, IconData icon) {
|
|
final theme = Theme.of(context);
|
|
final isDark = theme.brightness == Brightness.dark;
|
|
final isSelected = _relationshipStatus == status;
|
|
|
|
return GestureDetector(
|
|
onTap: () => setState(() => _relationshipStatus = status),
|
|
child: AnimatedContainer(
|
|
duration: const Duration(milliseconds: 200),
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: isSelected
|
|
? AppColors.sageGreen.withOpacity(isDark ? 0.3 : 0.1)
|
|
: theme.cardTheme.color,
|
|
borderRadius: BorderRadius.circular(12),
|
|
border: Border.all(
|
|
color: isSelected
|
|
? AppColors.sageGreen
|
|
: theme.colorScheme.outline.withOpacity(0.1),
|
|
width: isSelected ? 2 : 1,
|
|
),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Icon(icon,
|
|
color: isSelected
|
|
? AppColors.sageGreen
|
|
: theme.colorScheme.onSurfaceVariant),
|
|
const SizedBox(width: 16),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(title,
|
|
style: theme.textTheme.titleMedium?.copyWith(
|
|
fontWeight: FontWeight.w600,
|
|
color: theme.colorScheme.onSurface)),
|
|
Text(subtitle,
|
|
style: theme.textTheme.bodySmall?.copyWith(
|
|
color: theme.colorScheme.onSurfaceVariant)),
|
|
],
|
|
),
|
|
),
|
|
if (isSelected)
|
|
Icon(Icons.check_circle, color: AppColors.sageGreen),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildFertilityGoalPage() {
|
|
final theme = Theme.of(context);
|
|
|
|
return Padding(
|
|
padding: const EdgeInsets.all(32),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
const SizedBox(height: 40),
|
|
Text('What\'s your goal?',
|
|
style: theme.textTheme.displaySmall?.copyWith(
|
|
fontSize: 28,
|
|
fontWeight: FontWeight.w600,
|
|
color: theme.colorScheme.onSurface)),
|
|
const SizedBox(height: 32),
|
|
_buildGoalOption(
|
|
FertilityGoal.tryingToConceive,
|
|
'Trying to Conceive',
|
|
'Track fertile days',
|
|
Icons.child_care_outlined),
|
|
const SizedBox(height: 12),
|
|
_buildGoalOption(
|
|
FertilityGoal.tryingToAvoid,
|
|
'Natural Family Planning',
|
|
'Track fertility signs',
|
|
Icons.calendar_today_outlined),
|
|
const SizedBox(height: 12),
|
|
_buildGoalOption(FertilityGoal.justTracking, 'Just Tracking',
|
|
'Monitor cycle health', Icons.insights_outlined),
|
|
const Spacer(),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: SizedBox(
|
|
height: 54,
|
|
child: OutlinedButton(
|
|
onPressed: _previousPage,
|
|
style: OutlinedButton.styleFrom(
|
|
foregroundColor: AppColors.sageGreen,
|
|
side: const BorderSide(color: AppColors.sageGreen)),
|
|
child: const Text('Back'),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: 16),
|
|
Expanded(
|
|
child: SizedBox(
|
|
height: 54,
|
|
child: ElevatedButton(
|
|
onPressed: (_fertilityGoal != null && !_isNavigating)
|
|
? _nextPage
|
|
: null,
|
|
child: const Text('Continue'),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildGoalOption(
|
|
FertilityGoal goal, String title, String subtitle, IconData icon) {
|
|
final theme = Theme.of(context);
|
|
final isDark = theme.brightness == Brightness.dark;
|
|
final isSelected = _fertilityGoal == goal;
|
|
|
|
return GestureDetector(
|
|
onTap: () => setState(() => _fertilityGoal = goal),
|
|
child: AnimatedContainer(
|
|
duration: const Duration(milliseconds: 200),
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: isSelected
|
|
? AppColors.sageGreen.withOpacity(isDark ? 0.3 : 0.1)
|
|
: theme.cardTheme.color,
|
|
borderRadius: BorderRadius.circular(12),
|
|
border: Border.all(
|
|
color: isSelected
|
|
? AppColors.sageGreen
|
|
: theme.colorScheme.outline.withOpacity(0.1),
|
|
width: isSelected ? 2 : 1,
|
|
),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Icon(icon,
|
|
color: isSelected
|
|
? AppColors.sageGreen
|
|
: theme.colorScheme.onSurfaceVariant),
|
|
const SizedBox(width: 16),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(title,
|
|
style: theme.textTheme.titleMedium?.copyWith(
|
|
fontWeight: FontWeight.w600,
|
|
color: theme.colorScheme.onSurface)),
|
|
Text(subtitle,
|
|
style: theme.textTheme.bodySmall?.copyWith(
|
|
color: theme.colorScheme.onSurfaceVariant)),
|
|
],
|
|
),
|
|
),
|
|
if (isSelected)
|
|
Icon(Icons.check_circle, color: AppColors.sageGreen),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildCyclePage() {
|
|
final theme = Theme.of(context);
|
|
final isDark = theme.brightness == Brightness.dark;
|
|
|
|
return Padding(
|
|
padding: const EdgeInsets.all(32),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
const SizedBox(height: 40),
|
|
Text('About your cycle',
|
|
style: theme.textTheme.displaySmall?.copyWith(
|
|
fontSize: 28,
|
|
fontWeight: FontWeight.w600,
|
|
color: theme.colorScheme.onSurface)),
|
|
const SizedBox(height: 32),
|
|
Text('Average cycle length',
|
|
style: theme.textTheme.titleMedium?.copyWith(
|
|
fontWeight: FontWeight.w500,
|
|
color: theme.colorScheme.onSurface)),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: Slider(
|
|
value: _averageCycleLength.toDouble(),
|
|
min: 21,
|
|
max: 40,
|
|
divisions: 19,
|
|
activeColor: AppColors.sageGreen,
|
|
onChanged: (value) =>
|
|
setState(() => _averageCycleLength = value.round()),
|
|
),
|
|
),
|
|
Text('$_averageCycleLength days',
|
|
style: theme.textTheme.titleMedium?.copyWith(
|
|
fontWeight: FontWeight.w600,
|
|
color: AppColors.sageGreen)),
|
|
],
|
|
),
|
|
|
|
// Irregular Cycle Checkbox
|
|
CheckboxListTile(
|
|
title: Text('My cycles are irregular',
|
|
style: theme.textTheme.bodyLarge
|
|
?.copyWith(color: theme.colorScheme.onSurface)),
|
|
value: _isIrregularCycle,
|
|
onChanged: (val) =>
|
|
setState(() => _isIrregularCycle = val ?? false),
|
|
activeColor: AppColors.sageGreen,
|
|
contentPadding: EdgeInsets.zero,
|
|
controlAffinity: ListTileControlAffinity.leading,
|
|
),
|
|
|
|
const SizedBox(height: 24),
|
|
Text('Last period start date',
|
|
style: theme.textTheme.titleMedium?.copyWith(
|
|
fontWeight: FontWeight.w500,
|
|
color: theme.colorScheme.onSurface)),
|
|
const SizedBox(height: 8),
|
|
GestureDetector(
|
|
onTap: () async {
|
|
final date = await showDatePicker(
|
|
context: context,
|
|
initialDate: _lastPeriodStart ?? DateTime.now(),
|
|
firstDate: DateTime.now().subtract(const Duration(days: 60)),
|
|
lastDate: DateTime.now(),
|
|
builder: (context, child) {
|
|
return Theme(
|
|
data: theme.copyWith(
|
|
colorScheme: theme.colorScheme.copyWith(
|
|
primary: AppColors.sageGreen,
|
|
onPrimary: Colors.white,
|
|
),
|
|
),
|
|
child: child!,
|
|
);
|
|
},
|
|
);
|
|
if (date != null) setState(() => _lastPeriodStart = date);
|
|
},
|
|
child: Container(
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: theme.cardTheme.color,
|
|
borderRadius: BorderRadius.circular(12),
|
|
border: Border.all(
|
|
color: theme.colorScheme.outline.withOpacity(0.1))),
|
|
child: Row(
|
|
children: [
|
|
Icon(Icons.calendar_today,
|
|
color: theme.colorScheme.onSurfaceVariant),
|
|
const SizedBox(width: 12),
|
|
Text(
|
|
_lastPeriodStart != null
|
|
? "${_lastPeriodStart!.month}/${_lastPeriodStart!.day}/${_lastPeriodStart!.year}"
|
|
: "Select Date",
|
|
style: theme.textTheme.bodyLarge
|
|
?.copyWith(color: theme.colorScheme.onSurface)),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
|
|
const Spacer(),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: SizedBox(
|
|
height: 54,
|
|
child: OutlinedButton(
|
|
onPressed: _previousPage,
|
|
style: OutlinedButton.styleFrom(
|
|
foregroundColor: AppColors.sageGreen,
|
|
side: const BorderSide(color: AppColors.sageGreen)),
|
|
child: const Text('Back'),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: 16),
|
|
Expanded(
|
|
child: SizedBox(
|
|
height: 54,
|
|
child: ElevatedButton(
|
|
onPressed: (_lastPeriodStart != null && !_isNavigating)
|
|
? _nextPage
|
|
: null,
|
|
child: const Text('Get Started'),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|