// edit_common_account_screen.dart import 'dart:async'; import 'package:dropdown_button2/dropdown_button2.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; import 'package:generp/Notifiers/HomeScreenNotifier.dart'; import 'package:generp/Notifiers/commonProvider/editCommonAccountProvider.dart'; import 'package:generp/Utils/app_colors.dart'; import 'package:generp/Utils/commonWidgets.dart'; import 'package:generp/Utils/dropdownTheme.dart'; import '../../Models/commonModels/DistrictsResponse.dart'; import '../../Models/commonModels/SubLocationsResponse.dart'; import '../../Models/commonModels/commonAddAccountsViewResponse.dart'; class EditCommonAccountScreen extends StatefulWidget { final String accountID; const EditCommonAccountScreen({super.key, required this.accountID}); @override State createState() => _EditCommonAccountScreenState(); } class _EditCommonAccountScreenState extends State { Timer? _nameDebounce; Timer? _gstDebounce; final _formKey = GlobalKey(); // Focus nodes late List focusNodes; static const int FN_ACCOUNT = 0; static const int FN_NAME = 1; static const int FN_NAME_NEXT = 2; static const int FN_MOBILE = 3; static const int FN_CONTACT = 4; static const int FN_GST = 5; static const int FN_STATE = 6; static const int FN_DISTRICT = 7; static const int FN_SUBLOC = 8; static const int FN_ADDRESS = 9; static const int FN_BANK_ACC = 10; static const int FN_IFSC = 11; static const int FN_BANK_NAME = 12; static const int FN_BANK_BRANCH = 13; static const int FN_BANK_HOLDER = 14; static const int FN_UPI = 15; static const int FN_CONTACT_DESIG = 16; static const int FN_CONTACT_ALTMOB = 17; static const int FN_CONTACT_TELE = 18; static const int FN_CONTACT_MAIL = 19; int _currentStep = 0; final Dropdowntheme ddtheme = Dropdowntheme(); @override void initState() { super.initState(); // create enough focus nodes (0..19) focusNodes = List.generate(20, (_) => FocusNode()); WidgetsBinding.instance.addPostFrameCallback((_) { final prov = Provider.of(context, listen: false); // ensure only once: reset then load prov.resetValues(); prov.loadAccountDetailsForEdit(context, widget.accountID); }); } @override void dispose() { for (final fn in focusNodes) { try { fn.dispose(); } catch (_) {} } _nameDebounce?.cancel(); _gstDebounce?.cancel(); // reset provider values when leaving screen to avoid stale state final prov = Provider.of(context, listen: false); prov.resetValues(); super.dispose(); } // small helper to check IFSC simple pattern bool _isIfscValidFormat(String ifsc) { final reg = RegExp(r'^[A-Za-z]{4}0[A-Za-z0-9]{6}$'); return reg.hasMatch(ifsc.trim()); } bool _isGstValidFormat(String gst) { gst = gst.trim().toUpperCase(); final reg = RegExp(r'^[0-3][0-9][A-Z]{5}[0-9]{4}[A-Z][1-9A-Z]Z[0-9A-Z]$'); return reg.hasMatch(gst); } Future _validateGstIfNeeded(EditCommonAccountProvider provider) async { final gst = provider.gstNumberController.text.trim(); if (!_isGstValidFormat(gst)) { provider.gstNumberError = "Invalid GST format"; provider.notifyListeners(); return; } if (gst.isEmpty) return; final homeProv = Provider.of(context, listen: false); provider.isLoading = true; await provider.validateGstNumber(homeProv.empId, homeProv.session, gst); provider.isLoading = false; } Future _validateBankIfNeeded(EditCommonAccountProvider provider) async { final acc = provider.bankAcNumberController.text.trim(); final ifsc = provider.bankIfscCotroller.text.trim(); final accList = provider.detailsListResponse?.accountList!.first; if (acc.isEmpty && ifsc.isEmpty) return; if (acc.isNotEmpty && ifsc.isEmpty) { provider.bankIFSCError = "IFSC is required when account number is entered"; provider.notifyListeners(); return; } if (acc.isEmpty && ifsc.isEmpty) { provider.bankNameController.text = accList?.bankName ?? ""; provider.branchNameController.text = accList?.bankBranchName ?? ""; provider.bankHolderNameController.text = accList?.bankAccountHolderName ?? ""; provider.notifyListeners(); return; } if (!_isIfscValidFormat(ifsc)) { provider.bankIFSCError = "Invalid IFSC format"; provider.notifyListeners(); return; } final homeProv = Provider.of(context, listen: false); provider.isLoading = true; await provider.validateBankDetails(homeProv.empId, homeProv.session, acc); provider.isLoading = false; // If validateBankDetails succeeded provider.bankResponse will be set and UI autofills if (provider.bankResponse != null && provider.bankResponse!.error == "0") { provider.bankNameController.text = provider.bankResponse!.bankName ?? provider.bankNameController.text; provider.branchNameController.text = provider.bankResponse!.branch ?? provider.branchNameController.text; provider.bankHolderNameController.text = provider.bankResponse!.nameAtBank ?? provider.bankHolderNameController.text; provider.notifyListeners(); } } int? _firstErrorStep(EditCommonAccountProvider p) { if ((p.accountError?.isNotEmpty ?? false) || (p.nameError?.isNotEmpty ?? false) || (p.mobileError?.isNotEmpty ?? false) || (p.contactPersonError?.isNotEmpty ?? false)) { return 0; } if ((p.stateError?.isNotEmpty ?? false) || (p.districtError?.isNotEmpty ?? false) || (p.localityError?.isNotEmpty ?? false) || (p.addressError?.isNotEmpty ?? false)) { return 1; } if ((p.gstNumberError?.isNotEmpty ?? false) || (p.bankAcNumberError?.isNotEmpty ?? false) || (p.bankIFSCError?.isNotEmpty ?? false) || (p.banknameError?.isNotEmpty ?? false) || (p.bankBranchError?.isNotEmpty ?? false) || (p.bankHolderNameError?.isNotEmpty ?? false) || (p.upiError?.isNotEmpty ?? false)) { return 2; } if ((p.desigantionError?.isNotEmpty ?? false) || (p.altMobError?.isNotEmpty ?? false) || (p.teleError?.isNotEmpty ?? false) || (p.mailError?.isNotEmpty ?? false)) { return 3; } return null; } @override Widget build(BuildContext context) { return Consumer(builder: (context, provider, child) { return Scaffold( appBar: appbar2New(context, "Edit Account", provider.resetValues, const SizedBox.shrink(), 0xFFFFFFFF), backgroundColor: AppColors.scaffold_bg_color, body: Form( key: _formKey, child: Stepper( type: StepperType.horizontal, currentStep: _currentStep, onStepContinue: () async { // custom continue logic same as your flow if (_currentStep == 0) { provider.validateStep(); if (provider.gstNumberController.text.trim().isNotEmpty) { await _validateGstIfNeeded(provider); } if (provider.validateStep1(context)) { setState(() => _currentStep = 1); } else { provider.notifyListeners(); setState(() => _currentStep = 0); } } else if (_currentStep == 1) { // optional step -> only validate if something filled if (provider.stateSearchController.text.trim().isNotEmpty || provider.districtSearchController.text.trim().isNotEmpty || provider.addressController.text.trim().isNotEmpty) { if (provider.validateStep1(context)) { setState(() => _currentStep = 2); } else { provider.notifyListeners(); } } else { setState(() => _currentStep = 2); } } else if (_currentStep == 2) { // bank/gst step: if fields present, validate if (provider.gstNumberController.text.trim().isNotEmpty) { await _validateGstIfNeeded(provider); } provider.validateStep3(); if (provider.bankAcNumberController.text.trim().isNotEmpty || provider.bankIfscCotroller.text.trim().isNotEmpty) { await _validateBankIfNeeded(provider); } // if any errors remain, stay final hasBankErrors = (provider.gstNumberError?.isNotEmpty ?? false) || (provider.bankAcNumberError?.isNotEmpty ?? false) || (provider.bankIFSCError?.isNotEmpty ?? false); if (hasBankErrors) { provider.notifyListeners(); setState(() => _currentStep = 2); } else { setState(() => _currentStep = 3); } } else { // last step - do nothing (submit handled by controlsBuilder) } }, onStepCancel: () { if (_currentStep > 0) setState(() => _currentStep -= 1); }, onStepTapped: (i) async { // allow step tapping but guard jump if step0 invalid if (i == 0) { setState(() => _currentStep = 0); } else if (i == 1) { if (provider.validateStep1(context)) { setState(() => _currentStep = 1); } else { provider.notifyListeners(); setState(() => _currentStep = 0); } } else if (i == 2) { if (provider.validateStep1(context)) { setState(() => _currentStep = 2); } else { provider.notifyListeners(); setState(() => _currentStep = 0); } } else if (i == 3) { if (!provider.validateStep1(context)) { provider.notifyListeners(); setState(() => _currentStep = 0); return; } setState(() => _currentStep = 3); } }, connectorColor: WidgetStatePropertyAll(AppColors.app_blue), stepIconBuilder: (stepIndex, stepState) { return CircleAvatar( radius: 12, backgroundColor: stepIndex <= _currentStep ? Colors.transparent : Colors.grey[300], ); }, steps: [ // ---------------- Step 1 ---------------- Step( title: const Text(''), label: const Text("Step 1", style: TextStyle(fontSize: 12)), content: Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10), decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(16)), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("Account", style: TextStyle(color: AppColors.app_blue, fontSize: 16)), const SizedBox(height: 8), // Account dropdown (keeps DropdownButton2 style) errorWidget(context, provider.accountError), const SizedBox(height: 10), // Company Name (debounced uniqueness check) textControllerWidget( context, provider.nameController, "Company Name", "Enter Company Name", (p0) { // local update + recheck with GST provider.updateName(p0); provider.recheckNameWithGst(); // debounce uniqueness API call _nameDebounce?.cancel(); _nameDebounce = Timer(const Duration(milliseconds: 600), () { final val = provider.nameController.text.trim(); if (val.isNotEmpty) provider.checkInputsAPI(context, "name", val); }); }, TextInputType.text, false, null, focusNodes[FN_NAME], focusNodes[FN_NAME_NEXT], TextInputAction.next, ), errorWidget(context, provider.nameError), const SizedBox(height: 8), // Mobile (debounced uniqueness check) textControllerWidget( context, provider.mobileController, "Mobile Number", "Enter Mobile", (p0) { provider.updateMobile(p0); // debounce mobile check _nameDebounce?.cancel(); _nameDebounce = Timer(const Duration(milliseconds: 600), () { final val = provider.mobileController.text.trim(); if (val.isNotEmpty) provider.checkInputsAPI(context, "mob1", val); }); }, TextInputType.phone, false, FilteringTextInputFormatter.digitsOnly, focusNodes[FN_MOBILE], focusNodes[FN_CONTACT], TextInputAction.next, 10, ), errorWidget(context, provider.mobileError), const SizedBox(height: 8), // Contact Person - explicit TextFormField to avoid helper-side reassignment Text("Contact Person Name"), const SizedBox(height: 6), TextFormField( controller: provider.contactPersonController, focusNode: focusNodes[FN_CONTACT], textInputAction: TextInputAction.next, keyboardType: TextInputType.text, onChanged: (v) { // only update provider state (provider may call notifyListeners inside) provider.updateContactPerson(v); // do not call heavy APIs here }, decoration: InputDecoration( hintText: "Enter Contact Person Name", hintStyle: TextStyle( height: 1.2, backgroundColor: AppColors.text_field_color, color: AppColors.grey_semi, ), filled: true, fillColor: AppColors.text_field_color, contentPadding: const EdgeInsets.symmetric(horizontal: 14, vertical: 14), enabledBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(16), borderSide: BorderSide.none), focusedBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(16), borderSide: BorderSide.none), ), ), errorWidget(context, provider.contactPersonError), const SizedBox(height: 12), // GST (debounced validation + autofill). Typing + deletion will work now. Text("GST Number"), const SizedBox(height: 6), TextFormField( controller: provider.gstNumberController, focusNode: focusNodes[FN_GST], textInputAction: TextInputAction.done, keyboardType: TextInputType.text, onChanged: (value) { provider.updateGSTNumber(value); // if cleared -> reset responses immediately (keeps UI clean) if (value.trim().isEmpty) { provider.gstResponse = null; provider.gstNumberError = null; provider.nameError = null; provider.notifyListeners(); return; } // immediate apply when full GST length typed (fast feedback) if (value.trim().length == 15) { _gstDebounce?.cancel(); provider.checkAndApplyGst(context, value.trim()); return; } // otherwise debounce (user typing) _gstDebounce?.cancel(); _gstDebounce = Timer(const Duration(milliseconds: 700), () { final trimmed = provider.gstNumberController.text.trim(); if (trimmed.isNotEmpty && trimmed.length == 15) { provider.checkAndApplyGst(context, trimmed); } }); }, onFieldSubmitted: (value) async { final trimmed = value.trim(); if (trimmed.isNotEmpty) await provider.checkAndApplyGst(context, trimmed); FocusScope.of(context).requestFocus(focusNodes[FN_BANK_ACC]); }, decoration: InputDecoration( hintText: "Enter GST Number", hintStyle: TextStyle( height: 1.2, backgroundColor: AppColors.text_field_color, color: AppColors.grey_semi, ), filled: true, fillColor: AppColors.text_field_color, contentPadding: const EdgeInsets.symmetric(horizontal: 14, vertical: 14), enabledBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(16), borderSide: BorderSide.none), focusedBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(16), borderSide: BorderSide.none), ), ), const SizedBox(height: 4), errorWidget(context, provider.gstNumberError), ], ), ), ), // ---------------- Step 2 ---------------- Step( title: const Text(''), label: const Text("Step 2", style: TextStyle(fontSize: 12)), isActive: _currentStep >= 1, content: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( margin: const EdgeInsets.only(bottom: 10), child: Text( "Address Details", style: TextStyle(color: AppColors.app_blue, fontSize: 16, fontFamily: "JakartaMedium"), ), ), Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10), decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(16)), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // State Text("State"), const SizedBox(height: 6), DropdownButtonHideUnderline( child: Row( children: [ Expanded( child: DropdownButton2( focusNode: focusNodes[FN_STATE], isExpanded: true, hint: const Text('Select State', style: TextStyle(fontSize: 14)), items: provider.states .map((states) => DropdownMenuItem( value: states, child: Text(states.name ?? '', style: const TextStyle(fontSize: 14), overflow: TextOverflow.ellipsis), )) .toList(), value: provider.states.contains(provider.selectedState) ? provider.selectedState : null, onChanged: (States? value) { if (value != null) { provider.selectedState = value; // Clear dependent district/sub-location provider.districts.clear(); provider.subLocations.clear(); provider.getDistrictAPI(context, provider.selectedStateID); } }, dropdownSearchData: DropdownSearchData( searchInnerWidgetHeight: 50, searchController: provider.stateSearchController, searchInnerWidget: Padding( padding: const EdgeInsets.all(8), child: TextFormField( controller: provider.stateSearchController, decoration: InputDecoration(isDense: true, contentPadding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8), hintText: 'Search States...', border: OutlineInputBorder(borderRadius: BorderRadius.circular(8))), ), ), searchMatchFn: (item, searchValue) { return item.value?.name?.toLowerCase().contains(searchValue.toLowerCase()) ?? false; }, ), onMenuStateChange: (isOpen) { if (!isOpen) provider.stateSearchController.clear(); }, buttonStyleData: ddtheme.buttonStyleData, iconStyleData: ddtheme.iconStyleData, menuItemStyleData: ddtheme.menuItemStyleData, dropdownStyleData: ddtheme.dropdownStyleData, ), ), ], ), ), errorWidget(context, provider.stateError), const SizedBox(height: 12), // District Text("District"), const SizedBox(height: 6), DropdownButtonHideUnderline( child: Row( children: [ Expanded( child: DropdownButton2( focusNode: focusNodes[FN_DISTRICT], isExpanded: true, hint: const Text('Select District', style: TextStyle(fontSize: 14)), items: provider.districts .map((dist) => DropdownMenuItem( value: dist, child: Text(dist.district ?? '', style: const TextStyle(fontSize: 14), overflow: TextOverflow.ellipsis), )) .toList(), value: provider.districts.contains(provider.selectedDistricts) ? provider.selectedDistricts : null, onChanged: (Districts? value) { if (value != null) { provider.selectedDistricts = value; provider.getSubLocationAPI(context, provider.selectedDistrictId); } }, dropdownSearchData: DropdownSearchData( searchInnerWidgetHeight: 50, searchController: provider.districtSearchController, searchInnerWidget: Padding( padding: const EdgeInsets.all(8), child: TextFormField( controller: provider.districtSearchController, decoration: InputDecoration(isDense: true, contentPadding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8), hintText: 'Search Districts...', border: OutlineInputBorder(borderRadius: BorderRadius.circular(8))), ), ), searchMatchFn: (item, searchValue) { return item.value?.district?.toLowerCase().contains(searchValue.toLowerCase()) ?? false; }, ), onMenuStateChange: (isOpen) { if (!isOpen) provider.districtSearchController.clear(); }, buttonStyleData: ddtheme.buttonStyleData, iconStyleData: ddtheme.iconStyleData, menuItemStyleData: ddtheme.menuItemStyleData, dropdownStyleData: ddtheme.dropdownStyleData, ), ), ], ), ), errorWidget(context, provider.districtError), const SizedBox(height: 12), // Sub Locality Text("Sub Locality"), const SizedBox(height: 6), DropdownButtonHideUnderline( child: Row( children: [ Expanded( child: DropdownButton2( focusNode: focusNodes[FN_SUBLOC], isExpanded: true, hint: const Text('Select Sub Locality', style: TextStyle(fontSize: 14)), items: provider.subLocations .map((subloc) => DropdownMenuItem( value: subloc, child: Text(subloc.subLocality ?? '', style: const TextStyle(fontSize: 14), overflow: TextOverflow.ellipsis), )) .toList(), value: provider.subLocations.contains(provider.selectedSubLocations) ? provider.selectedSubLocations : null, onChanged: (SubLocations? value) { if (value != null) { provider.selectedSubLocations = value; } }, dropdownSearchData: DropdownSearchData( searchInnerWidgetHeight: 50, searchController: provider.subLocSearchController, searchInnerWidget: Padding( padding: const EdgeInsets.all(8), child: TextFormField( controller: provider.subLocSearchController, decoration: InputDecoration(isDense: true, contentPadding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8), hintText: 'Search Sub Locality...', border: OutlineInputBorder(borderRadius: BorderRadius.circular(8))), ), ), searchMatchFn: (item, searchValue) { return item.value?.subLocality?.toLowerCase().contains(searchValue.toLowerCase()) ?? false; }, ), onMenuStateChange: (isOpen) { if (!isOpen) provider.subLocSearchController.clear(); }, buttonStyleData: ddtheme.buttonStyleData, iconStyleData: ddtheme.iconStyleData, menuItemStyleData: ddtheme.menuItemStyleData, dropdownStyleData: ddtheme.dropdownStyleData, ), ), ], ), ), errorWidget(context, provider.localityError), const SizedBox(height: 12), // Address text field (same UI as your other address field) textControllerWidget( context, provider.addressController, "Address", "Enter Address", provider.updateAddress, TextInputType.text, false, null, focusNodes[FN_ADDRESS], focusNodes[FN_BANK_ACC], TextInputAction.next, ), errorWidget(context, provider.addressError), ], ), ), ], ), ), // ---------------- Step 3 ---------------- Step( title: const Text(''), label: const Text("Step 3", style: TextStyle(fontSize: 12)), content: Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10), decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(16)), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("Bank Details", style: TextStyle(color: AppColors.app_blue, fontSize: 16)), const SizedBox(height: 8), const SizedBox(height: 12), // Bank Account Number textControllerWidget( context, provider.bankAcNumberController, "Bank Account Number", "Enter Bank Account Number", (p0) { provider.updateNumber(p0); // IFSC required if account entered if (p0.trim().isNotEmpty && provider.bankIfscCotroller.text.trim().isEmpty) { provider.bankIFSCError = "IFSC is required when account number is entered"; provider.notifyListeners(); } else if (p0.trim().isNotEmpty && provider.bankIfscCotroller.text.trim().isNotEmpty) { // both present -> validate bank provider.checkAndApplyBank(context, p0.trim()); } else { // cleared -> reset bank response/errors if (p0.trim().isEmpty) { provider.bankResponse = null; provider.bankAcNumberError = null; provider.bankIFSCError = null; provider.notifyListeners(); } } }, TextInputType.number, false, FilteringTextInputFormatter.digitsOnly, focusNodes[FN_BANK_ACC], focusNodes[FN_IFSC], TextInputAction.next, ), errorWidget(context, provider.bankAcNumberError), // IFSC textControllerWidget( context, provider.bankIfscCotroller, "Bank IFSC", "Enter Bank IFSC", (p0) async { provider.updateIFSC(p0); if (p0.trim().isEmpty) { if (provider.bankAcNumberController.text.trim().isNotEmpty) { provider.bankIFSCError = "IFSC is required when account number is entered"; provider.notifyListeners(); } else { provider.bankIFSCError = null; provider.notifyListeners(); } return; } if (!_isIfscValidFormat(p0)) { provider.bankIFSCError = "Invalid IFSC format"; provider.notifyListeners(); return; } else { provider.bankIFSCError = null; provider.notifyListeners(); } // if both present -> validate if (provider.bankAcNumberController.text.trim().isNotEmpty) { await provider.checkAndApplyBank(context, provider.bankAcNumberController.text.trim()); } }, TextInputType.text, false, null, focusNodes[FN_IFSC], focusNodes[FN_BANK_NAME], TextInputAction.next, ), errorWidget(context, provider.bankIFSCError), const SizedBox(height: 8), // Bank Name (autofilled, readonly) Text("Bank Name"), const SizedBox(height: 6), TextFormField( controller: provider.bankNameController, focusNode: focusNodes[FN_BANK_NAME], readOnly: true, decoration: InputDecoration( hintText: "Bank Name", hintStyle: TextStyle( height: 1.2, backgroundColor: AppColors.text_field_color, color: AppColors.grey_semi, ), filled: true, fillColor: AppColors.text_field_color, contentPadding: const EdgeInsets.symmetric(horizontal: 14, vertical: 14), enabledBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(16), borderSide: BorderSide.none), ), ), errorWidget(context, provider.banknameError), const SizedBox(height: 8), // Branch (read-only) Text("Bank Branch"), const SizedBox(height: 6), TextFormField( controller: provider.branchNameController, focusNode: focusNodes[FN_BANK_BRANCH], readOnly: true, decoration: InputDecoration( hintText: "Branch ", hintStyle: TextStyle( height: 1.2, backgroundColor: AppColors.text_field_color, color: AppColors.grey_semi, ), filled: true, fillColor: AppColors.text_field_color, contentPadding: const EdgeInsets.symmetric(horizontal: 14, vertical: 14), enabledBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(16), borderSide: BorderSide.none), ), ), errorWidget(context, provider.bankBranchError), const SizedBox(height: 8), // Bank Holder (read-only) Text("Bank Holder Name"), const SizedBox(height: 6), TextFormField( controller: provider.bankHolderNameController, focusNode: focusNodes[FN_BANK_HOLDER], readOnly: true, decoration: InputDecoration( hintText: "Holder ", hintStyle: TextStyle( height: 1.2, backgroundColor: AppColors.text_field_color, color: AppColors.grey_semi, ), filled: true, fillColor: AppColors.text_field_color, contentPadding: const EdgeInsets.symmetric(horizontal: 14, vertical: 14), enabledBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(16), borderSide: BorderSide.none), ), ), errorWidget(context, provider.bankHolderNameError), const SizedBox(height: 8), // UPI (editable) textControllerWidget( context, provider.bankUpiController, "Bank UPI ID", "Enter Bank UPI ID", provider.updateUPI, TextInputType.text, false, null, focusNodes[FN_UPI], focusNodes[FN_CONTACT_MAIL], TextInputAction.next, ), errorWidget(context, provider.upiError), ], ), ), ), // ---------------- Step 4 ---------------- Step( title: const Text(''), label: const Text("Step 4", style: TextStyle(fontSize: 12)), content: Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10), decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(16)), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("Contact Details", style: TextStyle(color: AppColors.app_blue, fontSize: 16)), const SizedBox(height: 8), textControllerWidget( context, provider.contectPersonMailController, "Mail ID", "Enter Customer Mail ID", provider.updateMail, TextInputType.emailAddress, false, null, focusNodes[FN_CONTACT_MAIL], FocusNode(), TextInputAction.done, ), errorWidget(context, provider.mailError), ], ), ), ), ], controlsBuilder: (context, details) { return Column( children: [ if (_currentStep == 3) ...[ InkResponse( onTap: provider.isSubmitting ? null : () async { // trigger validations for gst/bank if present if (provider.gstNumberController.text.trim().isNotEmpty) { await _validateGstIfNeeded(provider); } if (provider.bankAcNumberController.text.trim().isNotEmpty && provider.bankIfscCotroller.text.trim().isNotEmpty) { await _validateBankIfNeeded(provider); } final ok = provider.validatereceiptForm(context); if (!ok) { final err = _firstErrorStep(provider); if (err != null) setState(() => _currentStep = err); provider.notifyListeners(); return; } // call update API provider.isLoading = true; final success = await provider.updateAccountDetailsAPIFunction( context, accId: widget.accountID, name: provider.nameController.text.trim(), mob1: provider.mobileController.text.trim(), email: provider.contectPersonMailController.text.trim(), address: provider.addressController.text.trim(), state: provider.selectedStateID, district: provider.selectedDistrictId, subLocality: provider.selectedSubLocID, bankName: provider.bankNameController.text.trim(), branchName: provider.branchNameController.text.trim(), bankIfscCode: provider.bankIfscCotroller.text.trim(), accHolderName: provider.bankHolderNameController.text.trim(), bankAccNumber: provider.bankAcNumberController.text.trim(), bankUpiId: provider.bankUpiController.text.trim(), ); provider.isLoading = false; if (success) { Navigator.pop(context, true); // or pass updated id as you wish } else { if (kDebugMode) debugPrint("update failed: ${provider.updateErrorMessage}"); } }, child: Container( height: 45, alignment: Alignment.center, margin: const EdgeInsets.symmetric(horizontal: 10, vertical: 10), padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), decoration: BoxDecoration(color: AppColors.app_blue, borderRadius: BorderRadius.circular(15)), child: provider.submitClickced ? CircularProgressIndicator.adaptive(valueColor: AlwaysStoppedAnimation(AppColors.white)) : Text("Submit", style: TextStyle(fontSize: 15, fontFamily: "JakartaMedium", color: Colors.white)), ), ), ] else ...[ InkResponse( onTap: () { final error = provider.gstNumberError ?? ""; // proceed to next step (uses Stepper.onStepContinue logic) if (error.isEmpty) { details.onStepContinue!(); } }, child: Container( height: 45, alignment: Alignment.center, margin: const EdgeInsets.symmetric(horizontal: 10, vertical: 10), padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), decoration: BoxDecoration(color: AppColors.app_blue, borderRadius: BorderRadius.circular(15)), child: Text("Proceed to Next Step", textAlign: TextAlign.start, style: TextStyle(fontSize: 15, fontFamily: "JakartaMedium", color: Colors.white)), ), ), ], if (_currentStep > 0) TextButton(onPressed: details.onStepCancel, child: Text('Back', style: TextStyle(color: AppColors.app_blue))) ], ); }, ), ), ); }); } }