mirror of
https://github.com/Dvorinka/1356.git
synced 2026-06-04 20:12:56 +00:00
348 lines
13 KiB
Dart
348 lines
13 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import '../../../core/widgets/app_scaffold.dart';
|
|
import '../../../data/services/biometric_service.dart';
|
|
import '../../auth/application/auth_controller.dart';
|
|
import 'package:local_auth/local_auth.dart' as local_auth;
|
|
|
|
class BiometricSettingsScreen extends ConsumerStatefulWidget {
|
|
const BiometricSettingsScreen({super.key});
|
|
|
|
@override
|
|
ConsumerState<BiometricSettingsScreen> createState() => _BiometricSettingsScreenState();
|
|
}
|
|
|
|
class _BiometricSettingsScreenState extends ConsumerState<BiometricSettingsScreen> {
|
|
final BiometricService _biometricService = BiometricService();
|
|
bool _isLoading = false;
|
|
BiometricAvailability? _availability;
|
|
bool _isEnabled = false;
|
|
String _statusMessage = '';
|
|
List<local_auth.BiometricType> _availableBiometrics = [];
|
|
local_auth.BiometricType? _primaryBiometricType;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_loadBiometricStatus();
|
|
}
|
|
|
|
Future<void> _loadBiometricStatus() async {
|
|
setState(() => _isLoading = true);
|
|
try {
|
|
final availability = await _biometricService.checkAvailability();
|
|
final isEnabled = await _biometricService.isBiometricEnabled();
|
|
final statusMessage = await _biometricService.getBiometricStatusMessage();
|
|
final availableBiometrics = await _biometricService.getAvailableBiometrics();
|
|
final primaryBiometricType = await _biometricService.getPrimaryBiometricType();
|
|
|
|
setState(() {
|
|
_availability = availability;
|
|
_isEnabled = isEnabled;
|
|
_statusMessage = statusMessage;
|
|
_availableBiometrics = availableBiometrics;
|
|
_primaryBiometricType = primaryBiometricType;
|
|
_isLoading = false;
|
|
});
|
|
} catch (e) {
|
|
setState(() => _isLoading = false);
|
|
}
|
|
}
|
|
|
|
Future<void> _toggleBiometric() async {
|
|
if (_availability != BiometricAvailability.available) {
|
|
return;
|
|
}
|
|
|
|
setState(() => _isLoading = true);
|
|
try {
|
|
final authController = ref.read(authControllerProvider.notifier);
|
|
|
|
if (_isEnabled) {
|
|
// Disable biometric
|
|
final success = await authController.disableBiometric();
|
|
if (success) {
|
|
setState(() => _isEnabled = false);
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(content: Text('Biometric login disabled')),
|
|
);
|
|
} else {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(content: Text('Failed to disable biometric login')),
|
|
);
|
|
}
|
|
} else {
|
|
// Enable biometric
|
|
final success = await authController.enableBiometric();
|
|
if (success) {
|
|
setState(() => _isEnabled = true);
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(content: Text('Biometric login enabled successfully')),
|
|
);
|
|
} else {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(content: Text('Failed to enable biometric login')),
|
|
);
|
|
}
|
|
}
|
|
} catch (e) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(content: Text('Error: ${e.toString()}')),
|
|
);
|
|
} finally {
|
|
setState(() => _isLoading = false);
|
|
}
|
|
}
|
|
|
|
Future<void> _testBiometric() async {
|
|
if (!_isEnabled) return;
|
|
|
|
setState(() => _isLoading = true);
|
|
try {
|
|
final authController = ref.read(authControllerProvider.notifier);
|
|
final success = await authController.signInWithBiometric();
|
|
|
|
if (success) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(content: Text('Biometric login successful')),
|
|
);
|
|
} else {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(content: Text('Biometric login failed')),
|
|
);
|
|
}
|
|
} catch (e) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(content: Text('Error: ${e.toString()}')),
|
|
);
|
|
} finally {
|
|
setState(() => _isLoading = false);
|
|
}
|
|
}
|
|
|
|
String _getBiometricTypeEmoji(local_auth.BiometricType type) {
|
|
switch (type) {
|
|
case local_auth.BiometricType.fingerprint:
|
|
return '👆';
|
|
case local_auth.BiometricType.face:
|
|
return '👤';
|
|
case local_auth.BiometricType.iris:
|
|
return '👁️';
|
|
default:
|
|
return '🔒';
|
|
}
|
|
}
|
|
|
|
String _getBiometricTypeName(local_auth.BiometricType type) {
|
|
switch (type) {
|
|
case local_auth.BiometricType.fingerprint:
|
|
return 'Fingerprint';
|
|
case local_auth.BiometricType.face:
|
|
return 'Face ID';
|
|
case local_auth.BiometricType.iris:
|
|
return 'Iris Scanner';
|
|
default:
|
|
return 'Biometric';
|
|
}
|
|
}
|
|
|
|
Color _getStatusColor() {
|
|
switch (_availability) {
|
|
case BiometricAvailability.available:
|
|
return _isEnabled ? Colors.green : Colors.orange;
|
|
case BiometricAvailability.notAvailable:
|
|
return Colors.grey;
|
|
case BiometricAvailability.notEnrolled:
|
|
return Colors.orange;
|
|
case BiometricAvailability.lockedOut:
|
|
return Colors.red;
|
|
case BiometricAvailability.permanentlyUnavailable:
|
|
return Colors.red;
|
|
case null:
|
|
return Colors.grey;
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return AppScaffold(
|
|
title: 'Biometric Login',
|
|
body: _isLoading
|
|
? const Center(child: CircularProgressIndicator())
|
|
: ListView(
|
|
padding: const EdgeInsets.all(16),
|
|
children: [
|
|
// Status Card
|
|
Card(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Icon(
|
|
_primaryBiometricType != null
|
|
? Icons.fingerprint
|
|
: Icons.lock,
|
|
size: 32,
|
|
color: _getStatusColor(),
|
|
),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Biometric Status',
|
|
style: Theme.of(context).textTheme.titleMedium,
|
|
),
|
|
Text(
|
|
_statusMessage,
|
|
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
|
color: _getStatusColor(),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 16),
|
|
if (_availability == BiometricAvailability.available)
|
|
SwitchListTile(
|
|
title: const Text('Enable Biometric Login'),
|
|
subtitle: const Text('Use fingerprint or face ID for quick access'),
|
|
value: _isEnabled,
|
|
onChanged: (value) => _toggleBiometric(),
|
|
secondary: Icon(
|
|
_isEnabled ? Icons.lock_open : Icons.lock,
|
|
color: _getStatusColor(),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 16),
|
|
|
|
// Available Biometrics
|
|
if (_availableBiometrics.isNotEmpty)
|
|
Card(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Available Biometrics',
|
|
style: Theme.of(context).textTheme.titleMedium,
|
|
),
|
|
const SizedBox(height: 12),
|
|
..._availableBiometrics.map((type) => Padding(
|
|
padding: const EdgeInsets.only(bottom: 8),
|
|
child: Row(
|
|
children: [
|
|
Text(
|
|
_getBiometricTypeEmoji(type),
|
|
style: const TextStyle(fontSize: 24),
|
|
),
|
|
const SizedBox(width: 12),
|
|
Text(
|
|
_getBiometricTypeName(type),
|
|
style: Theme.of(context).textTheme.bodyLarge,
|
|
),
|
|
const Spacer(),
|
|
if (type == _primaryBiometricType)
|
|
Container(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 8,
|
|
vertical: 4,
|
|
),
|
|
decoration: BoxDecoration(
|
|
color: Theme.of(context).colorScheme.primary,
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
child: Text(
|
|
'Primary',
|
|
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
|
color: Theme.of(context).colorScheme.onPrimary,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
)),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 16),
|
|
|
|
// Test Biometric (if enabled)
|
|
if (_isEnabled)
|
|
Card(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Test Biometric Login',
|
|
style: Theme.of(context).textTheme.titleMedium,
|
|
),
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
'Test your biometric authentication to make sure it\'s working properly.',
|
|
style: Theme.of(context).textTheme.bodySmall,
|
|
),
|
|
const SizedBox(height: 16),
|
|
SizedBox(
|
|
width: double.infinity,
|
|
child: ElevatedButton.icon(
|
|
onPressed: _testBiometric,
|
|
icon: const Icon(Icons.fingerprint),
|
|
label: const Text('Test Biometric Login'),
|
|
style: ElevatedButton.styleFrom(
|
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 16),
|
|
|
|
// Information Card
|
|
Card(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'About Biometric Login',
|
|
style: Theme.of(context).textTheme.titleMedium,
|
|
),
|
|
const SizedBox(height: 12),
|
|
Text(
|
|
'• Biometric login allows you to sign in quickly using your fingerprint or face ID.\n'
|
|
'• Your biometric data is stored securely on your device and never sent to our servers.\n'
|
|
'• You can disable biometric login at any time in these settings.\n'
|
|
'• If you change your password, you may need to re-enable biometric login.',
|
|
style: Theme.of(context).textTheme.bodySmall,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|