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 '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/login_screen.dart'; import 'features/service/screens/service_screen.dart'; import 'features/dashboard/screens/dashboard_screen.dart'; import 'package:material_symbols_icons/material_symbols_icons.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(); } 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; }); }); } @override Widget build(BuildContext context) { // Set status bar color SystemChrome.setSystemUIOverlayStyle( const SystemUiOverlayStyle( statusBarColor: Colors.transparent, statusBarIconBrightness: Brightness.dark, ), ); if (_showSplash) { return const MaterialApp( debugShowCheckedModeBanner: false, home: 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(), ), ); } } class AuthGate extends StatefulWidget { const AuthGate({super.key}); @override State createState() => _AuthGateState(); } class _AuthGateState extends State { bool _checking = true; bool _isLoggedIn = false; 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: 'Authenticate to access kMobile', 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(); } } 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: const Text('Exit App'), content: const Text('Do you really want to exit?'), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(false), child: const Text('No'), ), TextButton( onPressed: () => Navigator.of(context).pop(true), child: const Text('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, items: const [ BottomNavigationBarItem( icon: Icon(Icons.home_filled), label: 'Home', ), BottomNavigationBarItem( icon: Icon(Symbols.credit_card), label: 'Card', ), BottomNavigationBarItem( icon: Icon(Symbols.concierge), label: 'Services', ), ], onTap: _onItemTapped, backgroundColor: const Color(0xFFE0F7FA), // Light blue background selectedItemColor: Colors.blue[800], unselectedItemColor: Colors.black54, ), ), ); } } 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('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: 'Enable fingerprint authentication for quick login?', 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: 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'), ), ], ), ); if (result == true) { await _handleBiometric(context); } else { final storage = getIt(); await storage.write('biometric_enabled', 'false'); onCompleted(); } } }