Self-Transfer #1

This commit is contained in:
2025-11-04 14:53:14 +05:30
parent 8f8fdb70e6
commit 5ac977e903
4 changed files with 476 additions and 97 deletions

View File

@@ -138,7 +138,7 @@ class _DashboardScreenState extends State<DashboardScreen>
// Convert to title case // Convert to title case
switch (accountType.toLowerCase()) { switch (accountType.toLowerCase()) {
case 'sa': case 'sa':
return AppLocalizations.of(context).savingsAccount; return AppLocalizations.of(context).savingsAccount;
case 'sb': case 'sb':
return AppLocalizations.of(context).savingsAccount; return AppLocalizations.of(context).savingsAccount;
case 'ln': case 'ln':
@@ -147,6 +147,8 @@ class _DashboardScreenState extends State<DashboardScreen>
return AppLocalizations.of(context).termDeposit; return AppLocalizations.of(context).termDeposit;
case 'rd': case 'rd':
return AppLocalizations.of(context).recurringDeposit; return AppLocalizations.of(context).recurringDeposit;
case 'ca':
return "Current Account";
default: default:
return AppLocalizations.of(context).unknownAccount; return AppLocalizations.of(context).unknownAccount;
} }
@@ -495,18 +497,20 @@ class _DashboardScreenState extends State<DashboardScreen>
}, },
), ),
_buildQuickLink(Symbols.send_money, _buildQuickLink(Symbols.send_money,
AppLocalizations.of(context).fundTransfer, () { AppLocalizations.of(context).fundTransfer, () {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => FundTransferScreen( builder: (context) => FundTransferScreen(
creditAccountNo: creditAccountNo:
users[selectedAccountIndex] users[selectedAccountIndex]
.accountNo!, .accountNo!,
remitterName: remitterName:
users[selectedAccountIndex] users[selectedAccountIndex]
.name!))); .name!,
}, disable: false), // Pass the full list of accounts
accounts: users)));
}, disable: false),
_buildQuickLink( _buildQuickLink(
Symbols.server_person, Symbols.server_person,
AppLocalizations.of(context).accountInfo, AppLocalizations.of(context).accountInfo,

View File

@@ -1,90 +1,126 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:kmobile/features/fund_transfer/screens/fund_transfer_beneficiary_screen.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:material_symbols_icons/symbols.dart'; import 'package:kmobile/data/models/user.dart';
import '../../../l10n/app_localizations.dart'; import 'package:kmobile/features/auth/controllers/auth_cubit.dart';
import 'package:kmobile/features/auth/controllers/auth_state.dart';
import 'package:kmobile/features/fund_transfer/screens/fund_transfer_beneficiary_screen.dart';
import 'package:kmobile/features/fund_transfer/screens/fund_transfer_self_accounts_screen.dart';
import 'package:material_symbols_icons/symbols.dart';
import '../../../l10n/app_localizations.dart'; // Keep localizations
class FundTransferScreen extends StatelessWidget { class FundTransferScreen extends StatelessWidget {
final String creditAccountNo; final String creditAccountNo;
final String remitterName; final String remitterName;
final List<User> accounts; // Continue to accept the list of accounts
const FundTransferScreen({ const FundTransferScreen({
super.key, super.key,
required this.creditAccountNo, required this.creditAccountNo,
required this.remitterName, required this.remitterName,
}); required this.accounts, // It is passed from the dashboard
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text(AppLocalizations.of(context) // Restore localization for the title
.fundTransfer title: Text(AppLocalizations.of(context)
.replaceFirst(RegExp('\n'), '')), .fundTransfer
), .replaceFirst(RegExp('\n'), '')),
body: ListView( ),
children: [ // Wrap with BlocBuilder to check the authentication state
FundTransferManagementTile( body: BlocBuilder<AuthCubit, AuthState>(
icon: Symbols.input_circle, builder: (context, state) {
label: AppLocalizations.of(context).ownBank, return ListView(
onTap: () { children: [
Navigator.push( FundTransferManagementTile(
context, icon: Symbols.person,
MaterialPageRoute( // Restore localization for the label
builder: (context) => FundTransferBeneficiaryScreen( label: "Self Pay",
creditAccountNo: creditAccountNo, onTap: () {
remitterName: remitterName, // The accounts list is passed directly from the constructor
isOwnBank: true, Navigator.push(
), context,
), MaterialPageRoute(
); builder: (context) => FundTransferSelfAccountsScreen(
}, debitAccountNo: creditAccountNo,
), remitterName: remitterName,
const Divider(height: 1), accounts: accounts,
FundTransferManagementTile( ),
icon: Symbols.output_circle, ),
label: AppLocalizations.of(context).outsideBank, );
onTap: () { },
Navigator.push( // Disable the tile if the state is not Authenticated
context, disable: state is! Authenticated,
MaterialPageRoute( ),
builder: (context) => FundTransferBeneficiaryScreen( const Divider(height: 1),
creditAccountNo: creditAccountNo, FundTransferManagementTile(
remitterName: remitterName, icon: Symbols.input_circle,
isOwnBank: false, // Restore localization for the label
), label: AppLocalizations.of(context).ownBank,
), onTap: () {
); Navigator.push(
}, context,
), MaterialPageRoute(
const Divider(height: 1), builder: (context) => FundTransferBeneficiaryScreen(
], creditAccountNo: creditAccountNo,
), remitterName: remitterName,
); isOwnBank: true,
} ),
} ),
);
},
),
const Divider(height: 1),
FundTransferManagementTile(
icon: Symbols.output_circle,
// Restore localization for the label
label: AppLocalizations.of(context).outsideBank,
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => FundTransferBeneficiaryScreen(
creditAccountNo: creditAccountNo,
remitterName: remitterName,
isOwnBank: false,
),
),
);
},
),
const Divider(height: 1),
],
);
},
),
);
}
}
class FundTransferManagementTile extends StatelessWidget { class FundTransferManagementTile extends StatelessWidget {
final IconData icon; final IconData icon;
final String label; final String label;
final VoidCallback onTap; final VoidCallback onTap;
final bool disable; final bool disable;
const FundTransferManagementTile({ const FundTransferManagementTile({
super.key, super.key,
required this.icon, required this.icon,
required this.label, required this.label,
required this.onTap, required this.onTap,
this.disable = false, this.disable = false,
}); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ListTile( return ListTile(
leading: Icon(icon), leading: Icon(icon),
title: Text(label), title: Text(label),
trailing: const Icon(Symbols.arrow_right, size: 20), trailing: const Icon(Symbols.arrow_right, size: 20),
onTap: onTap, onTap: onTap,
enabled: !disable, enabled: !disable,
); );
} }
} }

