Flutter Android login vault: local DB (id+base64 content), email/Google auth, dashboard search/edit/swipe-delete, dark/system theme, minSdk 29

This commit is contained in:
cania
2026-08-12 11:41:20 +02:00
commit a8791a5b10
138 changed files with 6285 additions and 0 deletions

128
lib/auth_screen.dart Normal file
View File

@@ -0,0 +1,128 @@
import 'package:flutter/material.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:login_vault_app/auth_service.dart';
/// First-launch screen: register with email+password, or with Google.
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; // toggle register/login
bool _busy = false;
String? _error;
Future<void> _submitEmail() 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 {
// success -> pop true
if (mounted) Navigator.pop(context, true);
}
} else {
await widget.auth.registerWithEmail(email, pass);
if (mounted) Navigator.pop(context, true);
}
} catch (e) {
setState(() => _error = e.toString());
} finally {
if (mounted) setState(() => _busy = false);
}
}
Future<void> _signInGoogle() async {
setState(() => _busy = true);
try {
final gs = GoogleSignIn(scopes: ['email']);
final account = await gs.signIn();
if (account?.email != null) {
await widget.auth.registerWithGoogle(account!.email);
if (mounted) Navigator.pop(context, true);
} else {
setState(() => _error = 'Google sign-in dibatalkan');
}
} catch (e) {
setState(() => _error = 'Google sign-in gagal: ${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()
: Column(
children: [
ElevatedButton(
onPressed: _submitEmail,
child: Text(_isLogin ? 'Masuk' : 'Daftar'),
),
TextButton(
onPressed: () =>
setState(() => _isLogin = !_isLogin),
child: Text(_isLogin
? 'Belum punya akun? Daftar'
: 'Sudah punya akun? Masuk'),
),
const Divider(),
OutlinedButton.icon(
onPressed: _signInGoogle,
icon: const Icon(Icons.login),
label: const Text('Lanjut dengan Google'),
),
],
),
],
),
),
);
}
}