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/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'; 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); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Failed to load transactions: $e'))); } finally { 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('Failed to refresh data')), ); } setState(() { isRefreshing = false; }); } Widget _buildBalanceShimmer() { return Shimmer.fromColors( baseColor: Colors.white.withOpacity(0.7), highlightColor: Colors.white.withOpacity(0.3), child: Container( width: 100, height: 32, color: Colors.white, ), ); } 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 'Savings Account'; case 'ln': return 'Loan Account'; case 'td': return 'Term Deposit Account'; case 'rd': return 'Recurring Deposit Account'; default: return 'Unknown Account Type'; } } Future _showBiometricOptInDialog() async { final storage = SecureStorage(); showDialog( context: context, barrierDismissible: false, builder: (_) => AlertDialog( title: const Text('Enable Biometric Authentication'), content: const Text('Use fingerprint/face ID for faster login?'), actions: [ TextButton( onPressed: () async { await storage.write('biometric_prompt_shown', 'true'); Navigator.of(context).pop(); }, child: const Text('Later'), ), TextButton( onPressed: () async { final auth = LocalAuthentication(); final canCheck = await auth.canCheckBiometrics; bool ok = false; if (canCheck) { ok = await auth.authenticate( localizedReason: 'Scan to enable biometric login', ); } if (ok) { await storage.write('biometric_enabled', 'true'); } await storage.write('biometric_prompt_shown', 'true'); Navigator.of(context).pop(); }, child: const Text('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: const Color(0xfff5f9fc), appBar: AppBar( backgroundColor: const Color(0xfff5f9fc), automaticallyImplyLeading: false, title: Text( 'kMobile', style: TextStyle( color: Theme.of(context).primaryColor, fontWeight: FontWeight.w500), ), actions: [ Padding( padding: const EdgeInsets.only(right: 10.0), child: InkWell( borderRadius: BorderRadius.circular(20), onTap: () { // Navigator.push( // context, // MaterialPageRoute( // builder: (context) => const Preference())); }, 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( "Hi! $firstName", style: GoogleFonts.montserrat().copyWith( fontSize: 25, color: Theme.of(context).primaryColorDark, 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: [ const Text("Account Number: ", style: TextStyle( color: Colors.white, fontSize: 12)), DropdownButton( 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( 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( 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), ), 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)), 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), ), ], ), const SizedBox(height: 15), ], ), ), const SizedBox(height: 18), const Text( 'Quick Links', style: TextStyle(fontSize: 17), ), const SizedBox(height: 16), // Quick Links GridView.count( crossAxisCount: 4, shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), children: [ _buildQuickLink(Symbols.id_card, "Customer \n Info", () { Navigator.push( context, MaterialPageRoute( builder: (context) => CustomerInfoScreen( user: users[selectedAccountIndex], ))); }), _buildQuickLink(Symbols.currency_rupee, "Quick \n Pay", () { Navigator.push( context, MaterialPageRoute( builder: (context) => QuickPayScreen( debitAccount: currAccount.accountNo!))); }), _buildQuickLink(Symbols.send_money, "Fund \n Transfer", () { Navigator.push( context, MaterialPageRoute( builder: (context) => const FundTransferBeneficiaryScreen())); }, disable: true), _buildQuickLink( Symbols.server_person, "Account \n Info", () { Navigator.push( context, MaterialPageRoute( builder: (context) => AccountInfoScreen( user: users[selectedAccountIndex]))); }), _buildQuickLink( Symbols.receipt_long, "Account \n Statement", () { Navigator.push( context, MaterialPageRoute( builder: (context) => AccountStatementScreen( accountNo: users[selectedAccountIndex] .accountNo!, ))); }), _buildQuickLink( Symbols.checkbook, "Handle \n Cheque", () {}, disable: true), _buildQuickLink(Icons.group, "Manage \n Beneficiary", () { Navigator.push( context, MaterialPageRoute( builder: (context) => const ManageBeneficiariesScreen())); }, disable: true), _buildQuickLink(Symbols.support_agent, "Contact \n Us", () { Navigator.push( context, MaterialPageRoute( builder: (context) => const EnquiryScreen())); }), ], ), const SizedBox(height: 5), // Recent Transactions const Text( 'Recent Transactions', style: 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( 'No transactions found for this account.', style: TextStyle( fontSize: 16, color: Colors.grey[600], ), ), ), ), ], ), ), ); } return const Center(child: Text("Something went wrong")); }), ), ); } List _buildTransactionShimmer() { return List.generate(3, (i) { return ListTile( leading: Shimmer.fromColors( baseColor: Colors.grey[300]!, highlightColor: Colors.grey[100]!, child: const CircleAvatar(radius: 12, backgroundColor: Colors.white), ), title: Shimmer.fromColors( baseColor: Colors.grey[300]!, highlightColor: Colors.grey[100]!, child: Container(height: 10, width: 100, color: Colors.white), ), subtitle: Shimmer.fromColors( baseColor: Colors.grey[300]!, highlightColor: Colors.grey[100]!, child: Container(height: 8, width: 60, color: Colors.white), ), ); }); } 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)), ], ), ); } }