Manage Beneficiary Services 2

This commit is contained in:
2025-08-10 11:18:00 +05:30
parent 83609fb778
commit 3024ddef15
2 changed files with 142 additions and 81 deletions

View File

@@ -1,17 +1,23 @@
// ignore_for_file: prefer_final_fields, unused_field
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:kmobile/api/services/beneficiary_service.dart'; import 'package:kmobile/api/services/beneficiary_service.dart';
import 'package:kmobile/data/models/ifsc.dart'; import 'package:kmobile/data/models/ifsc.dart';
import 'package:kmobile/data/models/beneficiary.dart'; import 'package:kmobile/data/models/beneficiary.dart';
import 'package:kmobile/data/models/user.dart';
import 'beneficiary_result_page.dart'; import 'beneficiary_result_page.dart';
import 'package:material_symbols_icons/material_symbols_icons.dart'; import 'package:material_symbols_icons/material_symbols_icons.dart';
import '../../../di/injection.dart'; import '../../../di/injection.dart';
import '../../../l10n/app_localizations.dart'; import '../../../l10n/app_localizations.dart';
class AddBeneficiaryScreen extends StatefulWidget { class AddBeneficiaryScreen extends StatefulWidget {
const AddBeneficiaryScreen({super.key}); final List<User>? users;
final int? selectedIndex;
const AddBeneficiaryScreen({
super.key,
this.users,
this.selectedIndex,
});
@override @override
State<AddBeneficiaryScreen> createState() => _AddBeneficiaryScreen(); State<AddBeneficiaryScreen> createState() => _AddBeneficiaryScreen();
@@ -20,6 +26,7 @@ class AddBeneficiaryScreen extends StatefulWidget {
class _AddBeneficiaryScreen extends State<AddBeneficiaryScreen> { class _AddBeneficiaryScreen extends State<AddBeneficiaryScreen> {
final _formKey = GlobalKey<FormState>(); final _formKey = GlobalKey<FormState>();
late User selectedUser = (widget.users ?? [])[widget.selectedIndex!];
final TextEditingController accountNumberController = TextEditingController(); final TextEditingController accountNumberController = TextEditingController();
final TextEditingController confirmAccountNumberController = final TextEditingController confirmAccountNumberController =
TextEditingController(); TextEditingController();
@@ -82,28 +89,83 @@ bool _isLoading = false; //for validateIFSC()
Future<void> _validateBeneficiary() async { Future<void> _validateBeneficiary() async {
var beneficiaryService = getIt<BeneficiaryService>(); // start spinner / disable button
setState(() { setState(() {
_isValidating = true; _isValidating = true;
_validationError = null; _validationError = null;
}); _isBeneficiaryValidated = false;
try { nameController.text = ''; // clear previous name
final name = await beneficiaryService });
.validateBeneficiaryWithinBank(accountNumberController.text);
final String accountNo = accountNumberController.text.trim();
final String ifsc = ifscController.text.trim();
final String remitter = selectedUser.name ?? '';
final service = getIt<BeneficiaryService>();
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(() { setState(() {
_beneficiaryName = name; _validationError = 'Validation request failed. Please check details.';
_isBeneficiaryValidated = true;
_isValidating = false;
});
} catch (e) {
setState(() {
_validationError = "Account Number not from KCCB";
_isValidating = false;
_isBeneficiaryValidated = false; _isBeneficiaryValidated = false;
_beneficiaryName = null; });
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 String _selectedAccountType = 'Savings'; // default value
@@ -361,67 +423,59 @@ void validateAndAddBeneficiary() async {
), ),
), ),
), ),
//Validate Beneficiary Name
//validate Beneficiary Button if (!_isBeneficiaryValidated)
if (!_isBeneficiaryValidated) Padding(
Padding( padding: const EdgeInsets.only(top: 12.0),
padding: const EdgeInsets.only(top: 12.0), child: SizedBox(
child: SizedBox( width: double.infinity,
width: double.infinity, child: ElevatedButton(
child: ElevatedButton( onPressed: _isValidating
onPressed: _isValidating ? null
? null : () {
: () { if (
if (accountNumberController.text.length == 11 && confirmAccountNumberController.text ==
confirmAccountNumberController.text == accountNumberController.text) {
accountNumberController.text) { _validateBeneficiary();
_validateBeneficiary(); } else {
} else { setState(() {
setState(() { _validationError =
_validationError = 'Please enter a valid and matching account number.';
'Please enter a valid and matching account number.'; });
}); }
} },
}, child: _isValidating
child: _isValidating ? const SizedBox(
? const SizedBox( width: 20,
width: 20, height: 20,
height: 20, child: CircularProgressIndicator(strokeWidth: 2),
child: CircularProgressIndicator(strokeWidth: 2), )
: const Text('Validate Beneficiary'),
) ),
: const Text('Validate Beneficiary'), ),
), ),
),
),
//Beneficiary Name (Disabled) //Beneficiary Name (Disabled)
const SizedBox(height: 24),
TextFormField( TextFormField(
controller: nameController, controller: nameController,
enabled: false, enabled: false,
decoration: InputDecoration( decoration: InputDecoration(
labelText: AppLocalizations.of(context).beneficiaryName, labelText: AppLocalizations.of(context).beneficiaryName,
// prefixIcon: Icon(Icons.person), border: const OutlineInputBorder(),
border: const OutlineInputBorder(), isDense: true,
isDense: true, filled: true,
filled: true, fillColor: Theme.of(context).dialogBackgroundColor,
fillColor: Theme.of(context).dialogBackgroundColor, enabledBorder: const OutlineInputBorder(
enabledBorder: const OutlineInputBorder( borderSide: BorderSide(color: Colors.black),
borderSide: BorderSide(color: Colors.black), ),
), focusedBorder: const OutlineInputBorder(
focusedBorder: const OutlineInputBorder( borderSide: BorderSide(color: Colors.black, width: 2),
borderSide: BorderSide( ),
color: Colors.black, ),
width: 2, textInputAction: TextInputAction.next,
), validator: (value) => value == null || value.isEmpty
), ? AppLocalizations.of(context).nameRequired
), : null,
textInputAction: TextInputAction.next, ),
validator: (value) => value == null || value.isEmpty
? AppLocalizations.of(context).nameRequired
: null,
),
const SizedBox(height: 24), const SizedBox(height: 24),
// 🔹 Account Type Dropdown // 🔹 Account Type Dropdown
DropdownButtonFormField<String>( DropdownButtonFormField<String>(

View File

@@ -1,13 +1,17 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:kmobile/data/models/beneficiary.dart'; import 'package:kmobile/data/models/beneficiary.dart';
import 'package:kmobile/features/beneficiaries/screens/add_beneficiary_screen.dart'; import 'package:kmobile/features/beneficiaries/screens/add_beneficiary_screen.dart';
import '../../../data/models/user.dart';
import '../../../l10n/app_localizations.dart'; import '../../../l10n/app_localizations.dart';
import '../../../di/injection.dart'; import '../../../di/injection.dart';
import 'package:kmobile/api/services/beneficiary_service.dart'; import 'package:kmobile/api/services/beneficiary_service.dart';
import 'package:shimmer/shimmer.dart'; import 'package:shimmer/shimmer.dart';
//import 'package:kmobile/data/models/user.dart';
class ManageBeneficiariesScreen extends StatefulWidget { class ManageBeneficiariesScreen extends StatefulWidget {
// final List<User> users;
// final int selectedIndex;
const ManageBeneficiariesScreen({super.key}); const ManageBeneficiariesScreen({super.key});
@override @override
@@ -17,9 +21,12 @@ class ManageBeneficiariesScreen extends StatefulWidget {
class _ManageBeneficiariesScreen extends State<ManageBeneficiariesScreen> { class _ManageBeneficiariesScreen extends State<ManageBeneficiariesScreen> {
var service = getIt<BeneficiaryService>(); var service = getIt<BeneficiaryService>();
// late User selectedUser = widget.users[widget.selectedIndex];
//final BeneficiaryService _service = BeneficiaryService(); //final BeneficiaryService _service = BeneficiaryService();
bool _isLoading = true; bool _isLoading = true;
int selectedAccountIndex = 0;
List<Beneficiary> _beneficiaries = []; List<Beneficiary> _beneficiaries = [];
@override @override
void initState() { void initState() {
@@ -101,7 +108,7 @@ class _ManageBeneficiariesScreen extends State<ManageBeneficiariesScreen> {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => const AddBeneficiaryScreen(), builder: (context) => AddBeneficiaryScreen(),
), ),
); );
}, },