View File

@@ -0,0 +1,94 @@
import 'package:flutter/material.dart';
import 'package:kmobile/data/models/user.dart';
import 'package:kmobile/features/fund_transfer/screens/fund_transfer_self_amount_screen.dart';
import 'package:kmobile/widgets/bank_logos.dart';
class FundTransferSelfAccountsScreen extends StatelessWidget {
final String debitAccountNo;
final String remitterName;
final List<User> accounts;
const FundTransferSelfAccountsScreen({
super.key,
required this.debitAccountNo,
required this.remitterName,
required this.accounts,
});
// Helper function to get the full account type name from the short code
String _getFullAccountType(String? accountType) {
if (accountType == null || accountType.isEmpty) return 'N/A';
switch (accountType.toLowerCase()) {
case 'sa':
case 'sb':
return "Savings Account";
case 'ln':
return "Loan Account";
case 'td':
return "Term Deposit";
case 'rd':
return "Recurring Deposit";
case 'ca':
return "Current Account";
default:
return "Unknown Account";
}
}
@override
Widget build(BuildContext context) {
// Filter out the account from which the transfer is being made
final filteredAccounts =
accounts.where((acc) => acc.accountNo != debitAccountNo).toList();
return Scaffold(
appBar: AppBar(
title: const Text("Select Account"),
),
body: filteredAccounts.isEmpty
? const Center(
child: Text("No other accounts found"),
)
: ListView.builder(
itemCount: filteredAccounts.length,
itemBuilder: (context, index) {
final account = filteredAccounts[index];
return ListTile(
leading: CircleAvatar(
radius: 24,
backgroundColor: Colors.transparent,
child:
getBankLogo('Kangra Central Co-operative Bank', context),
),
title: Text(account.name ?? 'N/A'),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(account.accountNo ?? 'N/A'),
Text(
_getFullAccountType(account.accountType),
style:
TextStyle(fontSize: 12, color: Colors.grey[600]),
),
],
),
onTap: () {
// Navigate to the amount screen, passing the selected User object directly.
// No Beneficiary object is created.
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => FundTransferSelfAmountScreen(
debitAccountNo: debitAccountNo,
creditAccount: account, // Pass the User object
remitterName: remitterName,
),
),
);
},
);
},
),
);
}
}

View File

