import 'dart:developer'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/svg.dart'; import 'package:kmobile/data/repositories/transaction_repository.dart'; import 'package:kmobile/di/injection.dart'; import 'package:kmobile/features/accounts/screens/account_info_screen.dart'; import 'package:kmobile/features/accounts/screens/account_statement_screen.dart'; import 'package:kmobile/features/auth/controllers/auth_cubit.dart'; import 'package:kmobile/features/auth/controllers/auth_state.dart'; import 'package:kmobile/features/customer_info/screens/customer_info_screen.dart'; import 'package:kmobile/features/beneficiaries/screens/manage_beneficiaries_screen.dart'; import 'package:kmobile/features/enquiry/screens/enquiry_screen.dart'; import 'package:kmobile/features/fund_transfer/screens/fund_transfer_beneficiary_screen.dart'; import 'package:kmobile/features/profile/profile_screen.dart'; import 'package:kmobile/features/quick_pay/screens/quick_pay_screen.dart'; import 'package:kmobile/security/secure_storage.dart'; import 'package:local_auth/local_auth.dart'; 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 '../../../l10n/app_localizations.dart'; class DashboardScreen extends StatefulWidget { const DashboardScreen({super.key}); @override State createState() => _DashboardScreenState(); } class _DashboardScreenState extends State { int selectedAccountIndex = 0; bool isVisible = false; bool isRefreshing = false; bool isBalanceLoading = false; bool _biometricPromptShown = false; bool _txLoading = false; List _transactions = []; bool _txInitialized = false; Future _loadTransactions(String accountNo) async { setState(() { _txLoading = true; _transactions = []; }); try { final repo = getIt(); final txs = await repo.fetchTransactions(accountNo); var fiveTxns = []; //only take the first 5 transactions if (txs.length > 5) { fiveTxns = txs.sublist(0, 5); } else { fiveTxns = txs; } setState(() => _transactions = fiveTxns); } catch (e) { log(accountNo, error: e); if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( AppLocalizations.of(context).failedToLoad(e.toString()), ), ), ); } finally { if (mounted) { setState(() => _txLoading = false); } } } Future _refreshAccountData(BuildContext context) async { setState(() { isRefreshing = true; }); try { // Call your AuthCubit or repository to refresh user/accounts data await context.read().refreshUserData(); } catch (e) { ScaffoldMessenger.of(context).showSnackBar( /*const*/ SnackBar( content: Text(AppLocalizations.of(context).failedToRefresh), ), ); } setState(() { isRefreshing = false; }); } Widget _buildBalanceShimmer() { return Shimmer.fromColors( baseColor: Theme.of(context).dialogBackgroundColor, highlightColor: Theme.of(context).dialogBackgroundColor, child: Container(width: 100, height: 32, color: Theme.of(context).scaffoldBackgroundColor), ); } String getProcessedFirstName(String? name) { if (name == null || name.trim().isEmpty) return ''; // Remove common titles final titles = [ 'mr.', 'mrs.', 'ms.', 'miss', 'dr.', 'shri', 'smt.', 'kumari', ]; String processed = name.trim().toLowerCase(); for (final title in titles) { if (processed.startsWith(title)) { processed = processed.replaceFirst(title, '').trim(); break; } } // Take the first word (first name) final firstName = processed.split(' ').first; // Convert to title case if (firstName.isEmpty) return ''; return firstName[0].toUpperCase() + firstName.substring(1); } String getFullAccountType(String? accountType) { if (accountType == null || accountType.isEmpty) return 'N/A'; // Convert to title case switch (accountType.toLowerCase()) { case 'sa': return AppLocalizations.of(context).savingsAccount; case 'ln': return AppLocalizations.of(context).loanAccount; case 'td': return AppLocalizations.of(context).termDeposit; case 'rd': return AppLocalizations.of(context).recurringDeposit; default: return AppLocalizations.of(context).unknownAccount; } } Future _showBiometricOptInDialog() async { final storage = SecureStorage(); showDialog( context: context, barrierDismissible: false, builder: (_) => AlertDialog( title: Text(AppLocalizations.of(context).enableBiometric), content: Text(AppLocalizations.of(context).useBiometricPrompt), actions: [ TextButton( onPressed: () async { await storage.write('biometric_prompt_shown', 'true'); if (!mounted) return; Navigator.of(context).pop(); }, child: Text(AppLocalizations.of(context).later), ), TextButton( onPressed: () async { final auth = LocalAuthentication(); final canCheck = await auth.canCheckBiometrics; bool ok = false; if (canCheck) { ok = await auth.authenticate( localizedReason: AppLocalizations.of(context).scanBiometric, ); } if (ok) { await storage.write('biometric_enabled', 'true'); } await storage.write('biometric_prompt_shown', 'true'); Navigator.of(context).pop(); }, child: Text(AppLocalizations.of(context).enable), ), ], ), ); } @override Widget build(BuildContext context) { return BlocListener( listener: (context, state) async { if (state is Authenticated && !_biometricPromptShown) { _biometricPromptShown = true; final storage = getIt(); final already = await storage.read('biometric_prompt_shown'); if (already == null) { _showBiometricOptInDialog(); } } }, child: Scaffold( backgroundColor: Theme.of(context).scaffoldBackgroundColor, appBar: AppBar( backgroundColor:Theme.of(context).scaffoldBackgroundColor, automaticallyImplyLeading: false, title: Text( AppLocalizations.of(context).kMobile, style: TextStyle( color: Theme.of(context).primaryColor, fontWeight: FontWeight.w500, ), ), centerTitle: true, actions: [ Padding( padding: const EdgeInsets.only(right: 10.0), child: InkWell( borderRadius: BorderRadius.circular(20), onTap: () { Navigator.push( context, MaterialPageRoute( builder: (context) => const ProfileScreen(), ), ); }, child: CircleAvatar( backgroundColor: Colors.grey[200], radius: 20, child: SvgPicture.asset( 'assets/images/avatar_male.svg', width: 40, height: 40, fit: BoxFit.cover, ), ), ), ), ], ), body: BlocBuilder( 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]; // first‐time 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( fontSize: 25, color: Theme.of(context).primaryColor, fontWeight: FontWeight.w700, ), ), ), 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: Theme.of(context).dialogBackgroundColor, fontSize: 12, ), ), DropdownButton( value: selectedAccountIndex, dropdownColor: Theme.of(context).primaryColor, underline: const SizedBox(), icon: const Icon(Icons.keyboard_arrow_down), iconEnabledColor:Theme.of(context).dialogBackgroundColor, style: TextStyle( color: Theme.of(context).dialogBackgroundColor, fontSize: 14, ), items: List.generate(users.length, (index) { return DropdownMenuItem( value: index, child: Text( users[index].accountNo ?? 'N/A', style: TextStyle( color: Theme.of(context).dialogBackgroundColor, 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 ? SizedBox( width: 20, height: 20, child: CircularProgressIndicator( color: Theme.of(context).dialogBackgroundColor, strokeWidth: 2, ), ) : Icon( Icons.refresh, color: Theme.of(context).dialogBackgroundColor, ), onPressed: isRefreshing ? null : () => _refreshAccountData(context), tooltip: 'Refresh', ), ], ), Text( getFullAccountType(currAccount.accountType), style: TextStyle( color: Theme.of(context).dialogBackgroundColor, fontSize: 16, ), ), const SizedBox(height: 4), Row( mainAxisAlignment: MainAxisAlignment.start, children: [ Text( "₹ ", style: TextStyle( color: Theme.of(context).dialogBackgroundColor, fontSize: 40, fontWeight: FontWeight.w700, ), ), isRefreshing || isBalanceLoading ? _buildBalanceShimmer() : Text( isVisible ? currAccount.currentBalance ?? '0.00' : '********', style: TextStyle( color: Theme.of(context).dialogBackgroundColor, 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( isVisible ? Symbols.visibility_lock : Symbols.visibility, color: Theme.of(context).scaffoldBackgroundColor, ), ), ], ), 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: 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: true), _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: 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 : Symbols.call_made, color: tx.type == 'CR' ? Colors.green : Colors.red, ), title: Text( tx.name != null ? (tx.name!.length > 18 ? tx.name!.substring(0, 20) : tx.name!) : '', 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], ), ), ), ), ], ), ), ); } return Center( child: Text(AppLocalizations.of(context).somethingWentWrong), ); }, ), ), ); } List _buildTransactionShimmer() { return List.generate(3, (i) { return ListTile( leading: Shimmer.fromColors( baseColor: Colors.grey[300]!, highlightColor: Colors.grey[100]!, child: CircleAvatar(radius: 12, backgroundColor: Theme.of(context).scaffoldBackgroundColor), ), title: Shimmer.fromColors( baseColor: Colors.grey[300]!, highlightColor: Colors.grey[100]!, child: Container(height: 10, width: 100, color: Theme.of(context).scaffoldBackgroundColor), ), subtitle: Shimmer.fromColors( baseColor: Colors.grey[300]!, highlightColor: Colors.grey[100]!, child: Container(height: 8, width: 60, color: Theme.of(context).scaffoldBackgroundColor), ), ); }); } 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, ), const SizedBox(height: 4), Text( label, textAlign: TextAlign.center, style: const TextStyle(fontSize: 13), ), ], ), ); } }