mirror of
https://github.com/Dvorinka/1356.git
synced 2026-06-04 20:12:56 +00:00
small fix, don't worry about it
This commit is contained in:
@@ -202,13 +202,14 @@ class AboutChallengeScreen extends StatelessWidget {
|
||||
|
||||
Future<void> _openLink(BuildContext context, String url) async {
|
||||
final uri = Uri.parse(url);
|
||||
final scaffoldMessenger = ScaffoldMessenger.of(context);
|
||||
final launched = await launchUrl(
|
||||
uri,
|
||||
mode: LaunchMode.externalApplication,
|
||||
);
|
||||
|
||||
if (!launched) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
scaffoldMessenger.showSnackBar(
|
||||
const SnackBar(content: Text('Could not open link')),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,347 @@
|
||||
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,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:supabase_flutter/supabase_flutter.dart' as supabase;
|
||||
import '../../../bootstrap/supabase_client.dart';
|
||||
import '../../../core/widgets/app_scaffold.dart';
|
||||
|
||||
class SettingsHomeScreen extends ConsumerWidget {
|
||||
@@ -10,6 +10,7 @@ class SettingsHomeScreen extends ConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return AppScaffold(
|
||||
title: 'Settings',
|
||||
body: ListView(
|
||||
children: [
|
||||
_buildSection(
|
||||
@@ -25,7 +26,7 @@ class SettingsHomeScreen extends ConsumerWidget {
|
||||
_SettingsTile(
|
||||
icon: Icons.email,
|
||||
title: 'Email',
|
||||
subtitle: supabase.Supabase.instance.client.auth.currentUser?.email ?? '',
|
||||
subtitle: currentSupabaseUserEmail ?? 'Not signed in',
|
||||
onTap: () => context.push('/settings/account'),
|
||||
),
|
||||
_SettingsTile(
|
||||
@@ -64,6 +65,12 @@ class SettingsHomeScreen extends ConsumerWidget {
|
||||
subtitle: 'Public or Private profile',
|
||||
onTap: () => context.push('/settings/privacy'),
|
||||
),
|
||||
_SettingsTile(
|
||||
icon: Icons.fingerprint,
|
||||
title: 'Biometric Login',
|
||||
subtitle: 'Use fingerprint or face ID for quick access',
|
||||
onTap: () => context.push('/settings/biometric'),
|
||||
),
|
||||
_SettingsTile(
|
||||
icon: Icons.block,
|
||||
title: 'Blocked Users',
|
||||
|
||||
Reference in New Issue
Block a user