import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:kmobile/security/secure_storage.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import './l10n/app_localizations.dart'; import 'config/themes.dart'; import 'config/routes.dart'; import 'di/injection.dart'; import 'features/auth/controllers/auth_cubit.dart'; import 'features/card/screens/card_management_screen.dart'; import 'features/auth/screens/welcome_screen.dart'; import 'features/auth/screens/login_screen.dart'; import 'features/service/screens/service_screen.dart'; import 'features/dashboard/screens/dashboard_screen.dart'; import 'features/auth/screens/mpin_screen.dart'; import 'package:local_auth/local_auth.dart'; class KMobile extends StatefulWidget { const KMobile({super.key}); @override State createState() => _KMobileState(); static void setLocale(BuildContext context, Locale newLocale) { final _KMobileState? state = context .findAncestorStateOfType<_KMobileState>(); state?.setLocale(newLocale); } } class _KMobileState extends State { bool _showSplash = false; @override void initState() { super.initState(); // Simulate a splash screen delay Future.delayed(const Duration(seconds: 2), () { setState(() { _showSplash = false; }); }); } Locale? _locale; void setLocale(Locale locale) { setState(() { _locale = locale; }); } /* @override Widget build(BuildContext context) { // Set status bar color SystemChrome.setSystemUIOverlayStyle( const SystemUiOverlayStyle( statusBarColor: Colors.transparent, statusBarIconBrightness: Brightness.dark, ), ); if (_showSplash) { return MaterialApp( debugShowCheckedModeBanner: false, locale: _locale, supportedLocales: const [ Locale('en'), Locale('hi'), ], localizationsDelegates: const [ AppLocalizations.delegate, GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, GlobalCupertinoLocalizations.delegate, ], home: const SplashScreen(), ); } return MultiBlocProvider( providers: [ BlocProvider(create: (_) => getIt()), ], child: MaterialApp( title: 'kMobile', // debugShowCheckedModeBanner: false, theme: AppThemes.lightTheme, // darkTheme: AppThemes.darkTheme, themeMode: ThemeMode.system, // Use system theme by default onGenerateRoute: AppRoutes.generateRoute, initialRoute: AppRoutes.splash, home: const AuthGate(), ), ); } */ @override Widget build(BuildContext context) { // Set status bar color SystemChrome.setSystemUIOverlayStyle( const SystemUiOverlayStyle( statusBarColor: Colors.transparent, statusBarIconBrightness: Brightness.dark, ), ); return MultiBlocProvider( providers: [BlocProvider(create: (_) => getIt())], child: MaterialApp( debugShowCheckedModeBanner: false, locale: _locale, // Use your existing locale variable supportedLocales: const [Locale('en'), Locale('hi')], localizationsDelegates: const [ AppLocalizations.delegate, GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, GlobalCupertinoLocalizations.delegate, ], title: 'kMobile', theme: AppThemes.lightTheme, // darkTheme: AppThemes.darkTheme, themeMode: ThemeMode.system, // Use system theme by default onGenerateRoute: AppRoutes.generateRoute, initialRoute: AppRoutes.splash, home: _showSplash ? const SplashScreen() : const AuthGate(), ), ); } } class AuthGate extends StatefulWidget { const AuthGate({super.key}); @override State createState() => _AuthGateState(); } class _AuthGateState extends State { bool _checking = true; bool _isLoggedIn = false; bool _showWelcome = true; bool _hasMPin = false; bool _biometricEnabled = false; bool _biometricTried = false; @override void initState() { super.initState(); _checkAuth(); } Future _checkAuth() async { final storage = getIt(); final accessToken = await storage.read('access_token'); final accessTokenExpiry = await storage.read('token_expiry'); final mpin = await storage.read('mpin'); final biometric = await storage.read('biometric_enabled'); setState(() { _isLoggedIn = accessToken != null && accessTokenExpiry != null && DateTime.parse(accessTokenExpiry).isAfter(DateTime.now()); _hasMPin = mpin != null; _biometricEnabled = biometric == 'true'; _checking = false; }); } Future _tryBiometric() async { if (_biometricTried) return false; _biometricTried = true; final localAuth = LocalAuthentication(); final canCheck = await localAuth.canCheckBiometrics; if (!canCheck) return false; try { final didAuth = await localAuth.authenticate( localizedReason: AppLocalizations.of(context).authenticateToAccess, options: const AuthenticationOptions( stickyAuth: true, biometricOnly: true, ), ); return didAuth; } catch (_) { return false; } } /* @override Widget build(BuildContext context) { if (_checking) { return const SplashScreen(); } if (_isLoggedIn) { if (_hasMPin) { if (_biometricEnabled) { return FutureBuilder( future: _tryBiometric(), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return const SplashScreen(); } if (snapshot.data == true) { // Authenticated with biometrics, go to dashboard return const NavigationScaffold(); } // If not authenticated or user dismissed, show mPIN screen return MPinScreen( mode: MPinMode.enter, onCompleted: (_) { Navigator.of(context).pushReplacement( MaterialPageRoute( builder: (_) => const NavigationScaffold()), ); }, ); }, ); } else { return MPinScreen( mode: MPinMode.enter, onCompleted: (_) { Navigator.of(context).pushReplacement( MaterialPageRoute(builder: (_) => const NavigationScaffold()), ); }, ); } } else { return MPinScreen( mode: MPinMode.set, onCompleted: (_) async { final storage = getIt(); final localAuth = LocalAuthentication(); // 1) Prompt user to opt‐in for biometric final optIn = await showDialog( context: context, barrierDismissible: false, // force choice builder: (ctx) => AlertDialog( title: const Text('Enable Fingerprint Login?'), content: const Text( 'Would you like to enable fingerprint authentication for faster login?', ), actions: [ TextButton( onPressed: () => Navigator.of(ctx).pop(false), child: const Text('No'), ), TextButton( onPressed: () => Navigator.of(ctx).pop(true), child: const Text('Yes'), ), ], ), ); // 2) If opted in, perform biometric auth if (optIn == true) { final canCheck = await localAuth.canCheckBiometrics; bool didAuth = false; if (canCheck) { didAuth = await localAuth.authenticate( localizedReason: 'Authenticate to enable fingerprint login', options: const AuthenticationOptions( stickyAuth: true, biometricOnly: true, ), ); } await storage.write( 'biometric_enabled', didAuth ? 'true' : 'false'); } else { await storage.write('biometric_enabled', 'false'); } // 3) Finally go to your main scaffold Navigator.of(context).pushReplacement( MaterialPageRoute(builder: (_) => const NavigationScaffold()), ); }, ); } } return const LoginScreen(); } }*/ @override Widget build(BuildContext context) { if (_checking) { return const SplashScreen(); } // ✅ Step 1: Show welcome screen first, only once if (_showWelcome) { return WelcomeScreen( onContinue: () { setState(() { _showWelcome = false; }); }, ); } // ✅ Step 2: Check login status if (_isLoggedIn) { if (_hasMPin) { if (_biometricEnabled) { return FutureBuilder( future: _tryBiometric(), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return const SplashScreen(); } if (snapshot.data == true) { return const NavigationScaffold(); // Authenticated } // ❌ Biometric failed → Show MPIN screen return MPinScreen( mode: MPinMode.enter, onCompleted: (_) { Navigator.of(context).pushReplacement( MaterialPageRoute( builder: (_) => const NavigationScaffold(), ), ); }, ); }, ); } else { return MPinScreen( mode: MPinMode.enter, onCompleted: (_) { Navigator.of(context).pushReplacement( MaterialPageRoute( builder: (_) => const NavigationScaffold(), ), ); }, ); } } else { // No MPIN set → show MPIN set screen + biometric dialog return MPinScreen( mode: MPinMode.set, onCompleted: (_) async { final storage = getIt(); final localAuth = LocalAuthentication(); final optIn = await showDialog( context: context, barrierDismissible: false, builder: (ctx) => AlertDialog( title: Text(AppLocalizations.of(context).enableFingerprintLogin), content: Text(AppLocalizations.of(context).enableFingerprintMessage), actions: [ TextButton( onPressed: () => Navigator.of(ctx).pop(false), child: Text(AppLocalizations.of(context).no), ), TextButton( onPressed: () => Navigator.of(ctx).pop(true), child: Text(AppLocalizations.of(context).yes), ), ], ), ); if (optIn == true) { final canCheck = await localAuth.canCheckBiometrics; bool didAuth = false; if (canCheck) { didAuth = await localAuth.authenticate( localizedReason: AppLocalizations.of(context).authenticateToEnable, options: const AuthenticationOptions( stickyAuth: true, biometricOnly: true, ), ); await storage.write( 'biometric_enabled', didAuth ? 'true' : 'false'); } else { await storage.write('biometric_enabled', 'false'); } } Navigator.of(context).pushReplacement( MaterialPageRoute( builder: (_) => const NavigationScaffold(), ), ); }, ); } } // ✅ Step 3: If not logged in, show login screen return const LoginScreen(); } } /*@override Widget build(BuildContext context) { if (_checking) { return const SplashScreen(); } if (_isLoggedIn) { if (_hasMPin) { if (_biometricEnabled) { return FutureBuilder( future: _tryBiometric(), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return const SplashScreen(); } if (snapshot.data == true) { return const NavigationScaffold(); // Authenticated } // Failed or dismissed biometric → Show MPIN return MPinScreen( mode: MPinMode.enter, onCompleted: (_) { Navigator.of(context).pushReplacement( MaterialPageRoute( builder: (_) => const NavigationScaffold(), ), ); }, ); }, ); } else { return MPinScreen( mode: MPinMode.enter, onCompleted: (_) { Navigator.of(context).pushReplacement( MaterialPageRoute(builder: (_) => const NavigationScaffold()), ); }, ); } } else { return MPinScreen( mode: MPinMode.set, onCompleted: (_) async { final storage = getIt(); final localAuth = LocalAuthentication(); final optin = await showDialog( context: context, barrierDismissible: false, builder: (ctx) => AlertDialog( title: Text( AppLocalizations.of(context).enableFingerprintLogin, ), content: Text( AppLocalizations.of(context).enableFingerprintMessage, ), actions: [ TextButton( onPressed: () => Navigator.of(ctx).pop(false), child: Text(AppLocalizations.of(context).no), ), TextButton( onPressed: () => Navigator.of(ctx).pop(true), child: Text(AppLocalizations.of(context).yes), ), ], ), ); if (optin == true) { final canCheck = await localAuth.canCheckBiometrics; bool didAuth = false; if (canCheck) { didAuth = await localAuth.authenticate( localizedReason: AppLocalizations.of( context, ).authenticateToEnable, options: const AuthenticationOptions( stickyAuth: true, biometricOnly: true, ), ); } await storage.write( 'biometric_enabled', didAuth ? 'true' : 'false', ); } else { await storage.write('biometric_enabled', 'false'); } Navigator.of(context).pushReplacement( MaterialPageRoute(builder: (_) => const NavigationScaffold()), ); }, ); } } // 🔻 Show Welcome screen before login if not logged in if (_showWelcome) { return WelcomeScreen( onContinue: () { setState(() { _showWelcome = false; }); }, ); } return const LoginScreen(); } }*/ class NavigationScaffold extends StatefulWidget { const NavigationScaffold({super.key}); @override State createState() => _NavigationScaffoldState(); } class _NavigationScaffoldState extends State { final PageController _pageController = PageController(); int _selectedIndex = 0; final List _pages = [ const DashboardScreen(), const CardManagementScreen(), const ServiceScreen(), ]; void _onItemTapped(int index) { setState(() { _selectedIndex = index; }); _pageController.jumpToPage(index); } @override Widget build(BuildContext context) { return PopScope( canPop: false, onPopInvokedWithResult: (didPop, result) async { if (!didPop) { final shouldExit = await showDialog( context: context, builder: (context) => AlertDialog( title: Text(AppLocalizations.of(context).exitApp), content: Text(AppLocalizations.of(context).exitConfirmation), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(false), child: Text(AppLocalizations.of(context).no), ), TextButton( onPressed: () => Navigator.of(context).pop(true), child: Text(AppLocalizations.of(context).yes), ), ], ), ); if (shouldExit == true) { if (Platform.isAndroid) { SystemNavigator.pop(); } exit(0); } } }, child: Scaffold( body: PageView( controller: _pageController, physics: const NeverScrollableScrollPhysics(), children: _pages, ), bottomNavigationBar: BottomNavigationBar( currentIndex: _selectedIndex, type: BottomNavigationBarType.fixed, backgroundColor: const Color(0xFFE0F7FA), // Light blue background selectedItemColor: Colors.blue[800], unselectedItemColor: Colors.black54, onTap: _onItemTapped, items: [ BottomNavigationBarItem( icon: const Icon(Icons.home_filled), label: AppLocalizations.of(context).home, ), BottomNavigationBarItem( icon: const Icon(Icons.credit_card), label: AppLocalizations.of(context).card, ), BottomNavigationBarItem( icon: const Icon(Icons.miscellaneous_services), label: AppLocalizations.of(context).services, ), ], ), ), ); } } class SplashScreen extends StatelessWidget { const SplashScreen({super.key}); @override Widget build(BuildContext context) { return Scaffold( body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const CircularProgressIndicator(), const SizedBox(height: 20), Text( AppLocalizations.of(context).loading, style: Theme.of(context).textTheme.headlineMedium, ), ], ), ), ); } } // Add this widget at the end of the file class BiometricPromptScreen extends StatelessWidget { final VoidCallback onCompleted; const BiometricPromptScreen({super.key, required this.onCompleted}); Future _handleBiometric(BuildContext context) async { final localAuth = LocalAuthentication(); final canCheck = await localAuth.canCheckBiometrics; if (!canCheck) { onCompleted(); return; } final didAuth = await localAuth.authenticate( localizedReason: AppLocalizations.of(context).enableFingerprintQuick, options: const AuthenticationOptions( stickyAuth: true, biometricOnly: true, ), ); final storage = getIt(); if (didAuth) { await storage.write('biometric_enabled', 'true'); } else { await storage.write('biometric_enabled', 'false'); } onCompleted(); } @override Widget build(BuildContext context) { Future.microtask(() => _showDialog(context)); return const SplashScreen(); } Future _showDialog(BuildContext context) async { final result = await showDialog( context: context, barrierDismissible: true, builder: (ctx) => AlertDialog( title: Text(AppLocalizations.of(context).enableFingerprintLogin), content: Text(AppLocalizations.of(context).enableFingerprintMessage), actions: [ TextButton( onPressed: () { Navigator.of(ctx).pop(false); }, child: Text(AppLocalizations.of(context).no), ), TextButton( onPressed: () { Navigator.of(ctx).pop(true); }, child: Text(AppLocalizations.of(context).yes), ), ], ), ); if (result == true) { await _handleBiometric(context); } else { final storage = getIt(); await storage.write('biometric_enabled', 'false'); onCompleted(); } } }