biometricPermission&Authentication
This commit is contained in:
parent
3108093686
commit
8fb84abe83
@ -82,39 +82,4 @@ class AuthService {
|
|||||||
throw AuthException('Error fetching user profile: ${e.toString()}');
|
throw AuthException('Error fetching user profile: ${e.toString()}');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<void> authenticateWithBiometrics(BuildContext context) async {
|
|
||||||
final LocalAuthentication localAuth = LocalAuthentication();
|
|
||||||
try {
|
|
||||||
bool isBiometricAvailable = await localAuth.canCheckBiometrics;
|
|
||||||
bool isAuthenticated = false;
|
|
||||||
|
|
||||||
if (isBiometricAvailable) {
|
|
||||||
isAuthenticated = await localAuth.authenticate(
|
|
||||||
localizedReason: 'Touch the fingerprint sensor',
|
|
||||||
options: const AuthenticationOptions(
|
|
||||||
biometricOnly: true,
|
|
||||||
stickyAuth: true,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isAuthenticated) {
|
|
||||||
// Navigate to Dashboard
|
|
||||||
Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(builder: (context) => const NavigationScaffold()),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// Show error/snack bar
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
const SnackBar(content: Text("Authentication failed")),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
if (kDebugMode) {
|
|
||||||
print("Biometric error: $e");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
36
lib/app.dart
36
lib/app.dart
@ -2,8 +2,11 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:kmobile/features/auth/screens/customer_info_screen.dart';
|
import 'package:kmobile/features/auth/screens/customer_info_screen.dart';
|
||||||
|
import 'package:kmobile/security/secure_storage.dart';
|
||||||
|
import 'api/services/auth_service.dart';
|
||||||
import 'config/themes.dart';
|
import 'config/themes.dart';
|
||||||
import 'config/routes.dart';
|
import 'config/routes.dart';
|
||||||
|
import 'data/repositories/auth_repository.dart';
|
||||||
import 'di/injection.dart';
|
import 'di/injection.dart';
|
||||||
import 'features/auth/controllers/auth_cubit.dart';
|
import 'features/auth/controllers/auth_cubit.dart';
|
||||||
import 'features/auth/controllers/auth_state.dart';
|
import 'features/auth/controllers/auth_state.dart';
|
||||||
@ -56,6 +59,10 @@ class KMobile extends StatelessWidget {
|
|||||||
return const _SplashScreen();
|
return const _SplashScreen();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(state is ShowBiometricPermission){
|
||||||
|
return const BiometricPermissionScreen();
|
||||||
|
}
|
||||||
|
|
||||||
if (state is Authenticated) {
|
if (state is Authenticated) {
|
||||||
return const NavigationScaffold();
|
return const NavigationScaffold();
|
||||||
}
|
}
|
||||||
@ -168,4 +175,33 @@ class _NavigationScaffoldState extends State<NavigationScaffold> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class BiometricPermissionScreen extends StatelessWidget {
|
||||||
|
const BiometricPermissionScreen({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'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,4 +1,10 @@
|
|||||||
import 'package:bloc/bloc.dart';
|
import 'package:bloc/bloc.dart';
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:local_auth/local_auth.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import '../../../app.dart';
|
||||||
import '../../../data/repositories/auth_repository.dart';
|
import '../../../data/repositories/auth_repository.dart';
|
||||||
import 'auth_state.dart';
|
import 'auth_state.dart';
|
||||||
|
|
||||||
@ -47,4 +53,60 @@ class AuthCubit extends Cubit<AuthState> {
|
|||||||
emit(AuthError(e.toString()));
|
emit(AuthError(e.toString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> checkFirstLaunch() async {
|
||||||
|
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||||
|
final isFirstLaunch = prefs.getBool('isFirstLaunch') ?? true;
|
||||||
|
|
||||||
|
if (isFirstLaunch) {
|
||||||
|
emit(ShowBiometricPermission());
|
||||||
|
} else {
|
||||||
|
// Continue to authentication logic (e.g., check token)
|
||||||
|
emit(AuthLoading()); // or Unauthenticated/Authenticated
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> handleBiometricChoice(bool enabled) async {
|
||||||
|
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||||
|
await prefs.setBool('biometric_opt_in', enabled);
|
||||||
|
await prefs.setBool('isFirstLaunch', false);
|
||||||
|
|
||||||
|
// Then continue to auth logic or home
|
||||||
|
if (enabled) {
|
||||||
|
authenticateBiometric(); // implement biometric logic
|
||||||
|
} else {
|
||||||
|
emit(Unauthenticated());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> authenticateBiometric() async {
|
||||||
|
final LocalAuthentication auth = LocalAuthentication();
|
||||||
|
|
||||||
|
try {
|
||||||
|
final isAvailable = await auth.canCheckBiometrics;
|
||||||
|
final isDeviceSupported = await auth.isDeviceSupported();
|
||||||
|
|
||||||
|
if (isAvailable && isDeviceSupported) {
|
||||||
|
final authenticated = await auth.authenticate(
|
||||||
|
localizedReason: 'Touch the fingerprint sensor',
|
||||||
|
options: const AuthenticationOptions(
|
||||||
|
biometricOnly: true,
|
||||||
|
stickyAuth: true,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (authenticated) {
|
||||||
|
// Continue to normal auth logic (e.g., auto login)
|
||||||
|
emit(AuthLoading());
|
||||||
|
await checkAuthStatus(); // Your existing method to verify token/session
|
||||||
|
} else {
|
||||||
|
emit(Unauthenticated());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
emit(Unauthenticated());
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
emit(Unauthenticated());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,8 @@ class AuthInitial extends AuthState {}
|
|||||||
|
|
||||||
class AuthLoading extends AuthState {}
|
class AuthLoading extends AuthState {}
|
||||||
|
|
||||||
|
class ShowBiometricPermission extends AuthState {}
|
||||||
|
|
||||||
class Authenticated extends AuthState {
|
class Authenticated extends AuthState {
|
||||||
final User user;
|
final User user;
|
||||||
|
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:kmobile/features/auth/controllers/auth_cubit.dart';
|
||||||
import 'package:material_symbols_icons/material_symbols_icons.dart';
|
import 'package:material_symbols_icons/material_symbols_icons.dart';
|
||||||
|
|
||||||
import '../../../app.dart';
|
import '../../../app.dart';
|
||||||
import '../../dashboard/screens/dashboard_screen.dart';
|
|
||||||
import '../../../api/services/auth_service.dart';
|
|
||||||
import 'package:material_symbols_icons/get.dart';
|
|
||||||
|
|
||||||
class MPinScreen extends StatefulWidget {
|
class MPinScreen extends StatefulWidget {
|
||||||
const MPinScreen({super.key});
|
const MPinScreen({super.key});
|
||||||
@ -112,6 +110,8 @@ class MPinScreenState extends State<MPinScreen> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final cubit = context.read<AuthCubit>();
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
child: Column(
|
child: Column(
|
||||||
@ -131,7 +131,7 @@ class MPinScreenState extends State<MPinScreen> {
|
|||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
TextButton(onPressed: () {
|
TextButton(onPressed: () {
|
||||||
AuthService.authenticateWithBiometrics(context);
|
cubit.authenticateBiometric();
|
||||||
}, child: const Text("Try another way")),
|
}, child: const Text("Try another way")),
|
||||||
TextButton(onPressed: () {}, child: const Text("Register?")),
|
TextButton(onPressed: () {}, child: const Text("Register?")),
|
||||||
],
|
],
|
||||||
|
56
pubspec.lock
56
pubspec.lock
@ -472,6 +472,62 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.1.4"
|
version: "6.1.4"
|
||||||
|
shared_preferences:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: shared_preferences
|
||||||
|
sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.5.3"
|
||||||
|
shared_preferences_android:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_android
|
||||||
|
sha256: "20cbd561f743a342c76c151d6ddb93a9ce6005751e7aa458baad3858bfbfb6ac"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.10"
|
||||||
|
shared_preferences_foundation:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_foundation
|
||||||
|
sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.5.4"
|
||||||
|
shared_preferences_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_linux
|
||||||
|
sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.1"
|
||||||
|
shared_preferences_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_platform_interface
|
||||||
|
sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.1"
|
||||||
|
shared_preferences_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_web
|
||||||
|
sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.3"
|
||||||
|
shared_preferences_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_windows
|
||||||
|
sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.1"
|
||||||
sky_engine:
|
sky_engine:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
|
@ -45,6 +45,7 @@ dependencies:
|
|||||||
flutter_svg: ^2.1.0
|
flutter_svg: ^2.1.0
|
||||||
local_auth: ^2.3.0
|
local_auth: ^2.3.0
|
||||||
material_symbols_icons: ^4.2815.0
|
material_symbols_icons: ^4.2815.0
|
||||||
|
shared_preferences: ^2.5.3
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user