changed imports of AppLocalization

Also added beneficiary validation while quick pay within bank
This commit is contained in:
2025-07-24 22:21:19 +05:30
parent 23d742ace9
commit 787fcdc2e2
42 changed files with 3965 additions and 1025 deletions

View File

@@ -20,7 +20,7 @@ import 'package:material_symbols_icons/material_symbols_icons.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:shimmer/shimmer.dart';
import 'package:kmobile/data/models/transaction.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import '../../../l10n/app_localizations.dart';
class DashboardScreen extends StatefulWidget {
const DashboardScreen({super.key});
@@ -57,11 +57,18 @@ class _DashboardScreenState extends State<DashboardScreen> {
setState(() => _transactions = fiveTxns);
} catch (e) {
log(accountNo, error: e);
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content:
Text(AppLocalizations.of(context).failedToLoad(e.toString()))));
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
AppLocalizations.of(context).failedToLoad(e.toString()),
),
),
);
} finally {
setState(() => _txLoading = false);
if (mounted) {
setState(() => _txLoading = false);
}
}
}
@@ -75,7 +82,8 @@ class _DashboardScreenState extends State<DashboardScreen> {
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
/*const*/ SnackBar(
content: Text(AppLocalizations.of(context).failedToRefresh)),
content: Text(AppLocalizations.of(context).failedToRefresh),
),
);
}
setState(() {
@@ -87,11 +95,7 @@ class _DashboardScreenState extends State<DashboardScreen> {
return Shimmer.fromColors(
baseColor: Colors.white.withOpacity(0.7),
highlightColor: Colors.white.withOpacity(0.3),
child: Container(
width: 100,
height: 32,
color: Colors.white,
),
child: Container(width: 100, height: 32, color: Colors.white),
);
}
@@ -106,7 +110,7 @@ class _DashboardScreenState extends State<DashboardScreen> {
'dr.',
'shri',
'smt.',
'kumari'
'kumari',
];
String processed = name.trim().toLowerCase();
for (final title in titles) {
@@ -151,6 +155,7 @@ class _DashboardScreenState extends State<DashboardScreen> {
TextButton(
onPressed: () async {
await storage.write('biometric_prompt_shown', 'true');
if (!mounted) return;
Navigator.of(context).pop();
},
child: Text(AppLocalizations.of(context).later),
@@ -199,8 +204,9 @@ class _DashboardScreenState extends State<DashboardScreen> {
title: Text(
AppLocalizations.of(context).kMobile,
style: TextStyle(
color: Theme.of(context).primaryColor,
fontWeight: FontWeight.w500),
color: Theme.of(context).primaryColor,
fontWeight: FontWeight.w500,
),
),
actions: [
Padding(
@@ -211,7 +217,8 @@ class _DashboardScreenState extends State<DashboardScreen> {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const ProfileScreen()),
builder: (context) => const ProfileScreen(),
),
);
},
child: CircleAvatar(
@@ -228,265 +235,334 @@ class _DashboardScreenState extends State<DashboardScreen> {
),
],
),
body: BlocBuilder<AuthCubit, AuthState>(builder: (context, state) {
if (state is AuthLoading || state is AuthInitial) {
return const Center(child: CircularProgressIndicator());
}
if (state is Authenticated) {
final users = state.users;
final currAccount = users[selectedAccountIndex];
// firsttime load
if (!_txInitialized) {
_txInitialized = true;
WidgetsBinding.instance.addPostFrameCallback((_) {
_loadTransactions(currAccount.accountNo!);
});
body: BlocBuilder<AuthCubit, AuthState>(
builder: (context, state) {
if (state is AuthLoading || state is AuthInitial) {
return const Center(child: CircularProgressIndicator());
}
final firstName = getProcessedFirstName(currAccount.name);
if (state is Authenticated) {
final users = state.users;
final currAccount = users[selectedAccountIndex];
// firsttime load
if (!_txInitialized) {
_txInitialized = true;
WidgetsBinding.instance.addPostFrameCallback((_) {
_loadTransactions(currAccount.accountNo!);
});
}
final firstName = getProcessedFirstName(currAccount.name);
return SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(left: 8.0),
child: Text(
"${AppLocalizations.of(context).hi} $firstName",
style: GoogleFonts.montserrat().copyWith(
return SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(left: 8.0),
child: Text(
"${AppLocalizations.of(context).hi} $firstName",
style: GoogleFonts.montserrat().copyWith(
fontSize: 25,
color: Theme.of(context).primaryColorDark,
fontWeight: FontWeight.w700),
fontWeight: FontWeight.w700,
),
),
),
),
const SizedBox(height: 16),
const SizedBox(height: 16),
// Account Info Card
Container(
padding: const EdgeInsets.symmetric(
horizontal: 18, vertical: 10),
decoration: BoxDecoration(
color: Theme.of(context).primaryColor,
borderRadius: BorderRadius.circular(16),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(AppLocalizations.of(context).accountNumber,
style: TextStyle(
color: Colors.white, fontSize: 12)),
DropdownButton<int>(
value: selectedAccountIndex,
dropdownColor: Theme.of(context).primaryColor,
underline: const SizedBox(),
icon: const Icon(Icons.keyboard_arrow_down),
iconEnabledColor: Colors.white,
style: const TextStyle(
color: Colors.white, fontSize: 14),
items: List.generate(users.length, (index) {
return DropdownMenuItem<int>(
value: index,
child: Text(
users[index].accountNo ?? 'N/A',
style: const TextStyle(
color: Colors.white, fontSize: 14),
),
);
}),
onChanged: (int? newIndex) async {
if (newIndex == null ||
newIndex == selectedAccountIndex) {
return;
}
if (isBalanceLoading) return;
if (isVisible) {
setState(() {
isBalanceLoading = true;
selectedAccountIndex = newIndex;
});
await Future.delayed(
const Duration(milliseconds: 200));
setState(() {
isBalanceLoading = false;
});
} else {
setState(() {
selectedAccountIndex = newIndex;
});
}
await _loadTransactions(
users[newIndex].accountNo!);
},
),
const Spacer(),
IconButton(
icon: isRefreshing
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
// Account Info Card
Container(
padding: const EdgeInsets.symmetric(
horizontal: 18,
vertical: 10,
),
decoration: BoxDecoration(
color: Theme.of(context).primaryColor,
borderRadius: BorderRadius.circular(16),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
"${AppLocalizations.of(context).accountNumber}: ",
style: const TextStyle(
color: Colors.white,
fontSize: 12,
),
),
DropdownButton<int>(
value: selectedAccountIndex,
dropdownColor: Theme.of(context).primaryColor,
underline: const SizedBox(),
icon: const Icon(Icons.keyboard_arrow_down),
iconEnabledColor: Colors.white,
style: const TextStyle(
color: Colors.white,
fontSize: 14,
),
items: List.generate(users.length, (index) {
return DropdownMenuItem<int>(
value: index,
child: Text(
users[index].accountNo ?? 'N/A',
style: const TextStyle(
color: Colors.white,
strokeWidth: 2,
fontSize: 14,
),
)
: const Icon(Icons.refresh,
color: Colors.white),
onPressed: isRefreshing
? null
: () => _refreshAccountData(context),
tooltip: 'Refresh',
),
);
}),
onChanged: (int? newIndex) async {
if (newIndex == null ||
newIndex == selectedAccountIndex) {
return;
}
if (isBalanceLoading) return;
if (isVisible) {
setState(() {
isBalanceLoading = true;
selectedAccountIndex = newIndex;
});
await Future.delayed(
const Duration(milliseconds: 200),
);
setState(() {
isBalanceLoading = false;
});
} else {
setState(() {
selectedAccountIndex = newIndex;
});
}
await _loadTransactions(
users[newIndex].accountNo!,
);
},
),
const Spacer(),
IconButton(
icon: isRefreshing
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
color: Colors.white,
strokeWidth: 2,
),
)
: const Icon(
Icons.refresh,
color: Colors.white,
),
onPressed: isRefreshing
? null
: () => _refreshAccountData(context),
tooltip: 'Refresh',
),
],
),
Text(
getFullAccountType(currAccount.accountType),
style: const TextStyle(
color: Colors.white,
fontSize: 16,
),
],
),
Text(
getFullAccountType(currAccount.accountType),
style: const TextStyle(
color: Colors.white, fontSize: 16),
),
const SizedBox(height: 4),
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
const Text("",
),
const SizedBox(height: 4),
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
const Text(
"",
style: TextStyle(
color: Colors.white,
fontSize: 40,
fontWeight: FontWeight.w700)),
isRefreshing || isBalanceLoading
? _buildBalanceShimmer()
: Text(
isVisible
? currAccount.currentBalance ?? '0.00'
: '********',
style: const TextStyle(
color: Colors.white,
fontSize: 40,
fontWeight: FontWeight.w700,
),
),
isRefreshing || isBalanceLoading
? _buildBalanceShimmer()
: Text(
isVisible
? currAccount.currentBalance ??
'0.00'
: '********',
style: const TextStyle(
color: Colors.white,
fontSize: 40,
fontWeight: FontWeight.w700)),
const Spacer(),
InkWell(
onTap: () async {
if (isBalanceLoading) return;
if (!isVisible) {
setState(() {
isBalanceLoading = true;
});
await Future.delayed(
const Duration(seconds: 1));
setState(() {
isVisible = true;
isBalanceLoading = false;
});
} else {
setState(() {
isVisible = false;
});
}
},
child: Icon(
fontWeight: FontWeight.w700,
),
),
const Spacer(),
InkWell(
onTap: () async {
if (isBalanceLoading) return;
if (!isVisible) {
setState(() {
isBalanceLoading = true;
});
await Future.delayed(
const Duration(seconds: 1),
);
setState(() {
isVisible = true;
isBalanceLoading = false;
});
} else {
setState(() {
isVisible = false;
});
}
},
child: Icon(
isVisible
? Symbols.visibility_lock
: Symbols.visibility,
color: Colors.white),
),
],
color: Colors.white,
),
),
],
),
const SizedBox(height: 15),
],
),
),
const SizedBox(height: 18),
Text(
AppLocalizations.of(context).quickLinks,
style: const TextStyle(fontSize: 17),
),
const SizedBox(height: 16),
// Quick Links
GridView.count(
crossAxisCount: 4,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
children: [
_buildQuickLink(
Symbols.id_card,
AppLocalizations.of(context).customerInfo,
() {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => CustomerInfoScreen(
user: users[selectedAccountIndex],
),
),
);
},
),
_buildQuickLink(
Symbols.currency_rupee,
AppLocalizations.of(context).quickPay,
() {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => QuickPayScreen(
debitAccount: currAccount.accountNo!,
),
),
);
},
),
_buildQuickLink(
Symbols.send_money,
AppLocalizations.of(context).fundTransfer,
() {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
const FundTransferBeneficiaryScreen(),
),
);
},
disable: true,
),
_buildQuickLink(
Symbols.server_person,
AppLocalizations.of(context).accountInfo,
() {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AccountInfoScreen(
users: users,
selectedIndex: selectedAccountIndex,
),
),
);
},
),
_buildQuickLink(
Symbols.receipt_long,
AppLocalizations.of(context).accountStatement,
() {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AccountStatementScreen(
accountNo:
users[selectedAccountIndex].accountNo!,
),
),
);
},
),
_buildQuickLink(
Symbols.checkbook,
AppLocalizations.of(context).handleCheque,
() {},
disable: true,
),
_buildQuickLink(
Icons.group,
AppLocalizations.of(context).manageBeneficiary,
() {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
const ManageBeneficiariesScreen(),
),
);
},
disable: true,
),
_buildQuickLink(
Symbols.support_agent,
AppLocalizations.of(context).contactUs,
() {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const EnquiryScreen(),
),
);
},
),
const SizedBox(height: 15),
],
),
),
const SizedBox(height: 18),
Text(
AppLocalizations.of(context).quickLinks,
style: TextStyle(fontSize: 17),
),
const SizedBox(height: 16),
const SizedBox(height: 5),
// Quick Links
GridView.count(
crossAxisCount: 4,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
children: [
_buildQuickLink(Symbols.id_card,
AppLocalizations.of(context).customerInfo, () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => CustomerInfoScreen(
user: users[selectedAccountIndex],
)));
}),
_buildQuickLink(Symbols.currency_rupee,
AppLocalizations.of(context).quickPay, () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => QuickPayScreen(
debitAccount: currAccount.accountNo!)));
}),
_buildQuickLink(Symbols.send_money,
AppLocalizations.of(context).fundTransfer, () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
const FundTransferBeneficiaryScreen()));
}, disable: false),
_buildQuickLink(Symbols.server_person,
AppLocalizations.of(context).accountInfo, () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AccountInfoScreen(
users: users,
selectedIndex: selectedAccountIndex)));
}),
_buildQuickLink(Symbols.receipt_long,
AppLocalizations.of(context).accountStatement, () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AccountStatementScreen(
accountNo: users[selectedAccountIndex]
.accountNo!,
)));
}),
_buildQuickLink(Symbols.checkbook,
AppLocalizations.of(context).handleCheque, () {},
disable: false),
_buildQuickLink(Icons.group,
AppLocalizations.of(context).manageBeneficiary, () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
const ManageBeneficiariesScreen()));
}, disable: false),
_buildQuickLink(Symbols.support_agent,
AppLocalizations.of(context).contactUs, () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const EnquiryScreen()));
}),
],
),
const SizedBox(height: 5),
// Recent Transactions
Text(
AppLocalizations.of(context).recentTransactions,
style: TextStyle(fontSize: 17),
),
const SizedBox(height: 16),
if (_txLoading)
..._buildTransactionShimmer()
else if (_transactions.isNotEmpty)
..._transactions.map((tx) => ListTile(
// Recent Transactions
Text(
AppLocalizations.of(context).recentTransactions,
style: const TextStyle(fontSize: 17),
),
const SizedBox(height: 16),
if (_txLoading)
..._buildTransactionShimmer()
else if (_transactions.isNotEmpty)
..._transactions.map(
(tx) => ListTile(
leading: Icon(
tx.type == 'CR'
? Symbols.call_received
@@ -502,32 +578,39 @@ class _DashboardScreenState extends State<DashboardScreen> {
: '',
style: const TextStyle(fontSize: 14),
),
subtitle: Text(tx.date ?? '',
style: const TextStyle(fontSize: 12)),
trailing: Text("${tx.amount}",
style: const TextStyle(fontSize: 16)),
))
else
Padding(
padding: const EdgeInsets.symmetric(vertical: 24.0),
child: Center(
child: Text(
AppLocalizations.of(context).noTransactions,
style: TextStyle(
fontSize: 16,
color: Colors.grey[600],
subtitle: Text(
tx.date ?? '',
style: const TextStyle(fontSize: 12),
),
trailing: Text(
"${tx.amount}",
style: const TextStyle(fontSize: 16),
),
),
)
else
Padding(
padding: const EdgeInsets.symmetric(vertical: 24.0),
child: Center(
child: Text(
AppLocalizations.of(context).noTransactions,
style: TextStyle(
fontSize: 16,
color: Colors.grey[600],
),
),
),
),
),
],
],
),
),
),
);
}
return Center(
child: Text(AppLocalizations.of(context).somethingWentWrong),
);
}
return Center(
child: Text(AppLocalizations.of(context).somethingWentWrong));
}),
},
),
),
);
}
@@ -554,20 +637,28 @@ class _DashboardScreenState extends State<DashboardScreen> {
});
}
Widget _buildQuickLink(IconData icon, String label, VoidCallback onTap,
{bool disable = false}) {
Widget _buildQuickLink(
IconData icon,
String label,
VoidCallback onTap, {
bool disable = false,
}) {
return InkWell(
onTap: disable ? null : onTap,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon,
size: 30,
color: disable ? Colors.grey : Theme.of(context).primaryColor),
Icon(
icon,
size: 30,
color: disable ? Colors.grey : Theme.of(context).primaryColor,
),
const SizedBox(height: 4),
Text(label,
textAlign: TextAlign.center,
style: const TextStyle(fontSize: 13)),
Text(
label,
textAlign: TextAlign.center,
style: const TextStyle(fontSize: 13),
),
],
),
);

View File

@@ -1,14 +1,11 @@
import 'package:flutter/material.dart';
import '../../accounts/models/account.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import '../../../../../l10n/app_localizations.dart';
class AccountCard extends StatelessWidget {
final Account account;
const AccountCard({
super.key,
required this.account,
});
const AccountCard({super.key, required this.account});
@override
Widget build(BuildContext context) {
@@ -59,10 +56,7 @@ class AccountCard extends StatelessWidget {
const SizedBox(height: 20),
Text(
account.accountNumber,
style: const TextStyle(
color: Colors.white70,
fontSize: 16,
),
style: const TextStyle(color: Colors.white70, fontSize: 16),
),
const SizedBox(height: 30),
Text(
@@ -76,10 +70,7 @@ class AccountCard extends StatelessWidget {
const SizedBox(height: 5),
Text(
AppLocalizations.of(context).availableBalance,
style: TextStyle(
color: Colors.white70,
fontSize: 12,
),
style: TextStyle(color: Colors.white70, fontSize: 12),
),
],
),