import 'dart:convert'; import 'package:bookmywages/consts_widgets/app_colors.dart'; import 'package:bookmywages/model/detail_page_model.dart'; import 'package:bookmywages/routers/consts_router.dart'; import 'package:bookmywages/view/auth/auth_repository.dart'; import 'package:bookmywages/view/user_main_screens/main_contoller.dart'; import 'package:bookmywages/view/user_main_screens/sucessfull_screen.dart'; import 'package:bookmywages/viewmodel/consts_api.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:dotted_line/dotted_line.dart'; import 'package:flutter/material.dart'; import 'package:flutter_rating_bar/flutter_rating_bar.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:get/get.dart'; import 'package:http/http.dart' as http; import 'package:intl/intl.dart'; import 'package:shared_preferences/shared_preferences.dart'; class BookingScreen extends StatefulWidget { final DetailPageModel service; const BookingScreen({super.key, required this.service}); @override State createState() => _BookingScreenState(); } class _BookingScreenState extends State { final _formKey = GlobalKey(); int rating = 0; final TextEditingController _nameController = TextEditingController(); final TextEditingController _mobileController = TextEditingController(); final TextEditingController _emailController = TextEditingController(); final TextEditingController _addressController = TextEditingController(); final TextEditingController _messageController = TextEditingController(); // Controllers for date and time final TextEditingController _dateController = TextEditingController(); final TextEditingController _timeController = TextEditingController(); // Variables to store selected date and time DateTime? _selectedDate; TimeOfDay? _selectedTime; // ScrollController to manage scrolling when keyboard appears final ScrollController _scrollController = ScrollController(); // FocusNodes for each field - SOLUTION 1: Use FocusNodes final FocusNode _nameFocusNode = FocusNode(); final FocusNode _mobileFocusNode = FocusNode(); final FocusNode _emailFocusNode = FocusNode(); final FocusNode _addressFocusNode = FocusNode(); final FocusNode _messageFocusNode = FocusNode(); // SOLUTION 2: Debounce validation to prevent frequent rebuilds bool _isValidating = false; @override void initState() { super.initState(); // SOLUTION 3: Remove listeners that cause frequent setState calls // Only add listeners if absolutely necessary _nameController.addListener(_onTextChangedDebounced); _mobileController.addListener(_onTextChangedDebounced); _emailController.addListener(_onTextChangedDebounced); _addressController.addListener(_onTextChangedDebounced); _messageController.addListener(_onTextChangedDebounced); _dateController.addListener(_onTextChangedDebounced); _timeController.addListener(_onTextChangedDebounced); } // SOLUTION 4: Debounced validation to prevent frequent rebuilds void _onTextChangedDebounced() { if (_isValidating) return; _isValidating = true; Future.delayed(const Duration(milliseconds: 500), () { if (mounted && _formKey.currentState != null) { // Only validate if form has been submitted before // This prevents validation during typing _isValidating = false; } }); } @override void dispose() { // Remove listeners before disposing controllers _nameController.removeListener(_onTextChangedDebounced); _mobileController.removeListener(_onTextChangedDebounced); _emailController.removeListener(_onTextChangedDebounced); _addressController.removeListener(_onTextChangedDebounced); _messageController.removeListener(_onTextChangedDebounced); _dateController.removeListener(_onTextChangedDebounced); _timeController.removeListener(_onTextChangedDebounced); // Dispose FocusNodes _nameFocusNode.dispose(); _mobileFocusNode.dispose(); _emailFocusNode.dispose(); _addressFocusNode.dispose(); _messageFocusNode.dispose(); _nameController.dispose(); _mobileController.dispose(); _emailController.dispose(); _addressController.dispose(); _messageController.dispose(); _dateController.dispose(); _timeController.dispose(); _scrollController.dispose(); super.dispose(); } // Function to open date picker Future _selectDate(BuildContext context) async { final DateTime now = DateTime.now(); final DateTime? picked = await showDatePicker( context: context, initialDate: _selectedDate ?? now, firstDate: now, lastDate: DateTime(now.year + 1, now.month, now.day), builder: (context, child) { return Theme( data: Theme.of(context).copyWith( colorScheme: const ColorScheme.light( primary: Colors.blue, onPrimary: Colors.white, onSurface: Colors.black, ), ), child: child!, ); }, ); if (picked != null && picked != _selectedDate) { setState(() { _selectedDate = picked; _dateController.text = DateFormat('dd-MM-yyyy').format(picked); }); } } // Function to open time picker Future _selectTime(BuildContext context) async { final TimeOfDay now = TimeOfDay.now(); final TimeOfDay? picked = await showTimePicker( context: context, initialTime: _selectedTime ?? now, builder: (context, child) { return Theme( data: Theme.of(context).copyWith( colorScheme: const ColorScheme.light( primary: Colors.blue, onPrimary: Colors.white, onSurface: Colors.black, ), ), child: child!, ); }, ); if (picked != null && picked != _selectedTime) { setState(() { _selectedTime = picked; final hour = picked.hourOfPeriod == 0 ? 12 : picked.hourOfPeriod; final period = picked.period == DayPeriod.am ? 'AM' : 'PM'; _timeController.text = '${hour.toString().padLeft(2, '0')}:${picked.minute.toString().padLeft(2, '0')} $period'; }); } } @override Widget build(BuildContext context) { final service = widget.service; final screenWidth = MediaQuery.of(context).size.width; final indexController = InheritedIndexController.of(context); return Scaffold( backgroundColor: AppColors.secondprimary, resizeToAvoidBottomInset: false, body: Stack( children: [ // Top app bar Positioned( top: 0, left: 0, right: 0, child: SafeArea( child: Padding( padding: const EdgeInsets.only(top: 10.0, left: 16, right: 16), child: Row( children: [ IconButton( icon: const Icon(Icons.arrow_back_ios), onPressed: () => Navigator.of(context).pop(), ), Expanded( child: Center( child: Text( 'Booking', style: Theme.of(context).textTheme.titleLarge, ), ), ), const SizedBox(width: 48), ], ), ), ), ), // Main scrollable content Padding( padding: EdgeInsets.only( top: MediaQuery.of(context).padding.top + 56, ), child: ListView( controller: _scrollController, physics: const ClampingScrollPhysics(), children: [ // Image + Overlay Padding( padding: const EdgeInsets.symmetric(horizontal: 10.0), child: Stack( clipBehavior: Clip.none, children: [ Padding( padding: const EdgeInsets.all(0.0), child: ClipRRect( borderRadius: BorderRadius.circular(30), child: service.images1.isNotEmpty ? CachedNetworkImage( imageUrl: service.images1.first, width: double.infinity, height: 300, fit: BoxFit.cover, placeholder: (context, url) => const Center( child: CircularProgressIndicator(), ), errorWidget: (context, url, error) => const Icon(Icons.error), ) : Container( width: double.infinity, height: 300, color: Colors.grey[300], child: const Center( child: Icon( Icons.image_not_supported, size: 50, ), ), ), ), ), Positioned( bottom: -130, left: 16, right: 16, child: Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: const Color(0xFFFCFCFC), borderRadius: BorderRadius.circular(22), border: Border.all( color: const Color(0xFFC9C9C9), width: 1, ), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.1), blurRadius: 10, offset: const Offset(0, 5), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( service.vendorName, style: const TextStyle( fontFamily: 'Gilroy-Bold', fontWeight: FontWeight.w700, fontSize: 20, letterSpacing: 0.2, color: Colors.black, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 5), Text( service.servicename, style: const TextStyle( fontFamily: 'Gilroy-Medium', fontWeight: FontWeight.w600, fontSize: 16, letterSpacing: 0.18, color: Color(0xFF5A5A5A), ), maxLines: 2, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 15), const DottedLine( dashColor: Color(0xFFBABABA), lineThickness: 1, ), Padding( padding: const EdgeInsets.only(top: 16.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Flexible( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( "Payment:", style: TextStyle( fontFamily: 'Gilroy-Bold', fontWeight: FontWeight.w700, fontSize: 15.61, ), ), const SizedBox(height: 15), Text( 'Rs ${service.amount}', style: const TextStyle( fontFamily: 'Gilroy-Medium', fontWeight: FontWeight.w500, fontSize: 14, color: Color(0xFF636363), ), ), ], ), ), Flexible( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( "Duration:", style: TextStyle( fontFamily: 'Gilroy-Bold', fontWeight: FontWeight.w700, fontSize: 15.61, ), ), const SizedBox(height: 15), Text( service.workingduration, style: const TextStyle( fontFamily: 'Gilroy-Medium', fontWeight: FontWeight.w500, fontSize: 14, color: Color(0xFF636363), ), ), ], ), ), Flexible( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( "Rating:", style: TextStyle( fontFamily: 'Gilroy-Bold', fontWeight: FontWeight.w700, fontSize: 15.61, ), ), const SizedBox(height: 15), Row( children: [ const Icon( Icons.star, size: 16, color: Colors.amber, ), const SizedBox(width: 4), ], ), ], ), ), ], ), ), ], ), ), ), ], ), ), const SizedBox(height: 140), // Form Padding( padding: const EdgeInsets.all(16.0), child: Form( key: _formKey, // SOLUTION 5: Change autovalidate mode to only validate after user interaction autovalidateMode: AutovalidateMode.onUserInteraction, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ buildFieldTitle("Name"), const SizedBox(height: 8), buildTextFormField( controller: _nameController, focusNode: _nameFocusNode, // Add FocusNode hintText: "Enter your name", validator: (value) { if (value == null || value.isEmpty) { return 'Please enter your name'; } return null; }, onTap: () { Future.delayed( const Duration(milliseconds: 300), () { _scrollToField(0); }, ); }, ), const SizedBox(height: 16), buildFieldTitle("Mobile Number"), const SizedBox(height: 8), buildTextFormField( controller: _mobileController, focusNode: _mobileFocusNode, // Add FocusNode hintText: "Enter your mobile number", keyboardType: TextInputType.phone, validator: (value) { if (value == null || value.isEmpty) { return 'Please enter your mobile number'; } if (!RegExp(r'^[0-9]{10}$').hasMatch(value)) { return 'Please enter a valid 10-digit mobile number'; } return null; }, onTap: () { Future.delayed( const Duration(milliseconds: 300), () { _scrollToField(1); }, ); }, ), const SizedBox(height: 16), buildFieldTitle("Email ID"), const SizedBox(height: 8), buildTextFormField( controller: _emailController, focusNode: _emailFocusNode, // Add FocusNode hintText: "Enter your email address", keyboardType: TextInputType.emailAddress, validator: (value) { if (value == null || value.isEmpty) { return 'Please enter your email address'; } if (!RegExp( r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$', ).hasMatch(value)) { return 'Please enter a valid email address'; } return null; }, onTap: () { Future.delayed( const Duration(milliseconds: 300), () { _scrollToField(2); }, ); }, ), const SizedBox(height: 16), // Date and Time Row Row( children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ buildFieldTitle("Date"), const SizedBox(height: 8), TextFormField( controller: _dateController, readOnly: true, decoration: InputDecoration( hintText: "Select Date", filled: true, fillColor: Colors.white, contentPadding: const EdgeInsets.symmetric( horizontal: 16, vertical: 16, ), border: OutlineInputBorder( borderRadius: BorderRadius.circular(14), borderSide: const BorderSide( color: Color(0xFFB7B7B7), width: 1, ), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(14), borderSide: const BorderSide( color: Color(0xFFB7B7B7), width: 1, ), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(14), borderSide: const BorderSide( color: Color(0xFFB7B7B7), width: 1, ), ), errorBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(14), borderSide: const BorderSide( color: Colors.red, width: 1, ), ), focusedErrorBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(14), borderSide: const BorderSide( color: Colors.red, width: 1, ), ), suffixIcon: IconButton( icon: const Icon( Icons.calendar_today, color: Colors.blue, ), onPressed: () => _selectDate(context), ), ), style: const TextStyle( fontFamily: 'Gilroy-Medium', fontSize: 14, ), validator: (value) { if (value == null || value.isEmpty) { return 'Please select a date'; } return null; }, onTap: () => _selectDate(context), ), ], ), ), const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ buildFieldTitle("Time"), const SizedBox(height: 8), TextFormField( controller: _timeController, readOnly: true, decoration: InputDecoration( hintText: "Select Time", filled: true, fillColor: Colors.white, contentPadding: const EdgeInsets.symmetric( horizontal: 16, vertical: 16, ), border: OutlineInputBorder( borderRadius: BorderRadius.circular(14), borderSide: const BorderSide( color: Color(0xFFB7B7B7), width: 1, ), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(14), borderSide: const BorderSide( color: Color(0xFFB7B7B7), width: 1, ), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(14), borderSide: const BorderSide( color: Color(0xFFB7B7B7), width: 1, ), ), errorBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(14), borderSide: const BorderSide( color: Colors.red, width: 1, ), ), focusedErrorBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(14), borderSide: const BorderSide( color: Colors.red, width: 1, ), ), suffixIcon: IconButton( icon: const Icon( Icons.access_time, color: Colors.blue, ), onPressed: () => _selectTime(context), ), ), style: const TextStyle( fontFamily: 'Gilroy-Medium', fontSize: 14, ), validator: (value) { if (value == null || value.isEmpty) { return 'Please select a time'; } return null; }, onTap: () => _selectTime(context), ), ], ), ), ], ), const SizedBox(height: 16), buildFieldTitle("Address"), const SizedBox(height: 8), buildTextFormField( controller: _addressController, focusNode: _addressFocusNode, // Add FocusNode hintText: "Enter your address", maxLines: 1, validator: (value) { if (value == null || value.isEmpty) { return 'Please enter your address'; } return null; }, onTap: () { Future.delayed( const Duration(milliseconds: 300), () { _scrollToField(3); }, ); }, ), const SizedBox(height: 16), buildFieldTitle("Message (Optional)"), const SizedBox(height: 8), // SOLUTION 6: Special handling for message field buildTextFormField( controller: _messageController, focusNode: _messageFocusNode, // Add FocusNode hintText: "Any special instructions?", maxLines: 3, onTap: () { // SOLUTION 7: Ensure focus is properly managed _messageFocusNode.requestFocus(); Future.delayed( const Duration(milliseconds: 300), () { _scrollToField(4); }, ); }, ), const SizedBox(height: 24), SizedBox( width: screenWidth - 32, height: 52, child: ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: Colors.blue, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(14), ), ), onPressed: () async { // Hide keyboard FocusScope.of(context).unfocus(); if (_formKey.currentState!.validate()) { try { final prefs = await SharedPreferences.getInstance(); final userId = prefs.getString('userId') ?? prefs.getString('user_id') ?? ''; if (userId.isEmpty) { Fluttertoast.showToast( msg: 'User session expired. Please login again.', toastLength: Toast.LENGTH_LONG, gravity: ToastGravity.BOTTOM, backgroundColor: Colors.red, textColor: Colors.white, ); return; } if (_selectedDate == null) { Fluttertoast.showToast( msg: 'Please select a date', toastLength: Toast.LENGTH_SHORT, gravity: ToastGravity.BOTTOM, backgroundColor: Colors.orange, textColor: Colors.white, ); return; } if (_selectedTime == null) { Fluttertoast.showToast( msg: 'Please select a time', toastLength: Toast.LENGTH_SHORT, gravity: ToastGravity.BOTTOM, backgroundColor: Colors.orange, textColor: Colors.white, ); return; } final formattedDate = DateFormat( 'yyyy-MM-dd', ).format(_selectedDate!); final requestBody = { 'user_id': userId, 'name': _nameController.text.trim(), 'mobile_number': _mobileController.text .trim(), 'email': _emailController.text.trim(), 'message': _messageController.text.trim(), 'address': _addressController.text.trim(), 'service_date': formattedDate, 'service_time': _timeController.text.trim(), 'service_id': service.id.toString(), }; print('API URL: ${ConstsApi.bookservice}'); print( 'Request Body: ${jsonEncode(requestBody)}', ); if (!ConstsApi.bookservice.startsWith( 'http', )) { print( 'ERROR: Invalid API URL - must start with http/https', ); if (context.mounted) { Fluttertoast.showToast( msg: 'Invalid API configuration. Please contact support.', toastLength: Toast.LENGTH_LONG, gravity: ToastGravity.BOTTOM, backgroundColor: Colors.red, textColor: Colors.white, ); } return; } print('Testing API connectivity...'); final isConnected = await _testApiConnectivity(); if (!isConnected) { if (context.mounted) { Fluttertoast.showToast( msg: 'Cannot reach server. Please check your internet connection.', toastLength: Toast.LENGTH_LONG, gravity: ToastGravity.BOTTOM, backgroundColor: Colors.orange, textColor: Colors.white, ); } return; } final response = await http .post( Uri.parse(ConstsApi.bookservice), headers: { 'Content-Type': 'application/json', }, body: jsonEncode(requestBody), ) .timeout(const Duration(seconds: 30)); print( 'Response Status: ${response.statusCode}', ); print('Response Body: ${response.body}'); print( 'Response Headers: ${response.headers}', ); final status = response.statusCode; if (response.headers['content-type'] ?.contains('text/html') == true || response.body.trim().startsWith( '', ) || response.body.trim().startsWith( ' jsonResponse; try { jsonResponse = jsonDecode(response.body); } catch (e) { print('JSON Parse Error: $e'); print('Raw Response: ${response.body}'); if (context.mounted) { Fluttertoast.showToast( msg: 'Invalid response from server. Please try again.', toastLength: Toast.LENGTH_LONG, gravity: ToastGravity.BOTTOM, backgroundColor: Colors.red, textColor: Colors.white, ); } return; } if (status == 200) { // Clear fields _nameController.clear(); _mobileController.clear(); _emailController.clear(); _messageController.clear(); _addressController.clear(); _dateController.clear(); _timeController.clear(); setState(() { _selectedDate = null; _selectedTime = null; }); if (context.mounted) { await Navigator.push( context, MaterialPageRoute( builder: (context) => const SucessfullScreen(), ), ); int rating = 0; await showDialog( context: context, barrierColor: Colors.black.withOpacity( 0.5, ), builder: (context) { return Dialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(40), ), child: StatefulBuilder( builder: (context, setState) { return Container( width: double.infinity, height: 280, padding: const EdgeInsets.all( 16, ), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular( 40, ), ), child: Padding( padding: const EdgeInsets.symmetric( vertical: 32, ), child: Column( mainAxisSize: MainAxisSize.min, children: [ const Text( 'Rate this service', style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, ), ), const SizedBox( height: 29, ), RatingBar( initialRating: 0, minRating: 0.5, direction: Axis.horizontal, allowHalfRating: true, itemCount: 5, ratingWidget: RatingWidget( full: Icon( Icons.star, color: AppColors .primary, ), half: Icon( Icons.star_half, color: AppColors .primary, ), empty: Icon( Icons .star_outline, color: Colors.black, ), ), itemPadding: const EdgeInsets.symmetric( horizontal: 4.0, ), onRatingUpdate: (newRating) { setState(() { rating = newRating .round(); }); }, ), const SizedBox( height: 20, ), Align( alignment: Alignment.center, child: SizedBox( width: 207, height: 47, child: ElevatedButton( onPressed: () async { if (rating == 0) { Fluttertoast.showToast( msg: 'Please provide a rating', toastLength: Toast .LENGTH_SHORT, gravity: ToastGravity .BOTTOM, backgroundColor: Colors .orange, textColor: Colors .white, ); return; } try { final repository = updatereviewRepository(); await repository.updatereviews( context: context, url: ConstsApi .updatereview, review: rating .toString(), serviceId: service .id .toString(), ); Navigator.of( context, ).pop(); Fluttertoast.showToast( msg: 'Thank you for your rating!', toastLength: Toast .LENGTH_SHORT, gravity: ToastGravity .BOTTOM, backgroundColor: Colors .green, textColor: Colors .white, ); indexController ?.changeIndex( 3, ); Get.offAllNamed( RouterConts .history, arguments: { 'historyTab': 0, }, ); } catch (e) { Fluttertoast.showToast( msg: 'Failed to submit rating: $e', toastLength: Toast .LENGTH_LONG, gravity: ToastGravity .BOTTOM, backgroundColor: Colors .red, textColor: Colors .white, ); } }, style: ElevatedButton.styleFrom( backgroundColor: Color( 0xFF0066FF, ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular( 20.83, ), ), ), child: const Text( "Submit", textAlign: TextAlign .center, style: TextStyle( fontFamily: 'Gilroy-Bold', fontWeight: FontWeight .w700, fontSize: 20, color: Colors .white, ), ), ), ), ), ], ), ), ); }, ), ); }, ); } } else if (status == 404) { if (context.mounted) { String message = 'Maximum bookings reached'; if (jsonResponse['data'] != null) { message = jsonResponse['data'] .toString(); } Fluttertoast.showToast( msg: message, toastLength: Toast.LENGTH_LONG, gravity: ToastGravity.BOTTOM, backgroundColor: Colors.orange, textColor: Colors.white, ); Get.offAllNamed( RouterConts.packageList, arguments: 1, ); } } else { if (context.mounted) { String message = 'Booking failed. Please try again.'; if (jsonResponse['message'] != null) { message = jsonResponse['message'] .toString(); } Fluttertoast.showToast( msg: message, toastLength: Toast.LENGTH_LONG, gravity: ToastGravity.BOTTOM, backgroundColor: Colors.red, textColor: Colors.white, ); } } } catch (e) { print('Network/API Error: $e'); print('Error Type: ${e.runtimeType}'); String errorMessage = 'Connection failed. Please check:'; if (e.toString().contains( 'SocketException', ) || e.toString().contains( 'NetworkException', )) { errorMessage = 'No internet connection. Please check your network.'; } else if (e.toString().contains( 'TimeoutException', )) { errorMessage = 'Request timeout. Please try again.'; } else if (e.toString().contains( 'FormatException', )) { errorMessage = 'Server returned invalid response. Please contact support.'; } else { errorMessage = 'Booking failed: ${e.toString()}'; } if (context.mounted) { Fluttertoast.showToast( msg: errorMessage, toastLength: Toast.LENGTH_LONG, gravity: ToastGravity.BOTTOM, backgroundColor: Colors.red, textColor: Colors.white, ); } } } }, child: const Text( "Book Now", style: TextStyle( fontFamily: 'Gilroy-Bold', fontSize: 18, color: Colors.white, ), ), ), ), const SizedBox(height: 30), ], ), ), ), ], ), ), ], ), ); } Future _testApiConnectivity() async { try { final uri = Uri.parse(ConstsApi.bookservice); final baseUrl = '${uri.scheme}://${uri.host}'; print('Testing connectivity to: $baseUrl'); final response = await http .get( Uri.parse(baseUrl), headers: {'Content-Type': 'application/json'}, ) .timeout(const Duration(seconds: 10)); print('Connectivity test - Status: ${response.statusCode}'); return response.statusCode < 500; } catch (e) { print('Connectivity test failed: $e'); return false; } } void _scrollToField(int fieldIndex) { final offset = 300.0 + (fieldIndex * 150.0); if (_scrollController.hasClients) { _scrollController.animateTo( offset, duration: const Duration(milliseconds: 300), curve: Curves.easeOut, ); } } Widget buildFieldTitle(String title) { return Text( title, style: const TextStyle( fontFamily: 'Gilroy-Bold', fontWeight: FontWeight.w700, fontSize: 16, ), ); } Widget buildTextFormField({ required TextEditingController controller, FocusNode? focusNode, // Add FocusNode parameter required String hintText, TextInputType? keyboardType, int maxLines = 1, Function()? onTap, String? Function(String?)? validator, }) { return TextFormField( controller: controller, focusNode: focusNode, // Use FocusNode keyboardType: keyboardType, maxLines: maxLines, onTap: onTap, // SOLUTION 8: Remove onChanged that causes frequent rebuilds // Only validate on form submission, not on every character change decoration: InputDecoration( hintText: hintText, filled: true, fillColor: Colors.white, contentPadding: const EdgeInsets.symmetric( horizontal: 16, vertical: 16, ), border: OutlineInputBorder( borderRadius: BorderRadius.circular(14), borderSide: const BorderSide(color: Color(0xFFB7B7B7), width: 1), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(14), borderSide: const BorderSide(color: Color(0xFFB7B7B7), width: 1), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(14), borderSide: const BorderSide(color: Color(0xFFB7B7B7), width: 1), ), errorBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(14), borderSide: const BorderSide(color: Colors.red, width: 1), ), focusedErrorBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(14), borderSide: const BorderSide(color: Colors.red, width: 1), ), ), style: const TextStyle(fontFamily: 'Gilroy-Medium', fontSize: 14), validator: validator, ); } }