Add fingerprint (local_auth) session + 3-min inactivity timeout (default), activity resets timer, settings: biometric toggle + timeout picker
This commit is contained in:
@@ -4,7 +4,8 @@ import 'package:flutter/services.dart';
|
||||
import 'package:login_vault_app/auth_service.dart';
|
||||
import 'package:login_vault_app/theme_provider.dart';
|
||||
|
||||
/// Settings: change app password, switch theme (dark/system), and Exit.
|
||||
/// Settings: change app password, switch theme (dark/system), session timeout,
|
||||
/// fingerprint toggle, and Exit.
|
||||
class SettingsScreen extends StatefulWidget {
|
||||
final AuthService auth;
|
||||
final ThemeProvider theme;
|
||||
@@ -18,6 +19,30 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
final _oldCtrl = TextEditingController();
|
||||
final _newCtrl = TextEditingController();
|
||||
String? _msg;
|
||||
bool _biometricOn = false;
|
||||
bool _biometricAvailable = false;
|
||||
int _timeout = 3;
|
||||
|
||||
final List<int> _timeoutOptions = [3, 5, 10, 15, 30, 60, 0];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadSettings();
|
||||
}
|
||||
|
||||
Future<void> _loadSettings() async {
|
||||
final on = await widget.auth.isBiometricEnabled();
|
||||
final avail = await widget.auth.canUseBiometrics();
|
||||
final t = await widget.auth.getTimeoutMinutes();
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_biometricOn = on && avail;
|
||||
_biometricAvailable = avail;
|
||||
_timeout = t;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _changePassword() async {
|
||||
final oldP = _oldCtrl.text;
|
||||
@@ -37,6 +62,31 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
_newCtrl.clear();
|
||||
}
|
||||
|
||||
Future<void> _toggleBiometric(bool value) async {
|
||||
if (value && !_biometricAvailable) {
|
||||
setState(() => _msg = 'Perangkat tidak mendukung sidik jari');
|
||||
return;
|
||||
}
|
||||
if (value) {
|
||||
final ok = await widget.auth.authenticateWithBiometrics();
|
||||
if (!ok) {
|
||||
setState(() => _msg = 'Verifikasi sidik jari gagal');
|
||||
return;
|
||||
}
|
||||
}
|
||||
await widget.auth.setBiometricEnabled(value);
|
||||
if (mounted) setState(() => _biometricOn = value);
|
||||
}
|
||||
|
||||
Future<void> _setTimeout(int? value) async {
|
||||
if (value == null) return;
|
||||
await widget.auth.setTimeoutMinutes(value);
|
||||
if (mounted) setState(() => _timeout = value);
|
||||
widget.auth.openSession(); // activity
|
||||
}
|
||||
|
||||
String _timeoutLabel(int m) => m == 0 ? 'Selamanya' : '$m menit';
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
@@ -44,8 +94,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
body: ListView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: [
|
||||
const Text('Tema',
|
||||
style: TextStyle(fontWeight: FontWeight.bold)),
|
||||
const Text('Tema', style: TextStyle(fontWeight: FontWeight.bold)),
|
||||
ListTile(
|
||||
title: const Text('Gelap (Dark)'),
|
||||
leading: const Icon(Icons.dark_mode),
|
||||
@@ -67,6 +116,27 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
onTap: () => widget.theme.setMode(ThemeMode.system),
|
||||
),
|
||||
const Divider(),
|
||||
const Text('Session', style: TextStyle(fontWeight: FontWeight.bold)),
|
||||
ListTile(
|
||||
title: const Text('Timeout (tanpa aktivitas)'),
|
||||
subtitle: Text('Logout otomatis setelah: ${_timeoutLabel(_timeout)}'),
|
||||
trailing: DropdownButton<int>(
|
||||
value: _timeout,
|
||||
items: _timeoutOptions
|
||||
.map((m) => DropdownMenuItem(value: m, child: Text(_timeoutLabel(m))))
|
||||
.toList(),
|
||||
onChanged: _setTimeout,
|
||||
),
|
||||
),
|
||||
SwitchListTile(
|
||||
title: const Text('Login dengan sidik jari'),
|
||||
subtitle: Text(_biometricAvailable
|
||||
? 'Gunakan fingerprint untuk membuka app'
|
||||
: 'Perangkat tidak mendukung biometrik'),
|
||||
value: _biometricOn,
|
||||
onChanged: _toggleBiometric,
|
||||
),
|
||||
const Divider(),
|
||||
const Text('Ubah Password Aplikasi',
|
||||
style: TextStyle(fontWeight: FontWeight.bold)),
|
||||
TextField(
|
||||
@@ -87,15 +157,14 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
if (_msg != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8),
|
||||
child: Text(_msg!,
|
||||
style: const TextStyle(color: Colors.green)),
|
||||
child: Text(_msg!, style: const TextStyle(color: Colors.green)),
|
||||
),
|
||||
const Divider(),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.exit_to_app),
|
||||
title: const Text('Keluar (Exit)'),
|
||||
onTap: () {
|
||||
// On Android, pop the whole stack then ask the system to close.
|
||||
onTap: () async {
|
||||
await widget.auth.closeSession();
|
||||
Navigator.of(context).popUntil((r) => r.isFirst);
|
||||
if (Platform.isAndroid) {
|
||||
SystemNavigator.pop();
|
||||
|
||||
Reference in New Issue
Block a user