@@ -0,0 +1,245 @@
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:kmobile/api/services/limit_service.dart';
import 'package:kmobile/api/services/payment_service.dart';
import 'package:kmobile/data/models/transfer.dart';
import 'package:kmobile/data/models/user.dart';
import 'package:kmobile/di/injection.dart';
import 'package:kmobile/features/fund_transfer/screens/payment_animation.dart';
import 'package:kmobile/features/fund_transfer/screens/transaction_pin_screen.dart';
import 'package:kmobile/widgets/bank_logos.dart';
class FundTransferSelfAmountScreen extends StatefulWidget {
final String debitAccountNo;
final User creditAccount;
final String remitterName;
const FundTransferSelfAmountScreen({
super.key,
required this.debitAccountNo,
required this.creditAccount,
required this.remitterName,
});
@override
State<FundTransferSelfAmountScreen> createState() =>
_FundTransferSelfAmountScreenState();
}
class _FundTransferSelfAmountScreenState
extends State<FundTransferSelfAmountScreen> {
final _formKey = GlobalKey<FormState>();
final _amountController = TextEditingController();
final _remarksController = TextEditingController();
// --- Limit Checking Variables ---
final _limitService = getIt<LimitService>();
Limit? _limit;
bool _isLoadingLimit = true;
bool _isAmountOverLimit = false;
final _formatCurrency = NumberFormat.currency(locale: 'en_IN', symbol: '');
@override
void initState() {
super.initState();
_loadLimit(); // Fetch the daily limit
_amountController.addListener(_checkAmountLimit); // Listen for amount changes
}
@override
void dispose() {
_amountController.removeListener(_checkAmountLimit);
_amountController.dispose();
_remarksController.dispose();
super.dispose();
}
Future<void> _loadLimit() async {
setState(() {
_isLoadingLimit = true;
});
try {
final limitData = await _limitService.getLimit();
setState(() {
_limit = limitData;
_isLoadingLimit = false;
});
} catch (e) {
setState(() {
_isLoadingLimit = false;
});
}
}
void _checkAmountLimit() {
if (_limit == null) return;
final amount = double.tryParse(_amountController.text) ?? 0;
final remainingLimit = _limit!.dailyLimit - _limit!.usedLimit;
final bool isOverLimit = amount > remainingLimit;
if (isOverLimit) {
WidgetsBinding.instance.addPostFrameCallback((_) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'Amount exceeds remaining daily limit of ${_formatCurrency.format(remainingLimit)}'),
backgroundColor: Colors.red,
),
);
});
}
if (_isAmountOverLimit != isOverLimit) {
setState(() {
_isAmountOverLimit = isOverLimit;
});
}
}
void _onProceed() {
if (_formKey.currentState!.validate()) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => TransactionPinScreen(
onPinCompleted: (pinScreenContext, tpin) async {
final transfer = Transfer(
fromAccount: widget.debitAccountNo,
toAccount: widget.creditAccount.accountNo!,
toAccountType: 'Savings', // Assuming 'SB' for savings
amount: _amountController.text,
tpin: tpin,
);
final paymentService = getIt<PaymentService>();
final paymentResponseFuture =
paymentService.processQuickPayWithinBank(transfer);
Navigator.of(pinScreenContext).pushReplacement(
MaterialPageRoute(
builder: (_) =>
PaymentAnimationScreen(paymentResponse: paymentResponseFuture),
),
);
},
),
),
);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Fund Transfer"),
),
body: SafeArea(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Debit Account (User)
Text(
"Debit From",
style: Theme.of(context).textTheme.titleSmall,
),
Card(
elevation: 0,
margin: const EdgeInsets.symmetric(vertical: 8.0),
child: ListTile(
leading: Image.asset(
'assets/images/logo.png',
width: 40,
height: 40,
),
title: Text(widget.remitterName),
subtitle: Text(widget.debitAccountNo),
),
),
const SizedBox(height: 24),
// Credit Account (Self)
Text(
"Credited To",
style: Theme.of(context).textTheme.titleSmall,
),
Card(
elevation: 0,
margin: const EdgeInsets.symmetric(vertical: 8.0),
child: ListTile(
leading:
getBankLogo('Kangra Central Co-operative Bank', context),
title: Text(widget.creditAccount.name ?? 'N/A'),
subtitle: Text(widget.creditAccount.accountNo ?? 'N/A'),
),
),
const SizedBox(height: 24),
// Remarks
TextFormField(
controller: _remarksController,
decoration: const InputDecoration(
labelText: "Remarks (Optional)",
border: OutlineInputBorder(),
),
),
const SizedBox(height: 24),
// Amount
TextFormField(
controller: _amountController,
keyboardType: TextInputType.number,
decoration: const InputDecoration(
labelText: "Amount",
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.currency_rupee),
),
validator: (value) {
if (value == null || value.isEmpty) {
return "Amount is required";
}
if (double.tryParse(value) == null ||
double.parse(value) <= 0) {
return "Please enter a valid amount";
}
return null;
},
),
const SizedBox(height: 8),
// Daily Limit Display
if (_isLoadingLimit)
const Text('Fetching daily limit...'),
if (!_isLoadingLimit && _limit != null)
Text(
'Remaining Daily Limit: ${_formatCurrency.format(_limit!.dailyLimit - _limit!.usedLimit)}',
style: Theme.of(context).textTheme.bodySmall,
),
const Spacer(),
// Proceed Button
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: _isAmountOverLimit ? null : _onProceed,
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16),
),
child: const Text("Proceed"),
),
),
const SizedBox(height: 10),
],
),
),
),
),
);
}
}