import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:kmobile/api/services/beneficiary_service.dart'; import 'package:kmobile/data/models/ifsc.dart'; import 'package:kmobile/data/models/beneficiary.dart'; import 'package:kmobile/data/models/user.dart'; import 'beneficiary_result_page.dart'; import 'package:material_symbols_icons/material_symbols_icons.dart'; import '../../../di/injection.dart'; import '../../../l10n/app_localizations.dart'; class AddBeneficiaryScreen extends StatefulWidget { final List? users; final int? selectedIndex; const AddBeneficiaryScreen({ super.key, this.users, this.selectedIndex, }); @override State createState() => _AddBeneficiaryScreen(); } class _AddBeneficiaryScreen extends State { final _formKey = GlobalKey(); late User selectedUser = (widget.users ?? [])[widget.selectedIndex!]; final TextEditingController accountNumberController = TextEditingController(); final TextEditingController confirmAccountNumberController = TextEditingController(); final TextEditingController nameController = TextEditingController(); final TextEditingController bankNameController = TextEditingController(); final TextEditingController branchNameController = TextEditingController(); final TextEditingController ifscController = TextEditingController(); final TextEditingController phoneController = TextEditingController(); String? _beneficiaryName; bool _isValidating = false; bool _isBeneficiaryValidated = false; String? _validationError; late String accountType; @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { setState(() { accountType = AppLocalizations.of(context).savings; }); }); } ifsc? _ifscData; bool _isLoading = false; //for validateIFSC() void _validateIFSC() async { var beneficiaryService = getIt(); final ifsc = ifscController.text.trim().toUpperCase(); if (ifsc.isEmpty) return; setState(() { _isLoading = true; _ifscData = null; }); final result = await beneficiaryService.validateIFSC(ifsc); setState(() { _isLoading = false; _ifscData = result; }); if (result == null) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(AppLocalizations.of(context).invalidIfsc)), ); bankNameController.clear(); branchNameController.clear(); } else { print("${AppLocalizations.of(context).validIfsc}: ${result.bankName}, ${result.branchName}"); bankNameController.text = result.bankName; branchNameController.text = result.branchName; } } Future _validateBeneficiary() async { // start spinner / disable button setState(() { _isValidating = true; _validationError = null; _isBeneficiaryValidated = false; nameController.text = ''; // clear previous name }); final String accountNo = accountNumberController.text.trim(); final String ifsc = ifscController.text.trim(); final String remitter = selectedUser.name ?? ''; final service = getIt(); try { // Step 1: call validate API -> get refNo final String? refNo = await service.validateBeneficiary( accountNo: accountNo, ifscCode: ifsc, remitterName: remitter, ); if (refNo == null || refNo.isEmpty) { setState(() { _validationError = 'Validation request failed. Please check details.'; _isBeneficiaryValidated = false; }); return; } // Step 2: poll checkValidationStatus for up to 30 seconds const int timeoutSeconds = 30; const int intervalSeconds = 2; int elapsed = 0; String? foundName; while (elapsed < timeoutSeconds) { final String? name = await service.checkValidationStatus(refNo); if (name != null && name.trim().isNotEmpty) { foundName = name.trim(); break; } await Future.delayed(const Duration(seconds: intervalSeconds)); elapsed += intervalSeconds; } if (foundName != null) { setState(() { nameController.text = foundName!; _isBeneficiaryValidated = true; _validationError = null; }); } else { setState(() { _validationError = 'Beneficiary not found within timeout.'; _isBeneficiaryValidated = false; }); } } catch (e, st) { // handle unexpected errors // print or log if you want debugPrint('Error validating beneficiary: $e\n$st'); setState(() { _validationError = 'Something went wrong. Please try again.'; _isBeneficiaryValidated = false; }); } finally { if (mounted) { setState(() { _isValidating = false; }); } } } String _selectedAccountType = 'Savings'; // default value void validateAndAddBeneficiary() async { // Show spinner and disable UI showDialog( context: context, barrierDismissible: false, // Prevent dismiss on tap outside builder: (BuildContext context) { return WillPopScope( onWillPop: () async => false, // Disable back button child: const Center( child: CircularProgressIndicator(), ), ); }, ); final beneficiary = Beneficiary( accountNo: accountNumberController.text.trim(), accountType: _selectedAccountType, name: nameController.text.trim(), ifscCode: ifscController.text.trim(), ); var service = getIt(); try { await service.sendForValidation(beneficiary); bool isFound = await service.checkIfFound(beneficiary.accountNo); if (context.mounted) { Navigator.pop(context); // Close the spinner Navigator.push( context, MaterialPageRoute( builder: (context) => BeneficiaryResultPage(isSuccess: isFound), ), ); } } catch (e) { Navigator.pop(context); // Close the spinner ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(AppLocalizations.of(context).somethingWentWrong)), ); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( leading: IconButton( icon: const Icon(Symbols.arrow_back_ios_new), onPressed: () { Navigator.pop(context); }, ), title: Text( AppLocalizations.of(context).addBeneficiary, style: TextStyle(color: Colors.black, fontWeight: FontWeight.w500), ), centerTitle: false, actions: [ Padding( padding: const EdgeInsets.only(right: 10.0), child: CircleAvatar( backgroundColor: Colors.grey[200], radius: 20, child: SvgPicture.asset( 'assets/images/avatar_male.svg', width: 40, height: 40, fit: BoxFit.cover, ), ), ), ], ), body: SafeArea( child: Form( key: _formKey, child: Column( children: [ Expanded( child: SingleChildScrollView( physics: const AlwaysScrollableScrollPhysics(), child: Padding( padding: const EdgeInsets.all(10.0), child: Column( children: [ TextFormField( controller: accountNumberController, decoration: InputDecoration( labelText: AppLocalizations.of( context, ).accountNumber, // prefixIcon: Icon(Icons.person), border: OutlineInputBorder(), isDense: true, filled: true, fillColor: Theme.of(context).scaffoldBackgroundColor, enabledBorder: const OutlineInputBorder( borderSide: BorderSide(color: Colors.black), ), focusedBorder: const OutlineInputBorder( borderSide: BorderSide( color: Colors.black, width: 2, ), ), ), obscureText: true, keyboardType: TextInputType.number, textInputAction: TextInputAction.next, validator: (value) { if (value == null || value.length < 10) { return AppLocalizations.of( context, ).enterValidAccountNumber; } return null; }, ), const SizedBox(height: 24), // Confirm Account Number TextFormField( controller: confirmAccountNumberController, decoration: InputDecoration( labelText: AppLocalizations.of( context, ).confirmAccountNumber, // prefixIcon: Icon(Icons.person), border: OutlineInputBorder(), isDense: true, filled: true, fillColor: Theme.of(context).scaffoldBackgroundColor, enabledBorder: const OutlineInputBorder( borderSide: BorderSide(color: Colors.black), ), focusedBorder: const OutlineInputBorder( borderSide: BorderSide( color: Colors.black, width: 2, ), ), ), keyboardType: TextInputType.number, textInputAction: TextInputAction.next, validator: (value) { if (value == null || value.isEmpty) { return AppLocalizations.of( context, ).reenterAccountNumber; } if (value != accountNumberController.text) { return AppLocalizations.of( context, ).accountMismatch; } return null; }, ), const SizedBox(height: 24), // 🔹 IFSC Code Field TextFormField( controller: ifscController, decoration: InputDecoration( labelText: AppLocalizations.of(context).ifscCode, border: const OutlineInputBorder(), isDense: true, filled: true, fillColor: Theme.of(context).scaffoldBackgroundColor, enabledBorder: const OutlineInputBorder( borderSide: BorderSide(color: Colors.black), ), focusedBorder: const OutlineInputBorder( borderSide: BorderSide( color: Colors.black, width: 2, ), ), ), textCapitalization: TextCapitalization.characters, textInputAction: TextInputAction.next, onFieldSubmitted: (_) { _validateIFSC(); }, onChanged: (value) { final trimmed = value.trim().toUpperCase(); if (trimmed.length < 11) { // clear bank/branch if backspace or changed bankNameController.clear(); branchNameController.clear(); } }, validator: (value) { final pattern = RegExp(r'^[A-Z]{4}0[A-Z0-9]{6}$'); if (value == null || value.trim().isEmpty) { return AppLocalizations.of(context).enterIfsc; } else if (!pattern.hasMatch( value.trim().toUpperCase(), )) { return AppLocalizations.of( context, ).invalidIfscFormat; } return null; }, ), const SizedBox(height: 24), // 🔹 Bank Name (Disabled) TextFormField( controller: bankNameController, enabled: false, // changed from readOnly to disabled decoration: InputDecoration( labelText: AppLocalizations.of(context).bankName, border: const OutlineInputBorder(), isDense: true, filled: true, fillColor: Theme.of(context).dialogBackgroundColor, // disabled color enabledBorder: const OutlineInputBorder( borderSide: BorderSide(color: Colors.black), ), focusedBorder: const OutlineInputBorder( borderSide: BorderSide( color: Colors.black, width: 2, ), ), ), ), const SizedBox(height: 24), // 🔹 Branch Name (Disabled) TextFormField( controller: branchNameController, enabled: false, // changed from readOnly to disabled decoration: InputDecoration( labelText: AppLocalizations.of(context).branchName, border: const OutlineInputBorder(), isDense: true, filled: true, fillColor: Theme.of(context).dialogBackgroundColor, enabledBorder: const OutlineInputBorder( borderSide: BorderSide(color: Colors.black), ), focusedBorder: const OutlineInputBorder( borderSide: BorderSide( color: Colors.black, width: 2, ), ), ), ), //Validate Beneficiary Name if (!_isBeneficiaryValidated) Padding( padding: const EdgeInsets.only(top: 12.0), child: SizedBox( width: double.infinity, child: ElevatedButton( onPressed: _isValidating ? null : () { if ( confirmAccountNumberController.text == accountNumberController.text) { _validateBeneficiary(); } else { setState(() { _validationError = 'Please enter a valid and matching account number.'; }); } }, child: _isValidating ? const SizedBox( width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2), ) : const Text('Validate Beneficiary'), ), ), ), //Beneficiary Name (Disabled) TextFormField( controller: nameController, enabled: false, decoration: InputDecoration( labelText: AppLocalizations.of(context).beneficiaryName, border: const OutlineInputBorder(), isDense: true, filled: true, fillColor: Theme.of(context).dialogBackgroundColor, enabledBorder: const OutlineInputBorder( borderSide: BorderSide(color: Colors.black), ), focusedBorder: const OutlineInputBorder( borderSide: BorderSide(color: Colors.black, width: 2), ), ), textInputAction: TextInputAction.next, validator: (value) => value == null || value.isEmpty ? AppLocalizations.of(context).nameRequired : null, ), const SizedBox(height: 24), // 🔹 Account Type Dropdown DropdownButtonFormField( value: accountType, decoration: InputDecoration( labelText: AppLocalizations.of(context).accountType, border: const OutlineInputBorder(), isDense: true, filled: true, fillColor: Theme.of(context).scaffoldBackgroundColor, enabledBorder: const OutlineInputBorder( borderSide: BorderSide(color: Colors.black), ), focusedBorder: const OutlineInputBorder( borderSide: BorderSide( color: Colors.black, width: 2, ), ), ), items: [ AppLocalizations.of(context).savings, AppLocalizations.of(context).current, ] .map( (type) => DropdownMenuItem( value: type, child: Text(type), ), ) .toList(), onChanged: (value) { setState(() { accountType = value!; }); }, ), const SizedBox(height: 24), TextFormField( controller: phoneController, keyboardType: TextInputType.phone, decoration: InputDecoration( labelText: AppLocalizations.of(context).phone, prefixIcon: const Icon(Icons.phone), border: const OutlineInputBorder(), isDense: true, filled: true, fillColor: Theme.of(context).scaffoldBackgroundColor, enabledBorder: const OutlineInputBorder( borderSide: BorderSide(color: Colors.black), ), focusedBorder: const OutlineInputBorder( borderSide: BorderSide( color: Colors.black, width: 2, ), ), ), textInputAction: TextInputAction.done, validator: (value) => value == null || value.length != 10 ? AppLocalizations.of(context).enterValidPhone : null, ), const SizedBox(height: 35), ], ), ), ), ), Padding( padding: const EdgeInsets.all(16.0), child: SizedBox( width: 250, child: ElevatedButton( onPressed: validateAndAddBeneficiary, style: ElevatedButton.styleFrom( shape: const StadiumBorder(), padding: const EdgeInsets.symmetric(vertical: 16), backgroundColor: Theme.of(context).primaryColorDark, foregroundColor: Theme.of(context).scaffoldBackgroundColor, ), child: Text(AppLocalizations.of(context).validateAndAdd), ), ), ), ], ), ), ), ); } }