import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:get/get.dart'; import 'package:taxglide/consts/app_colors.dart'; import 'package:taxglide/consts/comman_dropdown.dart'; import 'package:taxglide/consts/image_permission.dart'; import 'package:taxglide/consts/validation_popup.dart'; import 'package:taxglide/controller/api_contoller.dart'; import 'package:taxglide/controller/api_repository.dart'; import 'package:taxglide/model/city_model.dart' as country; import 'package:taxglide/model/country_model.dart' as country; class KycDetailScreen extends ConsumerStatefulWidget { const KycDetailScreen({super.key}); @override ConsumerState createState() => _KycDetailScreenState(); } class _KycDetailScreenState extends ConsumerState { final ScrollController _scrollController = ScrollController(); // Controllers late final TextEditingController companyCtrl; late final TextEditingController panCtrl; late final TextEditingController gstCtrl; late final TextEditingController tanCtrl; late final TextEditingController cinCtrl; late final TextEditingController yearCtrl; late final TextEditingController emailCtrl; late final TextEditingController phoneCtrl; late final TextEditingController addressCtrl; late final TextEditingController pincodeCtrl; // Focus Nodes late final FocusNode companyNode; late final FocusNode panNode; late final FocusNode gstNode; late final FocusNode tanNode; late final FocusNode cinNode; late final FocusNode yearNode; late final FocusNode emailNode; late final FocusNode phoneNode; late final FocusNode addressNode; late final FocusNode pincodeNode; // Keys final companyKey = GlobalKey(); final panKey = GlobalKey(); final gstKey = GlobalKey(); final tanKey = GlobalKey(); final cinKey = GlobalKey(); final yearKey = GlobalKey(); final emailKey = GlobalKey(); final phoneKey = GlobalKey(); final addressKey = GlobalKey(); final pincodeKey = GlobalKey(); final countryKey = GlobalKey(); final stateKey = GlobalKey(); final cityKey = GlobalKey(); final panImageKey = GlobalKey(); final gstImageKey = GlobalKey(); final incImageKey = GlobalKey(); final logoImageKey = GlobalKey(); // Selection state String? selectedCountryName; int? selectedCountryId; String? selectedStateName; int? selectedStateId; String? selectedCityName; int? selectedCityId; String? countryError; String? stateError; String? cityError; // File state (now supports both image and PDF) File? panImageFile; String? panImageUrl; File? gstImageFile; String? gstImageUrl; File? incorporationImageFile; String? incorporationImageUrl; File? logoImageFile; String? logoImageUrl; String? panImageError; String? gstImageError; String? incImageError; String? logoImageError; bool isChecked = false; Map errors = {}; bool _isInitialized = false; bool _isSubmitting = false; @override void initState() { super.initState(); companyCtrl = TextEditingController(); panCtrl = TextEditingController(); gstCtrl = TextEditingController(); tanCtrl = TextEditingController(); cinCtrl = TextEditingController(); yearCtrl = TextEditingController(); emailCtrl = TextEditingController(); phoneCtrl = TextEditingController(); addressCtrl = TextEditingController(); pincodeCtrl = TextEditingController(); companyNode = FocusNode(); panNode = FocusNode(); gstNode = FocusNode(); tanNode = FocusNode(); cinNode = FocusNode(); yearNode = FocusNode(); emailNode = FocusNode(); phoneNode = FocusNode(); addressNode = FocusNode(); pincodeNode = FocusNode(); _setupFocusListeners(); } void _setupFocusListeners() { companyNode.addListener(() => _scrollToFocused(companyKey, companyNode)); panNode.addListener(() => _scrollToFocused(panKey, panNode)); gstNode.addListener(() => _scrollToFocused(gstKey, gstNode)); tanNode.addListener(() => _scrollToFocused(tanKey, tanNode)); cinNode.addListener(() => _scrollToFocused(cinKey, cinNode)); yearNode.addListener(() => _scrollToFocused(yearKey, yearNode)); emailNode.addListener(() => _scrollToFocused(emailKey, emailNode)); phoneNode.addListener(() => _scrollToFocused(phoneKey, phoneNode)); addressNode.addListener(() => _scrollToFocused(addressKey, addressNode)); pincodeNode.addListener(() => _scrollToFocused(pincodeKey, pincodeNode)); } void _scrollToFocused(GlobalKey key, FocusNode node) { if (node.hasFocus && mounted) { Future.delayed(const Duration(milliseconds: 250), () { if (!mounted) return; final context = key.currentContext; if (context != null) { Scrollable.ensureVisible( context, duration: const Duration(milliseconds: 300), curve: Curves.easeInOut, alignment: 0.2, ); } }); } } void _scrollToWidget(GlobalKey key) { if (!mounted) return; Future.delayed(const Duration(milliseconds: 100), () { if (!mounted) return; final context = key.currentContext; if (context != null) { Scrollable.ensureVisible( context, duration: const Duration(milliseconds: 300), curve: Curves.easeInOut, alignment: 0.2, ); } }); } bool _validate() { final panRegex = RegExp(r'^[A-Z]{5}[0-9]{4}[A-Z]{1}$'); final gstRegex = RegExp( r'^[0-9]{2}[A-Z]{5}[0-9]{4}[A-Z]{1}[1-9A-Z]{1}Z[0-9A-Z]{1}$', ); final tanRegex = RegExp(r'^[A-Z]{4}[0-9]{5}[A-Z]{1}$'); final cinRegex = RegExp( r'^[A-Z]{1}[0-9]{5}[A-Z]{2}[0-9]{4}[A-Z]{3}[0-9]{6}$', ); final pincodeRegex = RegExp(r'^[0-9]{6}$'); setState(() { errors.clear(); if (companyCtrl.text.isEmpty) { errors['company'] = 'Company name required'; } // PAN validation with exact digit check if (panCtrl.text.isEmpty) { errors['pan'] = 'PAN required'; } else { final panUpper = panCtrl.text.toUpperCase(); if (!panRegex.hasMatch(panUpper)) { errors['pan'] = 'Invalid PAN format (e.g. ABCDE1234F)'; } else { // Extract the 4 digits (positions 5-8) final panDigits = panUpper.substring(5, 9); final panYear = int.tryParse(panDigits); if (panYear == null) { errors['pan'] = 'Invalid PAN number format'; } } } if (gstCtrl.text.isEmpty) { errors['gst'] = 'GST required'; } else if (!gstRegex.hasMatch(gstCtrl.text.toUpperCase())) { errors['gst'] = 'Invalid GST format (e.g. 22ABCDE1234F1Z5)'; } // TAN validation with year check if (tanCtrl.text.isEmpty) { errors['tan'] = 'TAN required'; } else { final tanUpper = tanCtrl.text.toUpperCase(); if (!tanRegex.hasMatch(tanUpper)) { errors['tan'] = 'Invalid TAN format (e.g. ABCD12345E)'; } else { // Extract the 5 digits (positions 4-8) final tanDigits = tanUpper.substring(4, 9); final tanYear = int.tryParse(tanDigits); if (tanYear == null) { errors['tan'] = 'Invalid TAN number format'; } } } if (cinCtrl.text.isEmpty) { errors['cin'] = 'CIN required'; } else if (!cinRegex.hasMatch(cinCtrl.text.toUpperCase())) { errors['cin'] = 'Invalid CIN format (e.g. L12345TN2000PLC123456)'; } // Year of Incorporation validation if (yearCtrl.text.isEmpty) { errors['year'] = 'Year required'; } else { final year = int.tryParse(yearCtrl.text); final currentYear = DateTime.now().year; if (year == null) { errors['year'] = 'Invalid year format'; } else if (year < 1800 || year > currentYear) { errors['year'] = 'Year must be between 1800 and $currentYear'; } } if (emailCtrl.text.isEmpty) { errors['email'] = 'Email required'; } if (phoneCtrl.text.isEmpty) { errors['phone'] = 'Phone required'; } if (addressCtrl.text.isEmpty) { errors['address'] = 'Address required'; } // Pincode validation - must be exactly 6 digits if (pincodeCtrl.text.isEmpty) { errors['pincode'] = 'Pincode required'; } else if (!pincodeRegex.hasMatch(pincodeCtrl.text)) { errors['pincode'] = 'Pincode must be exactly 6 digits'; } countryError = selectedCountryId == null ? 'Select country' : null; stateError = selectedStateId == null ? 'Select state' : null; cityError = selectedCityId == null ? 'Select city' : null; panImageError = (panImageFile == null && panImageUrl == null) ? 'Upload PAN card image/PDF' : null; gstImageError = (gstImageFile == null && gstImageUrl == null) ? 'Upload GST certificate image/PDF' : null; incImageError = (incorporationImageFile == null && incorporationImageUrl == null) ? 'Upload incorporation certificate image/PDF' : null; logoImageError = (logoImageFile == null && logoImageUrl == null) ? 'Upload company logo' : null; }); if (errors.isNotEmpty) { final firstError = errors.keys.first; final keyMap = { 'company': companyKey, 'pan': panKey, 'gst': gstKey, 'tan': tanKey, 'cin': cinKey, 'year': yearKey, 'email': emailKey, 'phone': phoneKey, 'address': addressKey, 'pincode': pincodeKey, }; _scrollToWidget(keyMap[firstError]!); return false; } if (countryError != null) { _scrollToWidget(countryKey); return false; } if (stateError != null) { _scrollToWidget(stateKey); return false; } if (cityError != null) { _scrollToWidget(cityKey); return false; } if (panImageError != null) { _scrollToWidget(panImageKey); return false; } if (gstImageError != null) { _scrollToWidget(gstImageKey); return false; } if (incImageError != null) { _scrollToWidget(incImageKey); return false; } if (logoImageError != null) { _scrollToWidget(logoImageKey); return false; } return true; } Future _handleSubmit() async { if (_isSubmitting) return; if (!_validate()) return; if (!isChecked) { Get.snackbar( "Error", "Please accept declaration", backgroundColor: Colors.red, colorText: Colors.white, duration: const Duration(seconds: 2), ); return; } setState(() => _isSubmitting = true); try { await ApiRepository().kycFormUpdate( logo: logoImageFile, panFile: panImageFile, gstFile: gstImageFile, incorporationFile: incorporationImageFile, countryId: selectedCountryId!, stateId: selectedStateId!, cityId: selectedCityId!, companyPincode: pincodeCtrl.text, companyAddress: addressCtrl.text, panNumber: panCtrl.text, gstNumber: gstCtrl.text, tanNumber: tanCtrl.text, cinNumber: cinCtrl.text, yearOfIncorporation: yearCtrl.text, address: addressCtrl.text, ); if (!mounted) return; ValidationPopup().showSuccessMessage( context, "Form Submitted Successfully", ); ref.invalidate(profileProvider); await Future.delayed(const Duration(milliseconds: 500)); if (mounted) { Navigator.pop(context); } } catch (e) { if (!mounted) return; String errorMessage; if (e is Map) { final buffer = StringBuffer(); e.forEach((key, value) { buffer.writeln(value); }); errorMessage = buffer.toString().trim(); } else { errorMessage = e.toString(); } ValidationPopup().showErrorMessage(context, errorMessage); } finally { if (mounted) { setState(() => _isSubmitting = false); } } } @override Widget build(BuildContext context) { final profileAsync = ref.watch(profileProvider); final countryAsync = ref.watch(countryAndStatesProvider); return Scaffold( backgroundColor: AppColors.white, body: SafeArea( child: Stack( children: [ profileAsync.when( data: (profileData) { if (!_isInitialized && profileData.data != null) { WidgetsBinding.instance.addPostFrameCallback((_) { if (!mounted) return; setState(() { final p = profileData.data!; companyCtrl.text = p.companyName ?? ''; panCtrl.text = p.panNumber ?? ''; gstCtrl.text = p.gstNumber ?? ''; tanCtrl.text = p.tanNumber ?? ''; cinCtrl.text = p.cin ?? ''; yearCtrl.text = p.yearOfIncorporation?.toString() ?? ''; emailCtrl.text = p.companyEmail ?? ''; phoneCtrl.text = p.companyMobile ?? ''; addressCtrl.text = p.companyAddress ?? ''; pincodeCtrl.text = p.companyPincode ?? ''; selectedCountryName = p.countryName; selectedStateName = p.stateName; selectedCityName = p.districtName; panImageUrl = p.panFile; gstImageUrl = p.gstFile; incorporationImageUrl = p.incorporationFile; logoImageUrl = p.logo; _isInitialized = true; }); }); } return countryAsync.when( data: (countryModel) { final countryList = countryModel.countriesData ?.map((e) => e.countryName ?? '') .toList() ?? []; final selectedCountryData = countryModel.countriesData ?.firstWhere( (c) => c.countryName == selectedCountryName, orElse: () => country.CountryData(), ); if (selectedCountryData?.id != null) { selectedCountryId = selectedCountryData!.id; } final selectedStateList = countryModel.statesData ?.where((s) => s.countryId == selectedCountryId) .map((s) => s.stateName ?? '') .toList() ?? []; final selectedStateData = countryModel.statesData ?.firstWhere( (s) => s.stateName == selectedStateName, orElse: () => country.StateData(), ); if (selectedStateData?.id != null) { selectedStateId = selectedStateData!.id; } final cityAsync = selectedStateId != null ? ref.watch(fetchCityProvider(selectedStateId!)) : const AsyncValue.data(null); return cityAsync.when( data: (cityModel) { final cityList = cityModel?.districtsData ?.map((c) => c.districtName ?? '') .toList() ?? []; if (cityModel?.districtsData != null) { final selectedCityData = cityModel!.districtsData! .firstWhere( (c) => c.districtName == selectedCityName, orElse: () => country.DistrictData(), ); if (selectedCityData.id != null) { selectedCityId = selectedCityData.id; } } return _buildForm( countryList, selectedStateList, cityList, countryModel, cityModel, ); }, loading: () => _buildForm( countryList, selectedStateList, [], countryModel, null, ), error: (e, _) => _buildForm( countryList, selectedStateList, [], countryModel, null, ), ); }, loading: () => const Center( child: CircularProgressIndicator(color: Colors.blue), ), error: (e, _) => Center(child: Text('Error: $e')), ); }, loading: () => const Center( child: CircularProgressIndicator(color: Colors.blue), ), error: (e, _) => Center(child: Text('Error: $e')), ), if (_isSubmitting) Container( color: Colors.black54, child: const Center( child: Card( child: Padding( padding: EdgeInsets.all(20), child: Column( mainAxisSize: MainAxisSize.min, children: [ CircularProgressIndicator(), SizedBox(height: 16), Text( 'Submitting KYC Form...', style: TextStyle(fontSize: 16), ), ], ), ), ), ), ), ], ), ), ); } Widget _buildForm( List countries, List states, List cities, country.CountryModel countryModel, country.CityModel? cityModel, ) { return SingleChildScrollView( controller: _scrollController, padding: const EdgeInsets.symmetric(horizontal: 23, vertical: 21), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox( height: 50, width: double.infinity, child: Stack( alignment: Alignment.center, children: [ const Text( "KYC Form", textAlign: TextAlign.center, style: TextStyle( fontFamily: 'Gilroy-SemiBold', fontWeight: FontWeight.w400, fontStyle: FontStyle.normal, fontSize: 24, height: 1.3, letterSpacing: 0.04 * 24, color: Color(0xFF111827), ), ), Positioned( left: 0, child: GestureDetector( onTap: () => Get.back(), child: const Icon(Icons.arrow_back_ios_rounded), ), ), ], ), ), const SizedBox(height: 30), // Text fields LabeledTextField( fieldKey: companyKey, label: 'Company Name', keyName: 'company', hint: 'Enter company name', controller: companyCtrl, focusNode: companyNode, readOnly: true, errors: errors, onChanged: (val) { if (errors['company'] != null && val.trim().isNotEmpty) { setState(() => errors['company'] = null); } }, ), LabeledTextField( fieldKey: panKey, label: 'PAN Number', keyName: 'pan', hint: 'Enter PAN number', controller: panCtrl, focusNode: panNode, textCapitalization: TextCapitalization.characters, inputFormatters: [ FilteringTextInputFormatter.allow(RegExp(r'[A-Za-z0-9]')), LengthLimitingTextInputFormatter(10), ], errors: errors, onChanged: (val) { if (errors['pan'] != null) { final upper = val.toUpperCase(); if (RegExp(r'^[A-Z]{5}[0-9]{4}[A-Z]{1}$').hasMatch(upper)) { if (int.tryParse(upper.substring(5, 9)) != null) { setState(() => errors['pan'] = null); } } } }, ), LabeledTextField( fieldKey: gstKey, label: 'GST Number', keyName: 'gst', hint: 'Enter GST number', controller: gstCtrl, focusNode: gstNode, textCapitalization: TextCapitalization.characters, inputFormatters: [ FilteringTextInputFormatter.allow(RegExp(r'[A-Za-z0-9]')), LengthLimitingTextInputFormatter(15), ], errors: errors, onChanged: (val) { if (errors['gst'] != null) { if (RegExp( r'^[0-9]{2}[A-Z]{5}[0-9]{4}[A-Z]{1}[1-9A-Z]{1}Z[0-9A-Z]{1}$', ).hasMatch(val.toUpperCase())) { setState(() => errors['gst'] = null); } } }, ), LabeledTextField( fieldKey: tanKey, label: 'TAN Number', keyName: 'tan', hint: 'Enter TAN number', controller: tanCtrl, focusNode: tanNode, textCapitalization: TextCapitalization.characters, inputFormatters: [ FilteringTextInputFormatter.allow(RegExp(r'[A-Za-z0-9]')), LengthLimitingTextInputFormatter(10), ], errors: errors, onChanged: (val) { if (errors['tan'] != null) { final upper = val.toUpperCase(); if (RegExp(r'^[A-Z]{4}[0-9]{5}[A-Z]{1}$').hasMatch(upper)) { if (int.tryParse(upper.substring(4, 9)) != null) { setState(() => errors['tan'] = null); } } } }, ), LabeledTextField( fieldKey: cinKey, label: 'CIN Number', keyName: 'cin', hint: 'Enter CIN number', controller: cinCtrl, focusNode: cinNode, textCapitalization: TextCapitalization.characters, inputFormatters: [ FilteringTextInputFormatter.allow(RegExp(r'[A-Za-z0-9]')), LengthLimitingTextInputFormatter(21), ], errors: errors, onChanged: (val) { if (errors['cin'] != null) { if (RegExp( r'^[A-Z]{1}[0-9]{5}[A-Z]{2}[0-9]{4}[A-Z]{3}[0-9]{6}$', ).hasMatch(val.toUpperCase())) { setState(() => errors['cin'] = null); } } }, ), LabeledTextField( fieldKey: yearKey, label: 'Year of Incorporation', keyName: 'year', hint: 'Enter year (e.g. 2020)', controller: yearCtrl, focusNode: yearNode, keyboardType: TextInputType.number, inputFormatters: [ FilteringTextInputFormatter.digitsOnly, LengthLimitingTextInputFormatter(4), ], errors: errors, onChanged: (val) { if (errors['year'] != null) { final y = int.tryParse(val); if (y != null && y >= 1800 && y <= DateTime.now().year) { setState(() => errors['year'] = null); } } }, ), LabeledTextField( fieldKey: emailKey, label: 'Official Email Address', keyName: 'email', hint: 'Enter email', controller: emailCtrl, focusNode: emailNode, readOnly: true, errors: errors, onChanged: (val) { if (errors['email'] != null && val.trim().isNotEmpty) { setState(() => errors['email'] = null); } }, ), LabeledTextField( fieldKey: phoneKey, label: 'Phone Number', keyName: 'phone', hint: 'Enter phone number', controller: phoneCtrl, focusNode: phoneNode, keyboardType: TextInputType.phone, errors: errors, readOnly: true, onChanged: (val) { if (errors['phone'] != null && val.trim().isNotEmpty) { setState(() => errors['phone'] = null); } }, ), LabeledTextField( fieldKey: addressKey, label: 'Office Address', keyName: 'address', hint: 'Enter address', controller: addressCtrl, focusNode: addressNode, errors: errors, onChanged: (val) { if (errors['address'] != null && val.trim().isNotEmpty) { setState(() => errors['address'] = null); } }, ), // Dropdowns _buildDropdown( key: countryKey, label: "Country", hint: "Select Country", value: selectedCountryName, items: countries, onChanged: (val) { if (!mounted) return; final countryData = countryModel.countriesData?.firstWhere( (c) => c.countryName == val, orElse: () => country.CountryData(), ); setState(() { selectedCountryName = val; selectedCountryId = countryData?.id; selectedStateName = null; selectedStateId = null; selectedCityName = null; selectedCityId = null; stateError = null; cityError = null; countryError = null; }); }, error: countryError, ), _buildDropdown( key: stateKey, label: "State", hint: "Select State", value: selectedStateName, items: states, onChanged: (val) { if (!mounted) return; final stateData = countryModel.statesData?.firstWhere( (s) => s.stateName == val, orElse: () => country.StateData(), ); setState(() { selectedStateName = val; selectedStateId = stateData?.id; selectedCityName = null; selectedCityId = null; stateError = null; cityError = null; }); }, error: stateError, ), _buildDropdown( key: cityKey, label: "City", hint: "Select City", value: selectedCityName, items: cities, onChanged: (val) { if (!mounted) return; final cityData = cityModel?.districtsData?.firstWhere( (c) => c.districtName == val, orElse: () => country.DistrictData(), ); setState(() { selectedCityName = val; selectedCityId = cityData?.id; cityError = null; }); }, error: cityError, ), LabeledTextField( fieldKey: pincodeKey, label: 'Pincode', keyName: 'pincode', hint: 'Enter 6 digit pincode', controller: pincodeCtrl, focusNode: pincodeNode, keyboardType: TextInputType.number, inputFormatters: [ FilteringTextInputFormatter.digitsOnly, LengthLimitingTextInputFormatter(6), ], errors: errors, onChanged: (val) { if (errors['pincode'] != null) { if (RegExp(r'^[0-9]{6}$').hasMatch(val)) { setState(() => errors['pincode'] = null); } } }, ), const SizedBox(height: 20), // 🔥 Updated File Upload Fields (Image/PDF support for PAN, GST, Incorporation) FileUploadSectionField( fileKey: panImageKey, title: "Upload PAN Card (Image/PDF)", fileData: panImageFile, fileUrl: panImageUrl, errorText: panImageError, setError: (err) => setState(() => panImageError = err), onFileSelected: (file) => setState(() { panImageFile = file; panImageUrl = null; }), onFileRemoved: () => setState(() { panImageFile = null; panImageUrl = null; }), ), FileUploadSectionField( fileKey: gstImageKey, title: "Upload GST Certificate (Image/PDF)", fileData: gstImageFile, fileUrl: gstImageUrl, errorText: gstImageError, setError: (err) => setState(() => gstImageError = err), onFileSelected: (file) => setState(() { gstImageFile = file; gstImageUrl = null; }), onFileRemoved: () => setState(() { gstImageFile = null; gstImageUrl = null; }), ), FileUploadSectionField( fileKey: incImageKey, title: "Upload Incorporation Certificate (Image/PDF)", fileData: incorporationImageFile, fileUrl: incorporationImageUrl, errorText: incImageError, setError: (err) => setState(() => incImageError = err), onFileSelected: (file) => setState(() { incorporationImageFile = file; incorporationImageUrl = null; }), onFileRemoved: () => setState(() { incorporationImageFile = null; incorporationImageUrl = null; }), ), // 🔥 Logo remains image-only SingleImageSectionField( imageKey: logoImageKey, title: "Upload Company Logo", imageFile: logoImageFile, imageUrl: logoImageUrl, errorText: logoImageError, setError: (err) => setState(() => logoImageError = err), onImageSelected: (file) => setState(() { logoImageFile = file; logoImageUrl = null; }), onImageRemoved: () => setState(() { logoImageFile = null; logoImageUrl = null; }), ), Row( children: [ Checkbox( value: isChecked, onChanged: (v) => setState(() => isChecked = v ?? false), ), const Expanded( child: Text( "I hereby declare that the above information is true.", ), ), ], ), const SizedBox(height: 10), SizedBox( width: double.infinity, child: ElevatedButton( onPressed: _isSubmitting ? null : _handleSubmit, style: ElevatedButton.styleFrom( backgroundColor: AppColors.commanbutton, padding: const EdgeInsets.symmetric(vertical: 14), disabledBackgroundColor: Colors.grey, ), child: _isSubmitting ? const SizedBox( height: 20, width: 20, child: CircularProgressIndicator( color: Colors.white, strokeWidth: 2, ), ) : const Text( "Submit", style: TextStyle(color: Colors.white, fontSize: 16), ), ), ), const SizedBox(height: 20), ], ), ); } Widget _buildDropdown({ required GlobalKey key, required String label, required String hint, required String? value, required List items, required void Function(String?) onChanged, String? error, }) { return Padding( key: key, padding: const EdgeInsets.only(bottom: 15), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( label, style: const TextStyle( fontWeight: FontWeight.w600, fontSize: 16, color: Colors.black, ), ), const SizedBox(height: 8), CommanDropdown( items: items, value: value, hint: hint, onChanged: onChanged, ), if (error != null) Padding( padding: const EdgeInsets.only(top: 4, left: 6), child: Text( error, style: const TextStyle(color: Colors.red, fontSize: 12), ), ), ], ), ); } @override void dispose() { companyCtrl.dispose(); panCtrl.dispose(); gstCtrl.dispose(); tanCtrl.dispose(); cinCtrl.dispose(); yearCtrl.dispose(); emailCtrl.dispose(); phoneCtrl.dispose(); addressCtrl.dispose(); pincodeCtrl.dispose(); companyNode.dispose(); panNode.dispose(); gstNode.dispose(); tanNode.dispose(); cinNode.dispose(); yearNode.dispose(); emailNode.dispose(); phoneNode.dispose(); addressNode.dispose(); pincodeNode.dispose(); _scrollController.dispose(); super.dispose(); } }