Files
kmobile/lib/features/beneficiaries/screens/add_beneficiary_screen.dart

488 lines
20 KiB
Dart

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 '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 {
const AddBeneficiaryScreen({super.key});
@override
State<AddBeneficiaryScreen> createState() => _AddBeneficiaryScreen();
}
class _AddBeneficiaryScreen extends State<AddBeneficiaryScreen> {
final _formKey = GlobalKey<FormState>();
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();
bool _isLoading2 = false;
late String accountType;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
setState(() {
accountType = AppLocalizations.of(context).savings;
});
});
}
/*void _submitForm() {
if (_formKey.currentState!.validate()) {
// Handle successful submission
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
backgroundColor: Colors.grey[900],
behavior: SnackBarBehavior.floating,
margin: const EdgeInsets.all(12),
duration: const Duration(seconds: 5),
content: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
AppLocalizations.of(context).beneficiaryAdded,
style: TextStyle(color: Theme.of(context).dialogBackgroundColor),
),
),
TextButton(
onPressed: () {
// Navigate to Payment Screen or do something
},
style: TextButton.styleFrom(foregroundColor: Theme.of(context).primaryColorLight),
child: Text(AppLocalizations.of(context).payNow),
),
IconButton(
icon: Icon(Icons.close, color: Theme.of(context).scaffoldBackgroundColor),
onPressed: () {
ScaffoldMessenger.of(context).hideCurrentSnackBar();
},
),
],
),
),
);
}
}*/
ifsc? _ifscData;
bool _isLoading = false;
void _validateIFSC() async {
var beneficiaryService = getIt<BeneficiaryService>();
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(
const SnackBar(content: Text('Invalid IFSC code')),
);
bankNameController.clear();
branchNameController.clear();
} else {
print("Valid IFSC: ${result.bankName}, ${result.branchName}");
bankNameController.text = result.bankName;
branchNameController.text = result.branchName;
}
}
String _selectedAccountType = 'Savings'; // default value
void validateAndAddBeneficiary() async {
setState(() {
_isLoading = true;
});
final beneficiary = Beneficiary(
accountNo: accountNumberController.text.trim(),
accountType: _selectedAccountType,
name: nameController.text.trim(),
ifscCode: ifscController.text.trim(),
);
var service = getIt<BeneficiaryService>();
try {
await service.sendForValidation(beneficiary);
bool isFound = await service.checkIfFound(beneficiary.accountNo);
if (context.mounted) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => BeneficiaryResultPage(isSuccess: isFound),
),
);
}
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("Something went wrong during validation.")),
);
} finally {
if (mounted) {
setState(() {
_isLoading2 = false;
});
}
}
}
@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: OutlineInputBorder(
borderSide: BorderSide(color: Colors.black),
),
focusedBorder: 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: OutlineInputBorder(
borderSide: BorderSide(color: Colors.black),
),
focusedBorder: 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),
TextFormField(
controller: nameController,
decoration: InputDecoration(
labelText: AppLocalizations.of(context).name,
// prefixIcon: Icon(Icons.person),
border: OutlineInputBorder(),
isDense: true,
filled: true,
fillColor: Theme.of(context).dialogBackgroundColor,
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.black),
),
focusedBorder: 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),
// 🔹 IFSC Code Field
TextFormField(
controller: ifscController,
decoration: InputDecoration(
labelText: AppLocalizations.of(context).ifscCode,
border: OutlineInputBorder(),
isDense: true,
filled: true,
fillColor: Theme.of(context).scaffoldBackgroundColor,
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.black),
),
focusedBorder: 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: OutlineInputBorder(),
isDense: true,
filled: true,
fillColor: Theme.of(context).dialogBackgroundColor, // disabled color
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.black),
),
focusedBorder: 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: OutlineInputBorder(),
isDense: true,
filled: true,
fillColor: Theme.of(context).dialogBackgroundColor,
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.black),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.black,
width: 2,
),
),
),
),
const SizedBox(height: 24),
// 🔹 Account Type Dropdown
DropdownButtonFormField<String>(
value: accountType,
decoration: InputDecoration(
labelText: AppLocalizations.of(context).accountType,
border: OutlineInputBorder(),
isDense: true,
filled: true,
fillColor: Theme.of(context).scaffoldBackgroundColor,
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.black),
),
focusedBorder: 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: Icon(Icons.phone),
border: OutlineInputBorder(),
isDense: true,
filled: true,
fillColor: Theme.of(context).scaffoldBackgroundColor,
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.black),
),
focusedBorder: 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: _isLoading2 ? null :
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),
),
),
),
],
),
),
),
);
}
}