import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:provider/provider.dart'; import 'package:qpay/screens/onboarding/onboarding_controller.dart'; import 'package:qpay/screens/onboarding/phone/phone_controller.dart'; import 'package:qpay/screens/onboarding/widgets/onboarding_layout.dart'; import 'package:skeletonizer/skeletonizer.dart'; class PhoneScreen extends StatefulWidget { const PhoneScreen({super.key}); @override State createState() => _PhoneScreenState(); } class _PhoneScreenState extends State { late PhoneController phoneController; late OnboardingController onboardingController; final _formKey = GlobalKey(); final TextEditingController _phoneController = TextEditingController(); final TextEditingController _emailController = TextEditingController(); final TextEditingController _passwordController = TextEditingController(); final TextEditingController _confirmPasswordController = TextEditingController(); final TextEditingController _fullNameController = TextEditingController(); String selectedCountryCode = '+263'; // Default to ZW bool _obscurePassword = true; bool _obscureConfirmPassword = true; // Common country codes with flags and names final List> countryCodes = [ {'code': '+263', 'name': 'ZW', 'flag': '🇿🇼'}, {'code': '+27', 'name': 'ZA', 'flag': '🇿🇦'}, {'code': '+1', 'name': 'US', 'flag': '🇺🇸'}, {'code': '+44', 'name': 'UK', 'flag': '🇬🇧'}, {'code': '+61', 'name': 'AU', 'flag': '🇦🇺'}, {'code': '+91', 'name': 'IN', 'flag': '🇮🇳'}, {'code': '+86', 'name': 'CN', 'flag': '🇨🇳'}, {'code': '+81', 'name': 'JP', 'flag': '🇯🇵'}, {'code': '+49', 'name': 'DE', 'flag': '🇩🇪'}, {'code': '+33', 'name': 'FR', 'flag': '🇫🇷'}, {'code': '+39', 'name': 'IT', 'flag': '🇮🇹'}, {'code': '+34', 'name': 'ES', 'flag': '🇪🇸'}, {'code': '+7', 'name': 'RU', 'flag': '🇷🇺'}, {'code': '+55', 'name': 'BR', 'flag': '🇧🇷'}, {'code': '+52', 'name': 'MX', 'flag': '🇲🇽'}, {'code': '+971', 'name': 'AE', 'flag': '🇦🇪'}, {'code': '+966', 'name': 'SA', 'flag': '🇸🇦'}, {'code': '+234', 'name': 'NG', 'flag': '🇳🇬'}, {'code': '+254', 'name': 'KE', 'flag': '🇰🇪'}, {'code': '+233', 'name': 'GH', 'flag': '🇬🇭'}, {'code': '+256', 'name': 'UG', 'flag': '🇺🇬'}, ]; @override void initState() { super.initState(); phoneController = PhoneController(context); onboardingController = Provider.of( context, listen: false, ); // Pre-fill the email field if it has already been captured. final existingEmail = onboardingController.model.email; if (existingEmail != null && existingEmail.isNotEmpty) { _emailController.text = existingEmail; } // Pre-fill the password field if it has already been captured. final existingPassword = onboardingController.model.password; if (existingPassword != null && existingPassword.isNotEmpty) { _passwordController.text = existingPassword; _confirmPasswordController.text = existingPassword; } // Pre-fill the full name from firstName + lastName if previously captured. final firstName = onboardingController.model.firstName; final lastName = onboardingController.model.lastName; final combined = [ if (firstName != null && firstName.isNotEmpty) firstName, if (lastName != null && lastName.isNotEmpty) lastName, ].join(' '); if (combined.isNotEmpty) { _fullNameController.text = combined; } } @override void dispose() { _phoneController.dispose(); _emailController.dispose(); _passwordController.dispose(); _confirmPasswordController.dispose(); _fullNameController.dispose(); super.dispose(); } /// Splits a full name into a [firstName] and [lastName] using a single /// space as the delimiter. /// /// Examples: /// "John" -> firstName: "John", lastName: "" /// "John Doe" -> firstName: "John", lastName: "Doe" /// "John Michael Doe" -> firstName: "John", lastName: "Michael Doe" /// " John Doe " -> firstName: "John", lastName: "Doe" ({String firstName, String lastName}) splitFullName(String fullName) { final trimmed = fullName.trim(); if (trimmed.isEmpty) { return (firstName: '', lastName: ''); } final parts = trimmed.split(RegExp(r'\s+')); if (parts.length == 1) { return (firstName: parts.first, lastName: ''); } return (firstName: parts.first, lastName: parts.sublist(1).join(' ')); } Future _onSubmit() async { if (!(_formKey.currentState?.validate() ?? false)) return; final fullPhoneNumber = _phoneController.text; final email = _emailController.text.trim(); final password = _passwordController.text; final nameParts = splitFullName(_fullNameController.text); phoneController.updatePhone(fullPhoneNumber); onboardingController.updateEmail(email); onboardingController.updatePassword(password); onboardingController.updateFirstName(nameParts.firstName); onboardingController.updateLastName(nameParts.lastName); await phoneController.register(); if (phoneController.model.status != 'failed') { if (!mounted) return; context.go('/onboarding/verify'); } } @override Widget build(BuildContext context) { return ListenableBuilder( listenable: phoneController, builder: (context, child) { return OnboardingLayout( showBackButton: true, trailingLabel: 'Sign in', trailingRoute: '/onboarding/login', trailingIcon: Icons.login_rounded, bottomLinkLabel: 'Continue as guest?', bottomLinkRoute: '/', child: OnboardingCard( child: Form( key: _formKey, child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ const OnboardingHero( icon: Icons.person_add_alt_1_rounded, title: 'Create your account', subtitle: 'It only takes a minute — we\'ll send a verification code to confirm your details.', ), const SizedBox(height: 28), OnboardingTextField( controller: _fullNameController, hintText: 'Full name', prefixIcon: Icons.person_outline, textCapitalization: TextCapitalization.words, validator: (value) { if (value == null || value.trim().isEmpty) { return 'Please enter your full name'; } if (value.trim().length < 2) { return 'Name is too short'; } return null; }, ), const SizedBox(height: 14), _buildPhoneField(), const SizedBox(height: 14), OnboardingTextField( controller: _emailController, hintText: 'Email address', prefixIcon: Icons.email_outlined, keyboardType: TextInputType.emailAddress, validator: (value) { if (value == null || value.trim().isEmpty) { return 'Please enter your email address'; } final emailRegex = RegExp(r'^[\w\.\-+]+@[\w\.\-]+\.\w+$'); if (!emailRegex.hasMatch(value.trim())) { return 'Please enter a valid email address'; } return null; }, ), const SizedBox(height: 14), OnboardingTextField( controller: _passwordController, hintText: 'Create a password', prefixIcon: Icons.lock_outline, obscureText: _obscurePassword, enableSuggestions: false, autocorrect: false, suffixIcon: IconButton( icon: Icon( _obscurePassword ? Icons.visibility_rounded : Icons.visibility_off_rounded, ), tooltip: _obscurePassword ? 'Show password' : 'Hide password', onPressed: () { setState(() { _obscurePassword = !_obscurePassword; }); }, ), validator: (value) { if (value == null || value.isEmpty) { return 'Please enter a password'; } if (value.length < 8) { return 'Password must be at least 8 characters'; } if (value.length > 128) { return 'Password is too long'; } return null; }, ), const SizedBox(height: 14), OnboardingTextField( controller: _confirmPasswordController, hintText: 'Confirm your password', prefixIcon: Icons.lock_outline, obscureText: _obscureConfirmPassword, enableSuggestions: false, autocorrect: false, suffixIcon: IconButton( icon: Icon( _obscureConfirmPassword ? Icons.visibility_rounded : Icons.visibility_off_rounded, ), tooltip: _obscureConfirmPassword ? 'Show password' : 'Hide password', onPressed: () { setState(() { _obscureConfirmPassword = !_obscureConfirmPassword; }); }, ), validator: (value) { if (value == null || value.isEmpty) { return 'Please confirm your password'; } if (value != _passwordController.text) { return 'Passwords do not match'; } return null; }, ), const SizedBox(height: 28), Skeletonizer( enabled: phoneController.model.isLoading, child: OnboardingPrimaryButton( label: 'Send verification code', icon: Icons.arrow_forward_rounded, onPressed: _onSubmit, ), ), ], ), ), ), ); }, ); } Widget _buildPhoneField() { final colorScheme = Theme.of(context).colorScheme; return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Country code dropdown SizedBox( width: 110, child: Container( height: 47, decoration: BoxDecoration( color: colorScheme.surface, border: Border.all( color: colorScheme.onSurface.withValues(alpha: 0.12), ), borderRadius: BorderRadius.circular(14), ), child: DropdownButtonHideUnderline( child: DropdownButton( value: selectedCountryCode, isExpanded: true, icon: const Icon(Icons.arrow_drop_down_rounded), padding: const EdgeInsets.symmetric(horizontal: 12), borderRadius: BorderRadius.circular(14), items: countryCodes.map((country) { return DropdownMenuItem( value: country['code'], child: Row( mainAxisSize: MainAxisSize.min, children: [ Text(country['flag']!), const SizedBox(width: 6), Text( country['code']!, style: const TextStyle( fontSize: 15, fontWeight: FontWeight.w500, ), ), ], ), ); }).toList(), onChanged: (String? newValue) { if (newValue != null) { setState(() { selectedCountryCode = newValue; }); } }, ), ), ), ), const SizedBox(width: 10), // Phone number field Expanded( child: OnboardingTextField( controller: _phoneController, hintText: 'Phone number', prefixIcon: Icons.phone_outlined, keyboardType: TextInputType.numberWithOptions(decimal: true), validator: (value) { if (value == null || value.isEmpty) { return 'Please enter a phone number'; } // Remove any non-digit characters for validation final digitsOnly = value.replaceAll(RegExp(r'[^\d]'), ''); if (digitsOnly.length < 7) { return 'Phone number must be at least 7 digits'; } if (digitsOnly.length > 15) { return 'Phone number is too long'; } return null; }, ), ), ], ); } }