175 lines
5.0 KiB
Dart
175 lines
5.0 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
|
|
import '../../core/app_theme.dart';
|
|
import '../../core/routes.dart';
|
|
import '../../core/widgets/auth_primary_button.dart';
|
|
import '../../core/widgets/auth_password_field.dart';
|
|
import '../../core/widgets/auth_scaffold.dart';
|
|
import '../../core/widgets/auth_text_field.dart';
|
|
import 'auth_notifier.dart';
|
|
import 'auth_state.dart';
|
|
|
|
class LoginPage extends ConsumerStatefulWidget {
|
|
const LoginPage({super.key});
|
|
|
|
@override
|
|
ConsumerState<LoginPage> createState() => _LoginPageState();
|
|
}
|
|
|
|
class _LoginPageState extends ConsumerState<LoginPage> {
|
|
final _formKey = GlobalKey<FormState>();
|
|
final _emailCtrl = TextEditingController();
|
|
final _passwordCtrl = TextEditingController();
|
|
|
|
AutovalidateMode _autoValidate = AutovalidateMode.disabled;
|
|
bool _isButtonEnabled = false;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_emailCtrl.addListener(_checkButton);
|
|
_passwordCtrl.addListener(_checkButton);
|
|
}
|
|
|
|
void _checkButton() {
|
|
final filled = _emailCtrl.text.isNotEmpty &&
|
|
_passwordCtrl.text.isNotEmpty;
|
|
if (filled != _isButtonEnabled) {
|
|
setState(() => _isButtonEnabled = filled);
|
|
}
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_emailCtrl.dispose();
|
|
_passwordCtrl.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
Future<void> _submit() async {
|
|
setState(() => _autoValidate = AutovalidateMode.onUserInteraction);
|
|
|
|
if (!_formKey.currentState!.validate()) return;
|
|
|
|
await ref.read(authProvider.notifier).login(
|
|
_emailCtrl.text,
|
|
_passwordCtrl.text,
|
|
);
|
|
|
|
if (!mounted) return;
|
|
|
|
final state = ref.read(authProvider);
|
|
if (state is AuthSuccess) {
|
|
Navigator.pushNamedAndRemoveUntil(context, AppRoutes.main, (route) => false);
|
|
} else if (state is AuthError) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(
|
|
content: Text(state.message),
|
|
backgroundColor: AppTheme.statusDanger,
|
|
),
|
|
);
|
|
ref.read(authProvider.notifier).resetError();
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final authState = ref.watch(authProvider);
|
|
final isLoading = authState is AuthLoading;
|
|
|
|
return AuthScaffold(
|
|
title: 'Log in',
|
|
subtitle: 'Masukkan email & password yang telah terdaftar',
|
|
child: Form(
|
|
key: _formKey,
|
|
autovalidateMode: _autoValidate,
|
|
child: Column(
|
|
children: [
|
|
AuthTextField(
|
|
controller: _emailCtrl,
|
|
label: 'Email',
|
|
keyboardType: TextInputType.emailAddress,
|
|
prefixIcon: Icons.email_outlined,
|
|
validator: (value) {
|
|
if (value == null || value.isEmpty) {
|
|
return 'Email tidak boleh kosong';
|
|
}
|
|
if (!value.contains('@') || !value.contains('.')) {
|
|
return 'Format email tidak valid';
|
|
}
|
|
return null;
|
|
},
|
|
),
|
|
|
|
const SizedBox(height: 20),
|
|
|
|
AuthPasswordField(
|
|
controller: _passwordCtrl,
|
|
label: 'Password',
|
|
validator: (value) {
|
|
if (value == null || value.isEmpty) {
|
|
return 'Password tidak boleh kosong';
|
|
}
|
|
if (value.length < 6) {
|
|
return 'Minimal 6 karakter';
|
|
}
|
|
return null;
|
|
},
|
|
),
|
|
|
|
const SizedBox(height: 12),
|
|
|
|
Align(
|
|
alignment: Alignment.centerRight,
|
|
child: TextButton(
|
|
style: TextButton.styleFrom(
|
|
padding: EdgeInsets.zero,
|
|
minimumSize: Size.zero,
|
|
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
|
),
|
|
onPressed: () {},
|
|
child: const Text(
|
|
'Lupa password?',
|
|
style: TextStyle(color: AppTheme.primary, fontWeight: FontWeight.w600),
|
|
),
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 40),
|
|
|
|
AuthPrimaryButton(
|
|
text: 'Masuk',
|
|
enabled: _isButtonEnabled,
|
|
isLoading: isLoading,
|
|
onPressed: _submit,
|
|
),
|
|
|
|
const SizedBox(height: 24),
|
|
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
const Text(
|
|
'Belum punya akun? ',
|
|
style: TextStyle(color: AppTheme.textSecondary),
|
|
),
|
|
GestureDetector(
|
|
onTap: () => Navigator.pushReplacementNamed(context, AppRoutes.register),
|
|
child: const Text(
|
|
'Daftar di sini',
|
|
style: TextStyle(
|
|
color: AppTheme.primary,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|