From dbc61abf001ca28e4ce898b7a3efbd060fc4083a Mon Sep 17 00:00:00 2001 From: Nilanjan Chakrabarti Date: Fri, 8 Aug 2025 23:37:16 +0530 Subject: [PATCH] Manage Beneficiary UI --- lib/features/auth/screens/welcome_screen.dart | 12 +- .../screens/add_beneficiary_screen.dart | 133 +++++++++++++----- .../screens/beneficiary_result_page.dart | 29 ++++ .../screens/manage_beneficiaries_screen.dart | 83 ----------- .../dashboard/screens/dashboard_screen.dart | 2 +- .../fund_transfer_beneficiary_screen.dart | 117 ++++++++++++--- lib/l10n/app_en.arb | 3 +- lib/l10n/app_hi.arb | 3 +- lib/l10n/app_localizations.dart | 6 + lib/l10n/app_localizations_en.dart | 3 + lib/l10n/app_localizations_hi.dart | 3 + 11 files changed, 249 insertions(+), 145 deletions(-) diff --git a/lib/features/auth/screens/welcome_screen.dart b/lib/features/auth/screens/welcome_screen.dart index 10cc05d..83341e7 100644 --- a/lib/features/auth/screens/welcome_screen.dart +++ b/lib/features/auth/screens/welcome_screen.dart @@ -17,13 +17,13 @@ class _WelcomeScreenState extends State { void initState() { super.initState(); - // Automatically go to login after 6 seconds - // Timer(const Duration(seconds: 6), () { + // Automatically go to logizn after 4 seconds + Timer(const Duration(seconds: 4), () { - - // } - // ); - widget.onContinue(); + widget.onContinue(); + } + ); + } @override diff --git a/lib/features/beneficiaries/screens/add_beneficiary_screen.dart b/lib/features/beneficiaries/screens/add_beneficiary_screen.dart index b9f22af..6c0e818 100644 --- a/lib/features/beneficiaries/screens/add_beneficiary_screen.dart +++ b/lib/features/beneficiaries/screens/add_beneficiary_screen.dart @@ -27,7 +27,10 @@ class _AddBeneficiaryScreen extends State { final TextEditingController ifscController = TextEditingController(); final TextEditingController phoneController = TextEditingController(); - + String? _beneficiaryName; + bool _isValidating = false; + bool _isBeneficiaryValidated = false; + String? _validationError; late String accountType; @@ -75,6 +78,30 @@ bool _isLoading = false; //for validateIFSC() } } + +Future _validateBeneficiary() async { + var beneficiaryService = getIt(); + setState(() { + _isValidating = true; + _validationError = null; + }); + try { + final name = await beneficiaryService + .validateBeneficiaryWithinBank(accountNumberController.text); + setState(() { + _beneficiaryName = name; + _isBeneficiaryValidated = true; + _isValidating = false; + }); + } catch (e) { + setState(() { + _validationError = "Account Number not from KCCB"; + _isValidating = false; + _isBeneficiaryValidated = false; + _beneficiaryName = null; + }); + } + } String _selectedAccountType = 'Savings'; // default value @@ -239,32 +266,6 @@ void validateAndAddBeneficiary() async { 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: 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), // ЁЯФ╣ IFSC Code Field TextFormField( @@ -347,10 +348,10 @@ void validateAndAddBeneficiary() async { isDense: true, filled: true, fillColor: Theme.of(context).dialogBackgroundColor, - enabledBorder: OutlineInputBorder( + enabledBorder: const OutlineInputBorder( borderSide: BorderSide(color: Colors.black), ), - focusedBorder: OutlineInputBorder( + focusedBorder: const OutlineInputBorder( borderSide: BorderSide( color: Colors.black, width: 2, @@ -359,20 +360,80 @@ void validateAndAddBeneficiary() async { ), ), + //validate Beneficiary Button + if (!_isBeneficiaryValidated) + Padding( + padding: const EdgeInsets.only(top: 12.0), + child: SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: _isValidating + ? null + : () { + if (accountNumberController.text.length == 11 && + 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) + const SizedBox(height: 24), + TextFormField( + controller: nameController, + enabled: false, + decoration: InputDecoration( + labelText: AppLocalizations.of(context).beneficiaryName, + // prefixIcon: Icon(Icons.person), + 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: OutlineInputBorder(), + border: const OutlineInputBorder(), isDense: true, filled: true, fillColor: Theme.of(context).scaffoldBackgroundColor, - enabledBorder: OutlineInputBorder( + enabledBorder: const OutlineInputBorder( borderSide: BorderSide(color: Colors.black), ), - focusedBorder: OutlineInputBorder( + focusedBorder: const OutlineInputBorder( borderSide: BorderSide( color: Colors.black, width: 2, @@ -404,15 +465,15 @@ void validateAndAddBeneficiary() async { keyboardType: TextInputType.phone, decoration: InputDecoration( labelText: AppLocalizations.of(context).phone, - prefixIcon: Icon(Icons.phone), - border: OutlineInputBorder(), + prefixIcon: const Icon(Icons.phone), + border: const OutlineInputBorder(), isDense: true, filled: true, fillColor: Theme.of(context).scaffoldBackgroundColor, - enabledBorder: OutlineInputBorder( + enabledBorder: const OutlineInputBorder( borderSide: BorderSide(color: Colors.black), ), - focusedBorder: OutlineInputBorder( + focusedBorder: const OutlineInputBorder( borderSide: BorderSide( color: Colors.black, width: 2, diff --git a/lib/features/beneficiaries/screens/beneficiary_result_page.dart b/lib/features/beneficiaries/screens/beneficiary_result_page.dart index cd7401d..5640f77 100644 --- a/lib/features/beneficiaries/screens/beneficiary_result_page.dart +++ b/lib/features/beneficiaries/screens/beneficiary_result_page.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:lottie/lottie.dart'; import 'package:confetti/confetti.dart'; import 'dart:math'; +import '../../../app.dart'; import '../../../l10n/app_localizations.dart'; class BeneficiaryResultPage extends StatefulWidget { @@ -65,6 +66,34 @@ class _BeneficiaryResultPageState extends State { ), ], ), +Positioned( + bottom: 20, // keep it slightly above the very bottom + left: 16, + right: 16, + child: SizedBox( + height: 56, // larger button height + child: ElevatedButton( + onPressed: () { + Navigator.pushReplacement( // ensures back goes to ScaffoldScreen + context, + MaterialPageRoute( + builder: (context) => const NavigationScaffold(), + ), + ); + }, + style: ElevatedButton.styleFrom( + shape: const StadiumBorder(), + padding: const EdgeInsets.symmetric(vertical: 12), + backgroundColor: Theme.of(context).primaryColorDark, + foregroundColor: Theme.of(context).scaffoldBackgroundColor, + ), + child: Text( + AppLocalizations.of(context).done, + style: const TextStyle(fontSize: 18), // slightly bigger text + ), + ), + ), +), if (widget.isSuccess) Align( alignment: Alignment.topCenter, diff --git a/lib/features/beneficiaries/screens/manage_beneficiaries_screen.dart b/lib/features/beneficiaries/screens/manage_beneficiaries_screen.dart index 1ce2b6d..d980e4b 100644 --- a/lib/features/beneficiaries/screens/manage_beneficiaries_screen.dart +++ b/lib/features/beneficiaries/screens/manage_beneficiaries_screen.dart @@ -15,89 +15,6 @@ class ManageBeneficiariesScreen extends StatefulWidget { _ManageBeneficiariesScreen(); } -/*class _ManageBeneficiariesScreen extends State { - final List> beneficiaries = [ - {'bank': 'State Bank Of India', 'name': 'Trina Bakshi'}, - {'bank': 'State Bank Of India', 'name': 'Sheetal Rao'}, - {'bank': 'Punjab National Bank', 'name': 'Manoj Kumar'}, - {'bank': 'State Bank Of India', 'name': 'Rohit Mehra'}, - ]; - - @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).beneficiaries, - 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: Padding( - padding: const EdgeInsets.all(8.0), - child: ListView.builder( - itemCount: beneficiaries.length, - itemBuilder: (context, index) { - final beneficiary = beneficiaries[index]; - return ListTile( - leading: CircleAvatar( - backgroundColor: Theme.of(context).primaryColor, - child: Text('A'), - ), - title: Text(beneficiary['name']!), - subtitle: Text(beneficiary['bank']!), - trailing: IconButton( - icon: const Icon(Symbols.delete_forever, color: Colors.red), - onPressed: () { - // Delete action - }, - ), - ); - }, - ), - ), - floatingActionButton: Padding( - padding: const EdgeInsets.only(bottom: 8.0), - child: FloatingActionButton( - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => const AddBeneficiaryScreen(), - ), - ); - }, - backgroundColor: Theme.of(context).scaffoldBackgroundColor, - foregroundColor: Theme.of(context).primaryColor, - elevation: 5, - child: const Icon(Icons.add), - ), - ), - ); - } -} -*/ class _ManageBeneficiariesScreen extends State { var service = getIt(); //final BeneficiaryService _service = BeneficiaryService(); diff --git a/lib/features/dashboard/screens/dashboard_screen.dart b/lib/features/dashboard/screens/dashboard_screen.dart index 8dc1b59..a489622 100644 --- a/lib/features/dashboard/screens/dashboard_screen.dart +++ b/lib/features/dashboard/screens/dashboard_screen.dart @@ -482,7 +482,7 @@ class _DashboardScreenState extends State { MaterialPageRoute( builder: (context) => const FundTransferBeneficiaryScreen())); - }, disable: false), + }, disable: true), _buildQuickLink(Symbols.server_person, AppLocalizations.of(context).accountInfo, () { Navigator.push( diff --git a/lib/features/fund_transfer/screens/fund_transfer_beneficiary_screen.dart b/lib/features/fund_transfer/screens/fund_transfer_beneficiary_screen.dart index 8f47d00..50406b4 100644 --- a/lib/features/fund_transfer/screens/fund_transfer_beneficiary_screen.dart +++ b/lib/features/fund_transfer/screens/fund_transfer_beneficiary_screen.dart @@ -1,6 +1,6 @@ -import 'package:flutter/material.dart'; +/*import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:kmobile/features/beneficiaries/screens/add_beneficiary_screen.dart'; +// import 'package:kmobile/features/beneficiaries/screens/add_beneficiary_screen.dart'; import 'package:kmobile/features/fund_transfer/screens/fund_transfer_screen.dart'; import 'package:material_symbols_icons/material_symbols_icons.dart'; import '../../../l10n/app_localizations.dart'; @@ -84,23 +84,106 @@ class _FundTransferBeneficiaryScreen }, ), ), - floatingActionButton: Padding( - padding: const EdgeInsets.only(bottom: 8.0), - child: FloatingActionButton( - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => const AddBeneficiaryScreen(), - ), - ); - }, - backgroundColor: Colors.grey[300], - foregroundColor: Theme.of(context).primaryColor, - elevation: 5, - child: const Icon(Icons.add), + ); + } +}*/ + +import 'package:flutter/material.dart'; +import 'package:kmobile/data/models/beneficiary.dart'; +//import 'package:kmobile/features/beneficiaries/screens/add_beneficiary_screen.dart'; +import '../../../l10n/app_localizations.dart'; +import '../../../di/injection.dart'; +import 'package:kmobile/api/services/beneficiary_service.dart'; +import 'package:shimmer/shimmer.dart'; + + +class FundTransferBeneficiaryScreen extends StatefulWidget { + const FundTransferBeneficiaryScreen({super.key}); + + @override + State createState() => + _ManageBeneficiariesScreen(); +} + +class _ManageBeneficiariesScreen extends State { + var service = getIt(); + //final BeneficiaryService _service = BeneficiaryService(); + bool _isLoading = true; + List _beneficiaries = []; + + @override + void initState() { + super.initState(); + _loadBeneficiaries(); + } + + Future _loadBeneficiaries() async { + final data = await service.fetchBeneficiaryList(); + setState(() { + _beneficiaries = data ; + _isLoading = false; + }); + } + + Widget _buildShimmerList() { + return ListView.builder( + itemCount: 6, + itemBuilder: (context, index) => Shimmer.fromColors( + baseColor: Colors.grey.shade300, + highlightColor: Colors.grey.shade100, + child: ListTile( + leading: CircleAvatar( + radius: 24, + backgroundColor: Colors.white, + ), + title: Container( + height: 16, + color: Colors.white, + margin: const EdgeInsets.symmetric(vertical: 4), + ), + subtitle: Container( + height: 14, + color: Colors.white, + margin: const EdgeInsets.symmetric(vertical: 4), + ), ), ), ); } + + Widget _buildBeneficiaryList() { + if (_beneficiaries.isEmpty) { + return Center(child: Text(AppLocalizations.of(context).noBeneficiaryFound)); + } + return ListView.builder( + itemCount: _beneficiaries.length, + itemBuilder: (context, index) { + final item = _beneficiaries[index]; + return ListTile( + leading: CircleAvatar( + radius: 24, + backgroundColor: Theme.of(context).primaryColor.withOpacity(0.2), + child: Text( + item.name.isNotEmpty + ? item.name[0].toUpperCase() + : '?', + style: const TextStyle(fontWeight: FontWeight.bold), + ), + ), + title: Text(item.name ?? 'Unknown'), + subtitle: Text(item.accountNo ?? 'No account number'), + ); + }, + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(AppLocalizations.of(context).beneficiaries), + ), + body: _isLoading ? _buildShimmerList() : _buildBeneficiaryList(), + ); + } } diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 64b08c6..42c8f17 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -228,6 +228,7 @@ "validIfsc": "Valid IFSC", "beneficiaryAddedSuccess": "Beneficiary Added Successfully", "beneficiaryAdditionFailed": "Beneficiary Addition Failed", - "noBeneficiaryFound": "No beneficiaries found" + "noBeneficiaryFound": "No beneficiaries found", + "beneficiaryName": "Beneficiary Name" } diff --git a/lib/l10n/app_hi.arb b/lib/l10n/app_hi.arb index aa5b049..10678e5 100644 --- a/lib/l10n/app_hi.arb +++ b/lib/l10n/app_hi.arb @@ -228,5 +228,6 @@ "validIfsc": "рдорд╛рдиреНрдп IFSC", "beneficiaryAddedSuccess": "рд▓рд╛рднрд╛рд░реНрдереА рд╕рдлрд▓рддрд╛рдкреВрд░реНрд╡рдХ рдЬреЛрдбрд╝рд╛ рдЧрдпрд╛", "beneficiaryAdditionFailed": "рд▓рд╛рднрд╛рд░реНрдереА рдЬреЛрдбрд╝рдиреЗ рдореЗрдВ рд╡рд┐рдлрд▓", - "noBeneficiaryFound": "рдХреЛрдИ рд▓рд╛рднрд╛рд░реНрдереА рдирд╣реАрдВ рдорд┐рд▓рд╛" + "noBeneficiaryFound": "рдХреЛрдИ рд▓рд╛рднрд╛рд░реНрдереА рдирд╣реАрдВ рдорд┐рд▓рд╛", + "beneficiaryName": "рд▓рд╛рднрд╛рд░реНрдереА рдирд╛рдо" } diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 8593d7b..076b262 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -1396,6 +1396,12 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'No beneficiaries found'** String get noBeneficiaryFound; + + /// No description provided for @beneficiaryName. + /// + /// In en, this message translates to: + /// **'Beneficiary Name'** + String get beneficiaryName; } class _AppLocalizationsDelegate extends LocalizationsDelegate { diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 9495354..b268fbe 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -658,4 +658,7 @@ class AppLocalizationsEn extends AppLocalizations { @override String get noBeneficiaryFound => 'No beneficiaries found'; + + @override + String get beneficiaryName => 'Beneficiary Name'; } diff --git a/lib/l10n/app_localizations_hi.dart b/lib/l10n/app_localizations_hi.dart index 2929a4a..a19fc95 100644 --- a/lib/l10n/app_localizations_hi.dart +++ b/lib/l10n/app_localizations_hi.dart @@ -658,4 +658,7 @@ class AppLocalizationsHi extends AppLocalizations { @override String get noBeneficiaryFound => 'рдХреЛрдИ рд▓рд╛рднрд╛рд░реНрдереА рдирд╣реАрдВ рдорд┐рд▓рд╛'; + + @override + String get beneficiaryName => 'рд▓рд╛рднрд╛рд░реНрдереА рдирд╛рдо'; }