Files
login-vault-android/lib/auth_screen.dart

156 lines
5.1 KiB
Dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:login_vault_app/auth_service.dart';
import 'package:login_vault_app/dashboard_screen.dart';
import 'package:login_vault_app/theme_provider.dart';
/// First screen: register (if not yet), or re-authenticate (if registered).
/// If a valid session exists, skip straight to the Dashboard.
class AuthScreen extends StatefulWidget {
final AuthService auth;
const AuthScreen({super.key, required this.auth});
@override
State<AuthScreen> createState() => _AuthScreenState();
}
class _AuthScreenState extends State<AuthScreen> {
final _emailCtrl = TextEditingController();
final _passCtrl = TextEditingController();
bool _isLogin = true;
bool _busy = false;
String? _error;
@override
void initState() {
super.initState();
_initMode();
}
/// Decide login vs register based on whether an account exists.
Future<void> _initMode() async {
final registered = await widget.auth.isRegistered();
if (!mounted) return;
setState(() => _isLogin = registered);
// If already registered and biometric is enabled, prompt fingerprint
// automatically (unless a valid session already skips to dashboard).
if (registered && await widget.auth.isBiometricEnabled()) {
final timeout = await widget.auth.getTimeoutMinutes();
final valid = await widget.auth.hasValidSession(timeout);
if (!valid) {
_promptBiometricOnOpen();
return; // skip silent-auth path; biometric handles entry
}
}
_trySilentAuth();
}
/// Auto-prompt fingerprint on open when enabled; on success go to dashboard
/// (email may be empty for google-less accounts, fallback to stored email).
Future<void> _promptBiometricOnOpen() async {
final (ok, err) = await widget.auth.authenticateWithBiometrics();
if (!mounted) return;
if (ok) {
final email = await widget.auth.getEmail() ?? '';
_goToDashboard(email);
}
// on failure, the auth screen stays; user can tap Masuk manually
}
/// On open: if a valid session is present, go straight to Dashboard.
Future<void> _trySilentAuth() async {
final timeout = await widget.auth.getTimeoutMinutes();
final valid = await widget.auth.hasValidSession(timeout);
if (valid && mounted) {
final email = await widget.auth.getEmail() ?? '';
_goToDashboard(email);
}
}
void _goToDashboard(String email) {
if (!mounted) return;
widget.auth.openSession(); // refresh session timestamp
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (_) => DashboardScreen(
auth: widget.auth,
theme: Provider.of<ThemeProvider>(context, listen: false),
email: email,
),
),
);
}
Future<void> _submit() async {
setState(() => _busy = true);
final email = _emailCtrl.text.trim();
final pass = _passCtrl.text;
if (email.isEmpty || pass.isEmpty) {
setState(() => _error = 'Email & password wajib diisi');
_busy = false;
return;
}
try {
if (_isLogin) {
final stored = await widget.auth.getAppPassword();
final ok = await widget.auth.verifyPassword(pass);
if (stored == null) {
setState(() => _error = 'Belum terdaftar, silakan registrasi');
} else if (!ok) {
setState(() => _error = 'Password aplikasi salah');
} else {
_goToDashboard(email);
}
} else {
await widget.auth.registerWithEmail(email, pass);
_goToDashboard(email);
}
} catch (e) {
setState(() => _error = e.toString());
} finally {
if (mounted) setState(() => _busy = false);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Login Vault')),
body: Padding(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(_isLogin ? 'Masuk' : 'Daftar',
style: Theme.of(context).textTheme.headlineSmall),
const SizedBox(height: 16),
TextField(
controller: _emailCtrl,
keyboardType: TextInputType.emailAddress,
decoration: const InputDecoration(
labelText: 'Email', border: OutlineInputBorder()),
),
const SizedBox(height: 12),
TextField(
controller: _passCtrl,
obscureText: true,
decoration: const InputDecoration(
labelText: 'Password aplikasi', border: OutlineInputBorder()),
),
const SizedBox(height: 8),
if (_error != null)
Text(_error!, style: const TextStyle(color: Colors.red)),
const SizedBox(height: 12),
_busy
? const CircularProgressIndicator()
: ElevatedButton(
onPressed: _submit,
child: Text(_isLogin ? 'Masuk' : 'Daftar'),
),
],
),
),
);
}
}