import 'dart:async'; // Add this import for Timer // import 'dart:convert'; // For encoding/decoding // Removed unused import to fix lint import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; // For Clipboard 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'; import '../../services/sync_service.dart'; class OnboardingScreen extends ConsumerStatefulWidget { const OnboardingScreen({super.key}); @override ConsumerState createState() => _OnboardingScreenState(); } class _OnboardingScreenState extends ConsumerState { 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 late String _userId; String? _partnerId; bool _useExampleData = false; bool _skipPartnerConnection = false; @override void initState() { super.initState(); _userId = const Uuid().v4(); } @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; // Early Server Registration (After Name/Role selection) if (_currentPage == 1) { // Don't await this, let it happen in background to keep UI snappy? // Actually, await it to ensure ID is valid before they reach "Connect"? // "Connect" is Page 2 for Husband. // So yes, we should probably await or just fire and hope response is fast. // But _nextPage is async. // Let's fire and forget, but maybe add a small delay or ensure it happens. // Since it's local network often, it should be fast. _registerEarly(); } // Logic for skipping pages // Logic for skipping pages if (_role == UserRole.husband) { if (_currentPage == 2) { // Finish after connect page if (!_useExampleData) { final id = await _showConnectDialog(); if (id != null && id.isNotEmpty) { setState(() => _partnerId = id); } else if (id == null) { // Cancelled if (mounted) setState(() => _isNavigating = false); return; } } 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) if (!_skipPartnerConnection) { await _showInviteDialog(); } 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 _registerEarly() async { // Register the user on the server early so partner can link to them // immediately. try { final userDetails = { 'name': _name, 'role': _role.name, 'partnerId': null, // No partner yet 'createdAt': DateTime.now().toIso8601String(), }; await SyncService().pushSyncData( userId: _userId, entries: [], teachingPlans: [], prayerRequests: [], userDetails: userDetails, ); debugPrint('Early registration successful for $_name'); } catch (e) { debugPrint('Early registration failed: $e'); } } Future _completeOnboarding() async { // 1. Check for Server-Linked Partner (Auto-Discovery) // If the husband linked to us while we were finishing the form, // the server will have the partnerId. try { final syncData = await SyncService().pullSyncData(_userId); if (syncData.containsKey('userProfile')) { final serverProfile = syncData['userProfile'] as Map; if (serverProfile['partnerId'] != null) { _partnerId = serverProfile['partnerId']; debugPrint('Auto-discovered partner: $_partnerId'); } } } catch (e) { debugPrint('Error checking for partner link: $e'); } // 2. Create User Profile final userProfile = UserProfile( id: _userId, name: _name, role: _role, relationshipStatus: _role == UserRole.husband ? RelationshipStatus.married : _relationshipStatus, partnerId: _partnerId, fertilityGoal: (_role == UserRole.wife && _relationshipStatus == RelationshipStatus.married) ? _fertilityGoal : null, averageCycleLength: _averageCycleLength, lastPeriodStartDate: _lastPeriodStart, isIrregularCycle: _isIrregularCycle, hasCompletedOnboarding: true, isPadTrackingEnabled: _isPadTrackingEnabled, createdAt: DateTime.now(), updatedAt: DateTime.now(), ); // 3. Save Profile (triggers local save) await ref.read(userProfileProvider.notifier).updateProfile(userProfile); // 4. Force Final Sync (Push everything including completed status) // Note: CycleEntriesNotifier handles data sync, but we want to ensure // profile is consistent. The Provider doesn't push profile changes automatically yet, // so we do it manually or rely on the next data change. // For safety, let's just push one last time or let the Home Screen handle it. // But since we just updated the profile, we should sync it. try { final userDetails = { 'name': userProfile.name, 'role': userProfile.role.name, 'partnerId': userProfile.partnerId, 'createdAt': userProfile.createdAt.toIso8601String(), }; await SyncService().pushSyncData( userId: _userId, entries: [], teachingPlans: [], prayerRequests: [], userDetails: userDetails, ); } catch (e) { debugPrint('Final onboarding sync failed: $e'); } // Generate example data if requested - REMOVED /* if (_useExampleData) { await ref .read(cycleEntriesProvider.notifier) .generateExampleData(userProfile.id); } */ // Trigger partner connection notification if applicable if (!_skipPartnerConnection) { 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: const Text('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), ], ), ), ); } Future _showConnectDialog() async { // Ensure we exist before connecting await _ensureServerRegistration(); final controller = TextEditingController(); String? error; bool isLoading = false; // State for the dialog: 'input', 'confirm' String step = 'input'; String? partnerName; String? partnerRole; return showDialog( context: context, barrierDismissible: false, builder: (context) => StatefulBuilder( builder: (context, setState) { if (step == 'confirm') { return AlertDialog( title: const Text('Confirm Connection'), content: Column( mainAxisSize: MainAxisSize.min, children: [ Text('Found Partner: $partnerName'), if (partnerRole != null) Text('Role: $partnerRole'), const SizedBox(height: 16), const Text('Do you want to connect with this user?'), if (isLoading) ...[ const SizedBox(height: 16), const CircularProgressIndicator(), ], ], ), actions: [ if (!isLoading) TextButton( onPressed: () { setState(() { step = 'input'; error = null; }); }, child: const Text('Back'), ), ElevatedButton( onPressed: isLoading ? null : () async { setState(() => isLoading = true); try { // Final Link final input = controller.text.trim(); await SyncService().verifyPartnerId(_userId, input); if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Connected to $partnerName!')), ); Navigator.pop(context, input); } } catch (e) { if (context.mounted) { setState(() { isLoading = false; error = 'Connection Request Failed'; step = 'input'; }); } } }, child: const Text('Confirm & Link'), ), ], ); } return AlertDialog( title: const Text('Connect with Partner'), content: Column( mainAxisSize: MainAxisSize.min, children: [ const Text('Enter your partner\'s User ID:'), const SizedBox(height: 16), TextField( controller: controller, decoration: InputDecoration( border: const OutlineInputBorder(), hintText: 'Paste ID here', errorText: error, ), enabled: !isLoading, ), if (isLoading) ...[ const SizedBox(height: 16), const CircularProgressIndicator(), const SizedBox(height: 8), const Text('Searching...'), ], ], ), actions: [ if (!isLoading) TextButton( onPressed: () => Navigator.pop(context), child: const Text('Cancel'), ), ElevatedButton( onPressed: isLoading ? null : () async { final input = controller.text.trim(); if (input.isEmpty) return; setState(() { isLoading = true; error = null; }); try { // Preview First final result = await SyncService().previewPartnerId(input); if (context.mounted) { setState(() { isLoading = false; partnerName = result['partnerName']; partnerRole = result['partnerRole']; step = 'confirm'; }); } } catch (e) { if (context.mounted) { setState(() { isLoading = false; // Show actual error for debugging error = e .toString() .replaceAll('Exception:', '') .trim(); }); } } }, child: const Text('Find Partner'), ), ], ); }, ), ); } Future _ensureServerRegistration() async { await _registerEarly(); } Future _showInviteDialog() async { // 1. Ensure we are actually registered so they can find us await _ensureServerRegistration(); Timer? pollTimer; await showDialog( context: context, barrierDismissible: false, builder: (context) => StatefulBuilder( builder: (context, setState) { // Poll for connection if (pollTimer == null) { pollTimer = Timer.periodic(const Duration(seconds: 3), (timer) async { if (!mounted) { timer.cancel(); return; } // Check if we are connected yet try { final result = await SyncService().pullSyncData(_userId); if (result.containsKey('userProfile')) { final profile = result['userProfile']; final partnerId = profile['partnerId']; if (partnerId != null) { // SUCCESS! timer.cancel(); if (context.mounted) { // We could also fetch partner name here if needed, // but for now we just know we are linked. // Or pull again to get teaching plans etc if they synced. ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Husband Connected Successfully!')), ); Navigator.pop(context); // Close dialog } } } } catch (e) { debugPrint('Poll error: $e'); } }); } return AlertDialog( title: const Text('Invite Partner'), content: Column( mainAxisSize: MainAxisSize.min, children: [ const Text('Share this code with your partner:'), const SizedBox(height: 16), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ SelectableText( _userId, style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 18), ), IconButton( icon: const Icon(Icons.copy), onPressed: () { Clipboard.setData(ClipboardData(text: _userId)); ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Copied to clipboard!')), ); }, ), ], ), const SizedBox(height: 16), const Text('Waiting for him to connect...'), const SizedBox(height: 8), const LinearProgressIndicator(), ], ), actions: [ TextButton( onPressed: () { pollTimer?.cancel(); Navigator.pop(context); }, child: const Text('Cancel / Done'), ), ], ); }, ), ); pollTimer?.cancel(); } }