Files
pfm-ocr/lib/features/auth/login_screen.dart
T
fhanyuh caf8e98378 chore: normalize line endings (CRLF -> LF)
No content changes: git diff --ignore-all-space over these files is empty.
The churn came from editing on Windows against a repo checked out with LF.
2026-08-27 10:40:49 +07:00

152 lines
5.3 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../../config/app_config.dart';
import '../../core/network/api_exception.dart';
import 'auth_provider.dart';
class LoginScreen extends ConsumerStatefulWidget {
const LoginScreen({super.key});
@override
ConsumerState<LoginScreen> createState() => _LoginScreenState();
}
class _LoginScreenState extends ConsumerState<LoginScreen> {
final _idController = TextEditingController();
final _passwordController = TextEditingController();
final _formKey = GlobalKey<FormState>();
bool _isLoading = false;
void _handleLogin() async {
if (!_formKey.currentState!.validate()) return;
final id = _idController.text.trim();
final pwd = _passwordController.text.trim();
setState(() => _isLoading = true);
try {
await ref.read(authProvider.notifier).login(id, pwd);
if (mounted) {
context.go('/camera');
}
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
ApiException.from(e).displayMessage,
style: const TextStyle(color: Colors.white),
),
backgroundColor: AppConfig.errorColor,
),
);
}
} finally {
if (mounted) {
setState(() => _isLoading = false);
}
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Center(
child: SingleChildScrollView(
padding: const EdgeInsets.symmetric(
horizontal: AppConfig.spaceXXL,
vertical: AppConfig.spaceXL,
),
child: Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Image.asset(
'assets/logo.png',
width: 120,
height: 120,
errorBuilder: (context, error, stackTrace) => const Icon(
Icons.storefront,
size: 80,
color: AppConfig.primaryColor
),
),
const SizedBox(height: AppConfig.spaceXL),
Text(
'Selamat Datang Kembali',
style: AppConfig.h1,
textAlign: TextAlign.center,
),
const SizedBox(height: AppConfig.spaceSmall),
Text(
'Masuk untuk menyinkronkan dokumen Anda ke server pusat',
style: AppConfig.bodyMedium,
textAlign: TextAlign.center,
),
const SizedBox(height: AppConfig.spaceXXL * 1.5),
TextFormField(
controller: _idController,
decoration: const InputDecoration(
labelText: 'ID dari Admin',
prefixIcon: Icon(Icons.person_outline),
),
validator: (value) {
if (value == null || value.trim().isEmpty) {
return 'Username/ID tidak boleh kosong';
}
return null;
},
),
const SizedBox(height: AppConfig.spaceLarge),
TextFormField(
controller: _passwordController,
obscureText: true,
decoration: const InputDecoration(
labelText: 'Kata Sandi',
prefixIcon: Icon(Icons.lock_outline),
),
validator: (value) {
if (value == null || value.trim().isEmpty) {
return 'Kata sandi tidak boleh kosong';
}
if (value.trim().length < 6) {
return 'Kata sandi minimal 6 karakter';
}
return null;
},
),
const SizedBox(height: AppConfig.spaceXXL),
SizedBox(
height: 56,
child: ElevatedButton(
onPressed: _isLoading ? null : _handleLogin,
child: _isLoading
? const SizedBox(
width: 24,
height: 24,
child: CircularProgressIndicator(color: Colors.white, strokeWidth: 3)
)
: const Text('Masuk'),
),
),
const SizedBox(height: AppConfig.spaceXL),
Text(
'Hubungi admin pusat untuk mendapatkan kredensial akun.',
style: AppConfig.bodySmall,
textAlign: TextAlign.center,
),
],
),
),
),
),
),
);
}
}