api integration

This commit is contained in:
2025-06-02 10:29:32 +05:30
parent 7c9e089c62
commit 805aa5e015
289 changed files with 40017 additions and 1082 deletions

View File

@@ -1,21 +1,42 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:kmobile/features/customer_info/screens/customer_info_screen.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/auth/controllers/auth_state.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 StatelessWidget {
class KMobile extends StatefulWidget {
const KMobile({super.key});
@override
State<KMobile> createState() => _KMobileState();
}
class _KMobileState extends State<KMobile> {
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
@@ -26,90 +47,184 @@ class KMobile extends StatelessWidget {
),
);
if (_showSplash) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: SplashScreen(),
);
}
return MultiBlocProvider(
providers: [
BlocProvider<AuthCubit>(
create: (_) => getIt<AuthCubit>(),
),
// Add other Bloc/Cubit providers here
BlocProvider<AuthCubit>(create: (_) => getIt<AuthCubit>()),
],
child: MaterialApp(
title: 'Banking App',
title: 'kMobile',
debugShowCheckedModeBanner: false,
theme: AppThemes.lightTheme,
darkTheme: AppThemes.darkTheme,
themeMode: ThemeMode.system, // Use system theme by default
onGenerateRoute: AppRoutes.generateRoute,
initialRoute: AppRoutes.splash,
builder: (context, child) {
return MediaQuery(
// Prevent font scaling to maintain design consistency
data: MediaQuery.of(context)
.copyWith(textScaler: const TextScaler.linear(1.0)),
child: child!,
);
},
home: BlocBuilder<AuthCubit, AuthState>(
builder: (context, state) {
// Handle different authentication states
if (state is AuthInitial || state is AuthLoading) {
return const SplashScreen();
}
if(state is ShowBiometricPermission){
return const BiometricPermissionScreen();
}
if (state is Authenticated) {
return const NavigationScaffold();
}
return const LoginScreen();
},
),
home: const AuthGate(),
),
);
}
}
// Simple splash screens component
class SplashScreen extends StatelessWidget {
const SplashScreen();
class AuthGate extends StatefulWidget {
const AuthGate({super.key});
@override
State<AuthGate> createState() => _AuthGateState();
}
class _AuthGateState extends State<AuthGate> {
bool _checking = true;
bool _isLoggedIn = false;
bool _hasMPin = false;
bool _biometricEnabled = false;
bool _biometricTried = false;
@override
void initState() {
super.initState();
_checkAuth();
}
Future<void> _checkAuth() async {
final storage = getIt<SecureStorage>();
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<bool> _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) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Replace with your bank logo
Image.asset(
'assets/images/logo.png',
width: 150,
height: 150,
errorBuilder: (context, error, stackTrace) {
return const Icon(
Icons.account_balance,
size: 100,
color: Colors.blue,
);
},
),
const SizedBox(height: 24),
const Text(
'SecureBank',
style: TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
if (_checking) {
return const SplashScreen();
}
if (_isLoggedIn) {
if (_hasMPin) {
if (_biometricEnabled) {
return FutureBuilder<bool>(
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<SecureStorage>();
final localAuth = LocalAuthentication();
// 1) Prompt user to optin for biometric
final optIn = await showDialog<bool>(
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'),
),
],
),
),
const SizedBox(height: 16),
const CircularProgressIndicator(),
],
),
),
);
);
// 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();
}
}
@@ -125,10 +240,9 @@ class _NavigationScaffoldState extends State<NavigationScaffold> {
int _selectedIndex = 0;
final List<Widget> _pages = [
DashboardScreen(),
CardManagementScreen(),
ServiceScreen(),
CustomerInfoScreen()
const DashboardScreen(),
const CardManagementScreen(),
const ServiceScreen(),
];
void _onItemTapped(int index) {
@@ -140,59 +254,82 @@ class _NavigationScaffoldState extends State<NavigationScaffold> {
@override
Widget build(BuildContext context) {
return 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,
return PopScope(
canPop: false,
onPopInvokedWithResult: (didPop, result) async {
if (!didPop) {
final shouldExit = await showDialog<bool>(
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 BiometricPermissionScreen extends StatelessWidget {
const BiometricPermissionScreen({super.key});
class SplashScreen extends StatelessWidget {
const SplashScreen({super.key});
@override
Widget build(BuildContext context) {
final cubit = context.read<AuthCubit>();
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('Would you like to enable biometric authentication?'),
ElevatedButton(
onPressed: () => cubit.handleBiometricChoice(true),
child: const Text('Yes'),
),
TextButton(
onPressed: () => cubit.handleBiometricChoice(false),
child: const Text('No, thanks'),
),
const CircularProgressIndicator(),
const SizedBox(height: 20),
Text('Loading...',
style: Theme.of(context).textTheme.headlineMedium),
],
),
),
@@ -200,5 +337,70 @@ class BiometricPermissionScreen extends StatelessWidget {
}
}
// Add this widget at the end of the file
class BiometricPromptScreen extends StatelessWidget {
final VoidCallback onCompleted;
const BiometricPromptScreen({super.key, required this.onCompleted});
Future<void> _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<SecureStorage>();
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<void> _showDialog(BuildContext context) async {
final result = await showDialog<bool>(
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<SecureStorage>();
await storage.write('biometric_enabled', 'false');
onCompleted();
}
}
}