import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:kmobile/data/models/user.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/accounts/screens/all_accounts_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_screen.dart'; import 'package:kmobile/features/profile/profile_screen.dart'; import 'package:kmobile/features/quick_pay/screens/quick_pay_screen.dart'; import 'package:kmobile/features/service/screens/branch_locator_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 '../../../l10n/app_localizations.dart'; class DashboardScreen extends StatefulWidget { const DashboardScreen({super.key}); @override State createState() => _DashboardScreenState(); } class _DashboardScreenState extends State with SingleTickerProviderStateMixin, RouteAware { int selectedAccountIndex = 0; Map _visibilityMap = {}; bool isRefreshing = false; bool _biometricPromptShown = false; bool _txInitialized = false; PageController? _pageController; final routeObserver = getIt>>(); @override void didChangeDependencies() { super.didChangeDependencies(); routeObserver.subscribe(this, ModalRoute.of(context)!); } @override void dispose() { routeObserver.unsubscribe(this); _pageController?.dispose(); _visibilityMap.clear(); super.dispose(); } @override void didPushNext() { // This method is called when another route is pushed on top of this one. // We clear the map and call setState to ensure the UI is updated // if the user navigates back. setState(() { _visibilityMap.clear(); }); } Widget _buildAccountCard(User user, bool isSelected) { final theme = Theme.of(context); final bool isCardVisible = _visibilityMap[user.accountNo] ?? false; // Animated scale for the selected card final scale = isSelected ? 1.02 : 0.9; return Padding( padding: const EdgeInsets.symmetric(horizontal: 3.0), child: AnimatedScale( duration: const Duration(milliseconds: 200), scale: scale, child: Container( padding: const EdgeInsets.symmetric( horizontal: 18, vertical: 10, ), decoration: BoxDecoration( color: const Color(0xFF01A04C), borderRadius: BorderRadius.circular(20), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Top section with account type and number (no refresh button here) Row( children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( getFullAccountType(user.accountType), style: TextStyle( color: theme.colorScheme.onPrimary, fontSize: 16, fontWeight: FontWeight.w700, ), ), Text( user.accountNo ?? 'N/A', style: TextStyle( color: theme.colorScheme.onPrimary, fontSize: 14, fontWeight: FontWeight.w700, ), overflow: TextOverflow.ellipsis, ), ], ), ), if (isSelected) // Show logo only if card is selected CircleAvatar( radius: 20, backgroundColor: Colors.white, child: Padding( padding: const EdgeInsets.all(2.0), child: ClipOval( child: Image.asset( 'assets/images/logo.png', width: 30, height: 30, fit: BoxFit.cover, ), ), ), ), ], ), const Spacer(), // Bottom section with balance and combined toggle/refresh Row( mainAxisAlignment: MainAxisAlignment.start, children: [ if (isRefreshing && isSelected) Expanded(child: _buildBalanceShimmer()) else Expanded( child: FittedBox( fit: BoxFit.scaleDown, alignment: Alignment.centerLeft, child: Row( children: [ Text( "₹ ", style: TextStyle( color: theme.colorScheme.onPrimary, fontSize: 40, fontWeight: FontWeight.w700, ), ), Text( isCardVisible ? user.currentBalance ?? '0.00' : '*****', style: TextStyle( color: theme.colorScheme.onPrimary, fontSize: 40, fontWeight: FontWeight.w700, ), ), ], ), ), ), const SizedBox(width: 10), // A steady space if (isSelected) // Only show toggle for selected card InkWell( onTap: () async { if (isRefreshing) return; // Prevent taps while refreshing final accountNo = user.accountNo; if (accountNo == null) return; final bool currentVisibility = _visibilityMap[accountNo] ?? false; if (!currentVisibility) { // If hidden, refresh data and then show the balance await _refreshAccountData(context); if (mounted) { setState(() { _visibilityMap[accountNo] = true; }); } } else { // If visible, just hide it setState(() { _visibilityMap[accountNo] = false; }); } }, child: Padding( padding: const EdgeInsets.all(8.0), child: Icon( isCardVisible ? Symbols.visibility_lock : Symbols.visibility, color: theme.scaffoldBackgroundColor, weight: 800, ), ), ), ], ), const Spacer(), ], ), ), ), ); } 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) { if (!context.mounted) { return; } ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(AppLocalizations.of(context).failedToRefresh), ), ); } setState(() { isRefreshing = false; }); } Widget _buildBalanceShimmer() { final theme = Theme.of(context); return Shimmer.fromColors( baseColor: theme.colorScheme.primary, highlightColor: theme.colorScheme.onPrimary, child: Container(height: 36, color: theme.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 'sb': 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; case 'ca': return "Current Account"; case 'cc': return "Cash Credit Account"; case 'od': return "Overdraft Account"; 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; if (!mounted) return; 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'); if (!mounted) return; Navigator.of(context).pop(); }, child: Text(AppLocalizations.of(context).enable), ), ], ), ); } @override Widget build(BuildContext context) { final theme = Theme.of(context); final authState = context.read().state; String mobileNumberToPass = ''; String customerNo = ''; String customerName = ''; if (authState is Authenticated) { if (selectedAccountIndex >= 0 && selectedAccountIndex < authState.users.length) { mobileNumberToPass = authState.users[selectedAccountIndex].mobileNo ?? ''; customerNo = authState.users[selectedAccountIndex].cifNumber ?? ''; customerName = authState.users[selectedAccountIndex].name ?? ''; } } 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.scaffoldBackgroundColor, appBar: AppBar( leading: Padding( padding: const EdgeInsets.only(left: 10.0), child: Material( elevation: 4.0, clipBehavior: Clip.antiAlias, shape: RoundedRectangleBorder( side: const BorderSide(color: Colors.white, width: 1.5), borderRadius: BorderRadius.circular(12.0), ), child: Container( width: 40, height: 40, decoration: const BoxDecoration( color: Colors.white, ), child: Image.asset( 'assets/images/logo.png', fit: BoxFit.fill, ), ), ), ), title: Text( AppLocalizations.of(context).kccBankFull.replaceAll('-', '\u2011'), textAlign: TextAlign.center, softWrap: true, // Explicitly allow wrapping maxLines: 2, // Allow text to wrap to a maximum of 2 lines style: TextStyle( color: theme.colorScheme.onPrimary, fontWeight: FontWeight.w700, fontSize: 20, ), ), // Removed centerTitle: true to give more space for text wrapping actions: [ Padding( padding: const EdgeInsets.only(right: 10.0), child: InkWell( borderRadius: BorderRadius.circular(20), onTap: () { Navigator.push( context, MaterialPageRoute( builder: (context) => ProfileScreen( mobileNumber: mobileNumberToPass, customerNo: customerNo, customerName: customerName), ), ); }, child: const CircleAvatar( radius: 21, child: Icon( Symbols.person, size: 30, ), ), ), ), ], ), 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]; final accountType = currAccount.accountType?.toLowerCase(); final isPaymentDisabled = accountType != 'sa' && accountType != 'sb' && accountType != 'ca'; // first‐time load if (!_txInitialized) { _txInitialized = true; WidgetsBinding.instance.addPostFrameCallback((_) {}); } _pageController ??= PageController( initialPage: selectedAccountIndex, viewportFraction: 0.75, ); final firstName = getProcessedFirstName(currAccount.name); return SingleChildScrollView( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 16), // Added spacing Padding( padding: const EdgeInsets.only(left: 4.0), child: Text( "${AppLocalizations.of(context).hi} $firstName!", style: GoogleFonts.baumans().copyWith( fontSize: 20, color: theme.colorScheme.primary, fontWeight: FontWeight.w700, ), ), ), const SizedBox(height: 16), // Account Info Cards SizedBox( height: 160, child: PageView.builder( clipBehavior: Clip.none, controller: _pageController, itemCount: users.length, // Keep this to show adjacent cards onPageChanged: (int newIndex) async { if (newIndex == selectedAccountIndex) return; // Hide the balance of the old card when scrolling away final oldAccountNo = users[selectedAccountIndex].accountNo; if (oldAccountNo != null) { _visibilityMap[oldAccountNo] = false; } setState(() { selectedAccountIndex = newIndex; }); }, itemBuilder: (context, index) { final user = users[index]; final isSelected = index == selectedAccountIndex; return _buildAccountCard(user, isSelected); }, ), ), const SizedBox(height: 8), Row( mainAxisAlignment: MainAxisAlignment.end, children: [ GestureDetector( onTap: () { Navigator.push( context, MaterialPageRoute( builder: (context) => AllAccountsScreen(users: users), ), ); }, child: Text( AppLocalizations.of(context).viewall, style: TextStyle( fontSize: 14, fontWeight: FontWeight.bold, color: theme.colorScheme.primary, ), ), ), ], ), const SizedBox(height: 18), Text( AppLocalizations.of(context).quickLinks, style: const TextStyle(fontSize: 17), ), const SizedBox(height: 24), // Quick Links GridView.count( crossAxisCount: 3, shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), crossAxisSpacing: 10, mainAxisSpacing: 10, childAspectRatio: 1, 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!, ), ), ); }, disable: isPaymentDisabled, ), _buildQuickLink(Symbols.send_money, AppLocalizations.of(context).fundTransfer, () { Navigator.push( context, MaterialPageRoute( builder: (context) => FundTransferScreen( creditAccountNo: users[selectedAccountIndex] .accountNo!, remitterName: users[selectedAccountIndex].name!, // Pass the full list of accounts accounts: users))); }, disable: isPaymentDisabled), _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( users: users, selectedIndex: selectedAccountIndex, ))); }), _buildQuickLink(Icons.location_pin, AppLocalizations.of(context).branchlocator, () { Navigator.push( context, MaterialPageRoute( builder: (context) => const BranchLocatorScreen())); }, disable: false), _buildQuickLink(Icons.group, AppLocalizations.of(context).manageBeneficiary, () { Navigator.push( context, MaterialPageRoute( builder: (context) => ManageBeneficiariesScreen( customerName: currAccount.name!))); }, disable: false), _buildQuickLink(Symbols.support_agent, AppLocalizations.of(context).contactUs, () { Navigator.push( context, MaterialPageRoute( builder: (context) => const EnquiryScreen())); }), _buildQuickLink( Symbols.person, AppLocalizations.of(context).profile, () { Navigator.push( context, MaterialPageRoute( builder: (context) => ProfileScreen( mobileNumber: mobileNumberToPass, customerNo: customerNo, customerName: customerName), ), ); }, disable: false, ), ], ), ], ), ), ); } return Center( child: Text(AppLocalizations.of(context).somethingWentWrong), ); }, ), ), ); } Widget _buildQuickLink( IconData icon, String label, VoidCallback onTap, { bool disable = false, }) { final theme = Theme.of(context); return Card( elevation: 2, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12.0), ), child: InkWell( onTap: disable ? null : onTap, borderRadius: BorderRadius.circular(12.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( icon, size: 30, color: disable ? theme.disabledColor : theme.colorScheme.primary, ), const SizedBox(height: 4), Text( label, textAlign: TextAlign.center, style: theme.textTheme.titleMedium?.copyWith( fontWeight: FontWeight.bold, fontSize: 12, color: disable ? theme.disabledColor : theme.colorScheme.onSurface, ), ), ], ), ), ); } }