From b7fe6a9d18e2cfcfa89d4aaa34adb0a35fafa0c0 Mon Sep 17 00:00:00 2001 From: asif Date: Mon, 24 Nov 2025 18:16:53 +0530 Subject: [PATCH] View All Created --- lib/app.dart | 3 + lib/di/injection.dart | 7 +- .../accounts/screens/all_accounts_screen.dart | 141 ++++++++++ .../dashboard/screens/dashboard_screen.dart | 247 ++++++++++++------ 4 files changed, 316 insertions(+), 82 deletions(-) create mode 100644 lib/features/accounts/screens/all_accounts_screen.dart diff --git a/lib/app.dart b/lib/app.dart index ce3d7e3..fe72f70 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -127,6 +127,9 @@ class _KMobileState extends State with WidgetsBindingObserver { theme: themeState.getLightThemeData(), darkTheme: themeState.getDarkThemeData(), themeMode: context.watch().state.mode, + navigatorObservers: [ + getIt>>(), + ], onGenerateRoute: AppRoutes.generateRoute, initialRoute: AppRoutes.splash, home: const AuthGate(), diff --git a/lib/di/injection.dart b/lib/di/injection.dart index 051976e..33ec60a 100644 --- a/lib/di/injection.dart +++ b/lib/di/injection.dart @@ -1,3 +1,4 @@ +import 'package:flutter/material.dart'; import 'package:kmobile/api/services/branch_service.dart'; import 'package:kmobile/api/services/limit_service.dart'; import 'package:kmobile/api/services/rtgs_service.dart'; @@ -21,6 +22,8 @@ import '../security/secure_storage.dart'; final getIt = GetIt.instance; Future setupDependencies() async { + getIt.registerSingleton>>( + RouteObserver>()); //getIt.registerLazySingleton(() => ThemeController()); //getIt.registerLazySingleton(() => ThemeModeController()); getIt.registerSingleton(ThemeCubit()); @@ -71,9 +74,9 @@ Dio _createDioClient() { final dio = Dio( BaseOptions( baseUrl: - //'http://lb-test-mobile-banking-app-192209417.ap-south-1.elb.amazonaws.com:8080', //test + 'http://lb-test-mobile-banking-app-192209417.ap-south-1.elb.amazonaws.com:8080', //test //'http://lb-kccb-mobile-banking-app-848675342.ap-south-1.elb.amazonaws.com', //prod - 'https://kccbmbnk.net', //prod small + //'https://kccbmbnk.net', //prod small connectTimeout: const Duration(seconds: 60), receiveTimeout: const Duration(seconds: 60), headers: { diff --git a/lib/features/accounts/screens/all_accounts_screen.dart b/lib/features/accounts/screens/all_accounts_screen.dart new file mode 100644 index 0000000..3413e34 --- /dev/null +++ b/lib/features/accounts/screens/all_accounts_screen.dart @@ -0,0 +1,141 @@ +import 'package:flutter/material.dart'; +import 'package:kmobile/data/models/user.dart'; +import 'package:material_symbols_icons/material_symbols_icons.dart'; + +class AllAccountsScreen extends StatefulWidget { + final List users; + const AllAccountsScreen({super.key, required this.users}); + + @override + State createState() => _AllAccountsScreenState(); +} + +class _AllAccountsScreenState extends State { + final Map _visibilityMap = {}; + + String getFullAccountType(BuildContext context, String? accountType) { + // This is duplicated from dashboard_screen.dart. + // In a real app, this should be moved to a utility/helper class. + if (accountType == null || accountType.isEmpty) return 'N/A'; + switch (accountType.toLowerCase()) { + case 'sa': + return "Savings Account"; // Using hardcoded strings for simplicity + 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) { + return Scaffold( + appBar: AppBar( + title: const Text('All Accounts'), + ), + body: ListView.builder( + itemCount: widget.users.length, + itemBuilder: (context, index) { + final user = widget.users[index]; + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), + child: _buildAccountCard(user), + ); + }, + ), + ); + } + + Widget _buildAccountCard(User user) { + final theme = Theme.of(context); + final accountNo = user.accountNo ?? ''; + final isVisible = _visibilityMap[accountNo] ?? false; + + return Container( + padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 12), + decoration: BoxDecoration( + color: const Color(0xFF01A04C), + borderRadius: BorderRadius.circular(16), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Top section: Account Type and Number + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + getFullAccountType(context, 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, + ), + ), + ], + ), + const SizedBox(height: 16), + // Bottom section: Balance and Toggle + Row( + children: [ + Text( + "₹ ", + style: TextStyle( + color: theme.colorScheme.onPrimary, + fontSize: 32, + fontWeight: FontWeight.w700, + ), + ), + Expanded( + child: FittedBox( + fit: BoxFit.scaleDown, + alignment: Alignment.centerLeft, + child: Text( + isVisible ? user.currentBalance ?? '0.00' : '*****', + style: TextStyle( + color: theme.colorScheme.onPrimary, + fontSize: 32, + fontWeight: FontWeight.w700, + ), + ), + ), + ), + const Spacer(), + InkWell( + onTap: () { + setState(() { + _visibilityMap[accountNo] = !isVisible; + }); + }, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Icon( + isVisible ? Symbols.visibility_lock : Symbols.visibility, + color: theme.scaffoldBackgroundColor, + weight: 800, + ), + ), + ), + ], + ), + ], + ), + ); + } +} diff --git a/lib/features/dashboard/screens/dashboard_screen.dart b/lib/features/dashboard/screens/dashboard_screen.dart index 7d93790..745e9a2 100644 --- a/lib/features/dashboard/screens/dashboard_screen.dart +++ b/lib/features/dashboard/screens/dashboard_screen.dart @@ -7,6 +7,7 @@ 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/accounts/screens/all_accounts_screen.dart'; import 'package:kmobile/features/accounts/screens/transaction_details_screen.dart'; import 'package:kmobile/features/auth/controllers/auth_cubit.dart'; import 'package:kmobile/features/auth/controllers/auth_state.dart'; @@ -34,33 +35,97 @@ class DashboardScreen extends StatefulWidget { } class _DashboardScreenState extends State - with SingleTickerProviderStateMixin { + with SingleTickerProviderStateMixin, RouteAware { int selectedAccountIndex = 0; Map _visibilityMap = {}; bool isRefreshing = false; - bool isBalanceLoading = false; + bool _biometricPromptShown = false; bool _txLoading = false; List _transactions = []; 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 _buildViewAllTab(List users) { + return GestureDetector( + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => AllAccountsScreen(users: users), + ), + ); + }, + child: Container( + width: 40, // Small width for the tab + height: 160, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surfaceVariant, + ), + child: const Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + "View", + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 12, + ), + ), + Text( + "All", + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 12, + ), + ), + Icon( + Icons.arrow_forward, + size: 16, + ), + ], + ), + ), + ); + } + 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.0 : 0.9; + final scale = isSelected ? 1.0 : 0.85; return AnimatedScale( duration: const Duration(milliseconds: 200), scale: scale, - child: Container( - margin: const EdgeInsets.symmetric(horizontal: 8), + child: Transform.translate( + offset: isSelected ? const Offset(10.0, 0.0) : Offset.zero, + child: Container( + margin: const EdgeInsets.symmetric(horizontal: 2), padding: const EdgeInsets.symmetric( horizontal: 18, vertical: 10, @@ -72,6 +137,7 @@ class _DashboardScreenState extends State child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ + // Top section with account type and number (no refresh button here) Row( children: [ Expanded( @@ -91,69 +157,72 @@ class _DashboardScreenState extends State style: TextStyle( color: theme.colorScheme.onPrimary, fontSize: 14, - fontWeight: FontWeight.w700, // Changed to bold + fontWeight: FontWeight.w700, ), overflow: TextOverflow.ellipsis, ), ], ), ), - if (isSelected) // Only show refresh for selected card - IconButton( - icon: isRefreshing - ? SizedBox( - width: 20, - height: 20, - child: CircularProgressIndicator( - color: theme.colorScheme.onPrimary, - strokeWidth: 2, - ), - ) - : Icon( - Symbols.refresh, - color: theme.colorScheme.onPrimary, - ), - onPressed: isRefreshing - ? null - : () => _refreshAccountData(context), - tooltip: 'Refresh', + 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: [ - Text( - "₹ ", - style: TextStyle( - color: theme.colorScheme.onPrimary, - fontSize: 32, // Adjusted for space - fontWeight: FontWeight.w700, - ), - ), - if (isRefreshing || (isBalanceLoading && isSelected)) + if (isRefreshing && isSelected) Expanded(child: _buildBalanceShimmer()) else Expanded( child: FittedBox( fit: BoxFit.scaleDown, alignment: Alignment.centerLeft, - child: Text( - isCardVisible ? user.currentBalance ?? '0.00' : '*****', - style: TextStyle( - color: theme.colorScheme.onPrimary, - fontSize: 32, - fontWeight: FontWeight.w700, - ), + 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 Spacer(), + const SizedBox(width: 10), // A steady space if (isSelected) // Only show toggle for selected card InkWell( onTap: () async { - if (isBalanceLoading) return; + if (isRefreshing) return; // Prevent taps while refreshing final accountNo = user.accountNo; if (accountNo == null) return; @@ -161,18 +230,15 @@ class _DashboardScreenState extends State _visibilityMap[accountNo] ?? false; if (!currentVisibility) { - // If it's currently hidden, we show it after a delay - setState(() { - isBalanceLoading = true; - }); - await Future.delayed( - const Duration(milliseconds: 500)); // Shorter delay - setState(() { - _visibilityMap[accountNo] = true; - isBalanceLoading = false; - }); + // If hidden, refresh data and then show the balance + await _refreshAccountData(context); + if (mounted) { + setState(() { + _visibilityMap[accountNo] = true; + }); + } } else { - // If it's currently visible, we hide it immediately + // If visible, just hide it setState(() { _visibilityMap[accountNo] = false; }); @@ -195,6 +261,7 @@ class _DashboardScreenState extends State ], ), ), + ), ); } @@ -443,7 +510,7 @@ class _DashboardScreenState extends State } _pageController ??= PageController( initialPage: selectedAccountIndex, - viewportFraction: 0.85, + viewportFraction: 0.80, ); final firstName = getProcessedFirstName(currAccount.name); @@ -467,33 +534,53 @@ class _DashboardScreenState extends State const SizedBox(height: 16), // Account Info Cards - SizedBox( - height: 160, // Adjusted height - child: PageView.builder( - controller: _pageController, - itemCount: users.length, - onPageChanged: (int newIndex) async { - if (newIndex == selectedAccountIndex) return; + ClipRect( + child: SizedBox( // This SizedBox defines the height for the Stack + height: 160, + child: Stack( + children: [ + // PageView part, painted underneath + Padding( + padding: const EdgeInsets.only(left: 48.0), // Space for tab (40) + gap (8) + child: SizedBox( // Keep SizedBox for PageView height + height: 160, + child: PageView.builder( + controller: _pageController, + itemCount: users.length, + clipBehavior: Clip.none, // Keep this to show adjacent cards + padEnds: false, + 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; - } + // Hide the balance of the old card when scrolling away + final oldAccountNo = users[selectedAccountIndex].accountNo; + if (oldAccountNo != null) { + _visibilityMap[oldAccountNo] = false; + } - setState(() { - selectedAccountIndex = newIndex; - }); - - await _loadTransactions( - users[newIndex].accountNo!, - ); - }, - itemBuilder: (context, index) { - final user = users[index]; - final isSelected = index == selectedAccountIndex; - return _buildAccountCard(user, isSelected); - }, + setState(() { + selectedAccountIndex = newIndex; + }); + + await _loadTransactions( + users[newIndex].accountNo!, + ); + }, + itemBuilder: (context, index) { + final user = users[index]; + final isSelected = index == selectedAccountIndex; + return _buildAccountCard(user, isSelected); + }, + ), + ), + ), + // View All tab part, painted on top + Align( + alignment: Alignment.centerLeft, + child: _buildViewAllTab(users), + ), + ], + ), ), ), const SizedBox(height: 18),