1134 lines
38 KiB
Dart
1134 lines
38 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:smooth_page_indicator/smooth_page_indicator.dart';
|
|
import 'package:uuid/uuid.dart';
|
|
import '../../theme/app_theme.dart';
|
|
import '../../models/user_profile.dart';
|
|
import '../home/home_screen.dart';
|
|
import '../husband/husband_home_screen.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import '../../providers/user_provider.dart';
|
|
import '../../services/notification_service.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;
|
|
int _minCycleLength = 25;
|
|
int _maxCycleLength = 35;
|
|
bool _isPadTrackingEnabled = false;
|
|
|
|
// Connection options
|
|
bool _useExampleData = false;
|
|
bool _skipPartnerConnection = false;
|
|
|
|
@override
|
|
void dispose() {
|
|
_pageController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
void _nextPage() async {
|
|
if (_isNavigating) return;
|
|
_isNavigating = true;
|
|
|
|
// Husband Flow: Role (0) -> Name (1) -> Connect (2) -> Finish
|
|
// Wife Flow: Role (0) -> Name (1) -> Relationship (2) -> [Fertility (3)] -> Cycle (4) -> [Connect (5) if married]
|
|
|
|
int nextPage = _currentPage + 1;
|
|
|
|
// Logic for skipping pages
|
|
if (_role == UserRole.husband) {
|
|
if (_currentPage == 2) {
|
|
// Finish after connect page
|
|
await _completeOnboarding();
|
|
return;
|
|
}
|
|
} else {
|
|
// Wife flow
|
|
if (_currentPage == 2 &&
|
|
_relationshipStatus != RelationshipStatus.married) {
|
|
// Skip fertility goal (page 3) if not married
|
|
nextPage = 4;
|
|
}
|
|
if (_currentPage == 4 &&
|
|
_relationshipStatus != RelationshipStatus.married) {
|
|
// Skip connect page (page 5) if not married - finish now
|
|
await _completeOnboarding();
|
|
return;
|
|
}
|
|
if (_currentPage == 5) {
|
|
// Finish after connect page (married wife)
|
|
await _completeOnboarding();
|
|
return;
|
|
}
|
|
}
|
|
|
|
final maxPages = _role == UserRole.husband ? 2 : 5;
|
|
if (nextPage <= maxPages) {
|
|
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,
|
|
useExampleData: _useExampleData,
|
|
createdAt: DateTime.now(),
|
|
updatedAt: DateTime.now(),
|
|
);
|
|
|
|
await ref.read(userProfileProvider.notifier).updateProfile(userProfile);
|
|
|
|
// Trigger partner connection notification if applicable
|
|
if (!_skipPartnerConnection && !_useExampleData) {
|
|
await NotificationService().showPartnerUpdateNotification(
|
|
title: 'Connection Successful!',
|
|
body: 'You are now connected with your partner. Tap to start sharing.',
|
|
);
|
|
}
|
|
|
|
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; - unused
|
|
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
|
|
? 3
|
|
: (_relationshipStatus == RelationshipStatus.married
|
|
? 6
|
|
: 5),
|
|
effect: WormEffect(
|
|
dotHeight: 8,
|
|
dotWidth: 8,
|
|
spacing: 12,
|
|
activeDotColor:
|
|
isHusband ? AppColors.navyBlue : AppColors.sageGreen,
|
|
dotColor: theme.colorScheme.outline.withValues(alpha: 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
|
|
if (_role == UserRole.husband)
|
|
_buildHusbandConnectPage() // Page 2 (Husband only)
|
|
else ...[
|
|
_buildRelationshipPage(), // Page 2 (Wife only)
|
|
_buildFertilityGoalPage(), // Page 3 (Wife married only)
|
|
_buildCyclePage(), // Page 4 (Wife only)
|
|
if (_relationshipStatus == RelationshipStatus.married)
|
|
_buildWifeConnectPage(), // Page 5 (Wife married 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.withValues(alpha: 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.withValues(alpha: 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.withValues(alpha: 0.1),
|
|
width: isSelected ? 2 : 1,
|
|
),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Container(
|
|
padding: const EdgeInsets.all(12),
|
|
decoration: BoxDecoration(
|
|
color: isSelected
|
|
? activeColor
|
|
: theme.colorScheme.surfaceContainerHighest,
|
|
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: !_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.withValues(alpha: isDark ? 0.3 : 0.1)
|
|
: theme.cardTheme.color,
|
|
borderRadius: BorderRadius.circular(12),
|
|
border: Border.all(
|
|
color: isSelected
|
|
? AppColors.sageGreen
|
|
: theme.colorScheme.outline.withValues(alpha: 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)
|
|
const 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.withValues(alpha: isDark ? 0.3 : 0.1)
|
|
: theme.cardTheme.color,
|
|
borderRadius: BorderRadius.circular(12),
|
|
border: Border.all(
|
|
color: isSelected
|
|
? AppColors.sageGreen
|
|
: theme.colorScheme.outline.withValues(alpha: 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)
|
|
const Icon(Icons.check_circle, color: AppColors.sageGreen),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildCyclePage() {
|
|
final theme = Theme.of(context);
|
|
// final isDark = theme.brightness == Brightness.dark; - unused
|
|
// 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,
|
|
),
|
|
|
|
if (_isIrregularCycle) ...[
|
|
const SizedBox(height: 8),
|
|
Text('Cycle range (shortest to longest)',
|
|
style: theme.textTheme.titleSmall?.copyWith(
|
|
fontWeight: FontWeight.w500,
|
|
color: theme.colorScheme.onSurface)),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: RangeSlider(
|
|
values: RangeValues(
|
|
_minCycleLength.toDouble(), _maxCycleLength.toDouble()),
|
|
min: 21,
|
|
max: 45,
|
|
divisions: 24,
|
|
activeColor: AppColors.sageGreen,
|
|
labels: RangeLabels(
|
|
'$_minCycleLength days', '$_maxCycleLength days'),
|
|
onChanged: (values) {
|
|
setState(() {
|
|
_minCycleLength = values.start.round();
|
|
_maxCycleLength = values.end.round();
|
|
});
|
|
},
|
|
),
|
|
),
|
|
],
|
|
),
|
|
Center(
|
|
child: Text('$_minCycleLength - $_maxCycleLength days',
|
|
style: theme.textTheme.bodyMedium?.copyWith(
|
|
fontWeight: FontWeight.w600, color: AppColors.sageGreen)),
|
|
),
|
|
],
|
|
|
|
// Enable Supply Tracking Checkbox
|
|
CheckboxListTile(
|
|
title: Text('Enable supply tracking',
|
|
style: theme.textTheme.bodyLarge
|
|
?.copyWith(color: theme.colorScheme.onSurface)),
|
|
value: _isPadTrackingEnabled,
|
|
onChanged: (val) =>
|
|
setState(() => _isPadTrackingEnabled = 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.withValues(alpha: 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'),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
/// Husband Connect Page - Choose to connect with wife or use example data
|
|
Widget _buildHusbandConnectPage() {
|
|
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),
|
|
Container(
|
|
width: 64,
|
|
height: 64,
|
|
decoration: BoxDecoration(
|
|
color: AppColors.navyBlue.withValues(alpha: 0.1),
|
|
borderRadius: BorderRadius.circular(16),
|
|
),
|
|
child: const Icon(Icons.link, size: 32, color: AppColors.navyBlue),
|
|
),
|
|
const SizedBox(height: 24),
|
|
Text(
|
|
'Connect with your wife',
|
|
style: theme.textTheme.displaySmall?.copyWith(
|
|
fontSize: 28,
|
|
fontWeight: FontWeight.w600,
|
|
color: theme.colorScheme.onSurface,
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
'See her cycle info and prayer requests to support her better.',
|
|
style: theme.textTheme.bodyMedium?.copyWith(
|
|
color: theme.colorScheme.onSurfaceVariant,
|
|
),
|
|
),
|
|
const SizedBox(height: 32),
|
|
|
|
// Option 1: Connect with Wife (placeholder for now)
|
|
_buildConnectOption(
|
|
icon: Icons.qr_code_scanner,
|
|
title: 'Connect with Wife',
|
|
subtitle: 'Enter connection code from her app',
|
|
isSelected: !_useExampleData,
|
|
onTap: () => setState(() => _useExampleData = false),
|
|
color: AppColors.navyBlue,
|
|
isDark: isDark,
|
|
),
|
|
const SizedBox(height: 16),
|
|
|
|
// Option 2: Use Example Data
|
|
_buildConnectOption(
|
|
icon: Icons.auto_awesome,
|
|
title: 'Use Example Data',
|
|
subtitle: 'Explore the app with sample data',
|
|
isSelected: _useExampleData,
|
|
onTap: () => setState(() => _useExampleData = true),
|
|
color: AppColors.navyBlue,
|
|
isDark: isDark,
|
|
),
|
|
|
|
const Spacer(),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: SizedBox(
|
|
height: 54,
|
|
child: OutlinedButton(
|
|
onPressed: _previousPage,
|
|
style: OutlinedButton.styleFrom(
|
|
foregroundColor: AppColors.navyBlue,
|
|
side: const BorderSide(color: AppColors.navyBlue),
|
|
),
|
|
child: const Text('Back'),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: 16),
|
|
Expanded(
|
|
child: SizedBox(
|
|
height: 54,
|
|
child: ElevatedButton(
|
|
onPressed: !_isNavigating ? _nextPage : null,
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: AppColors.navyBlue,
|
|
),
|
|
child: const Text('Finish Setup'),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
/// Wife Connect Page - Invite husband or skip
|
|
Widget _buildWifeConnectPage() {
|
|
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),
|
|
Container(
|
|
width: 64,
|
|
height: 64,
|
|
decoration: BoxDecoration(
|
|
color: AppColors.sageGreen.withValues(alpha: 0.1),
|
|
borderRadius: BorderRadius.circular(16),
|
|
),
|
|
child: const Icon(Icons.favorite,
|
|
size: 32, color: AppColors.sageGreen),
|
|
),
|
|
const SizedBox(height: 24),
|
|
Text(
|
|
'Invite your husband',
|
|
style: theme.textTheme.displaySmall?.copyWith(
|
|
fontSize: 28,
|
|
fontWeight: FontWeight.w600,
|
|
color: theme.colorScheme.onSurface,
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
'Share your cycle info and prayer requests so he can support you.',
|
|
style: theme.textTheme.bodyMedium?.copyWith(
|
|
color: theme.colorScheme.onSurfaceVariant,
|
|
),
|
|
),
|
|
const SizedBox(height: 32),
|
|
|
|
// Option 1: Invite Husband
|
|
_buildConnectOption(
|
|
icon: Icons.share,
|
|
title: 'Invite Husband',
|
|
subtitle: 'Generate a connection code to share',
|
|
isSelected: !_skipPartnerConnection,
|
|
onTap: () => setState(() => _skipPartnerConnection = false),
|
|
color: AppColors.sageGreen,
|
|
isDark: isDark,
|
|
),
|
|
const SizedBox(height: 16),
|
|
|
|
// Option 2: Skip for Now
|
|
_buildConnectOption(
|
|
icon: Icons.schedule,
|
|
title: 'Skip for Now',
|
|
subtitle: 'You can invite him later in settings',
|
|
isSelected: _skipPartnerConnection,
|
|
onTap: () => setState(() => _skipPartnerConnection = true),
|
|
color: AppColors.sageGreen,
|
|
isDark: isDark,
|
|
),
|
|
|
|
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: !_isNavigating ? _nextPage : null,
|
|
child: const Text('Get Started'),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
/// Helper for connection option cards
|
|
Widget _buildConnectOption({
|
|
required IconData icon,
|
|
required String title,
|
|
required String subtitle,
|
|
required bool isSelected,
|
|
required VoidCallback onTap,
|
|
required Color color,
|
|
required bool isDark,
|
|
}) {
|
|
final theme = Theme.of(context);
|
|
|
|
return GestureDetector(
|
|
onTap: onTap,
|
|
child: AnimatedContainer(
|
|
duration: const Duration(milliseconds: 200),
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: isSelected
|
|
? color.withValues(alpha: isDark ? 0.3 : 0.1)
|
|
: theme.cardTheme.color,
|
|
borderRadius: BorderRadius.circular(12),
|
|
border: Border.all(
|
|
color: isSelected
|
|
? color
|
|
: theme.colorScheme.outline.withValues(alpha: 0.1),
|
|
width: isSelected ? 2 : 1,
|
|
),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Container(
|
|
padding: const EdgeInsets.all(12),
|
|
decoration: BoxDecoration(
|
|
color: isSelected
|
|
? color
|
|
: theme.colorScheme.surfaceContainerHighest,
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
child: Icon(
|
|
icon,
|
|
color: isSelected
|
|
? Colors.white
|
|
: theme.colorScheme.onSurfaceVariant,
|
|
size: 20,
|
|
),
|
|
),
|
|
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: color),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|