166 lines
5.2 KiB
Dart
166 lines
5.2 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:go_router/go_router.dart';
|
|
import 'package:provider/provider.dart';
|
|
import 'package:qpay/screens/onboarding/login/login_controller.dart';
|
|
import 'package:qpay/screens/onboarding/onboarding_controller.dart';
|
|
import 'package:qpay/screens/onboarding/widgets/onboarding_layout.dart';
|
|
import 'package:qpay/widgets/app_snack_bar.dart';
|
|
import 'package:skeletonizer/skeletonizer.dart';
|
|
|
|
class LoginScreen extends StatefulWidget {
|
|
const LoginScreen({super.key});
|
|
|
|
@override
|
|
State<LoginScreen> createState() => _LoginScreenState();
|
|
}
|
|
|
|
class _LoginScreenState extends State<LoginScreen> {
|
|
late OnboardingController onboardingController;
|
|
|
|
final _formKey = GlobalKey<FormState>();
|
|
final TextEditingController _emailController = TextEditingController();
|
|
final TextEditingController _passwordController = TextEditingController();
|
|
|
|
bool _obscurePassword = true;
|
|
bool _loading = false;
|
|
|
|
late LoginController _loginController;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_loginController = LoginController(context);
|
|
onboardingController = Provider.of<OnboardingController>(
|
|
context,
|
|
listen: false,
|
|
);
|
|
|
|
// Pre-fill email if it was captured during registration.
|
|
final capturedEmail = onboardingController.model.email;
|
|
if (capturedEmail != null && capturedEmail.isNotEmpty) {
|
|
_emailController.text = capturedEmail;
|
|
}
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_emailController.dispose();
|
|
_passwordController.dispose();
|
|
_loginController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
Future<void> _onLogin() async {
|
|
if (!(_formKey.currentState?.validate() ?? false)) return;
|
|
|
|
final email = _emailController.text.trim();
|
|
final password = _passwordController.text;
|
|
|
|
// Cache credentials in the onboarding model for use by the rest
|
|
// of the registration/login flow.
|
|
onboardingController.updateEmail(email);
|
|
onboardingController.updatePassword(password);
|
|
|
|
setState(() => _loading = true);
|
|
|
|
final response = await _loginController.login(email, password);
|
|
|
|
if (!mounted) return;
|
|
|
|
setState(() => _loading = false);
|
|
|
|
if (response.isSuccess) {
|
|
AppSnackBar.showSuccess(context, 'Login successful!');
|
|
} else {
|
|
AppSnackBar.showError(context, response.error ?? 'Login failed');
|
|
}
|
|
|
|
if (!mounted) return;
|
|
|
|
if (response.isSuccess) {
|
|
context.go('/');
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return OnboardingLayout(
|
|
showBackButton: true,
|
|
trailingLabel: 'Sign up',
|
|
trailingRoute: '/onboarding/phone',
|
|
trailingIcon: Icons.person_add_alt_1_rounded,
|
|
bottomLinkLabel: 'Continue as guest?',
|
|
bottomLinkRoute: '/',
|
|
child: OnboardingCard(
|
|
child: Form(
|
|
key: _formKey,
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
const OnboardingHero(
|
|
icon: Icons.lock_open_rounded,
|
|
title: 'Welcome back',
|
|
subtitle: 'Sign in to continue with Velocity',
|
|
),
|
|
const SizedBox(height: 28),
|
|
OnboardingTextField(
|
|
controller: _emailController,
|
|
hintText: 'Email address',
|
|
prefixIcon: Icons.email_outlined,
|
|
keyboardType: TextInputType.emailAddress,
|
|
validator: (value) {
|
|
if (value == null || value.trim().isEmpty) {
|
|
return 'Please enter your email address';
|
|
}
|
|
final emailRegex = RegExp(r'^[\w\.\-+]+@[\w\.\-]+\.\w+$');
|
|
if (!emailRegex.hasMatch(value.trim())) {
|
|
return 'Please enter a valid email address';
|
|
}
|
|
return null;
|
|
},
|
|
),
|
|
const SizedBox(height: 14),
|
|
OnboardingTextField(
|
|
controller: _passwordController,
|
|
hintText: 'Password',
|
|
prefixIcon: Icons.lock_outline,
|
|
obscureText: _obscurePassword,
|
|
enableSuggestions: false,
|
|
autocorrect: false,
|
|
suffixIcon: IconButton(
|
|
icon: Icon(
|
|
_obscurePassword
|
|
? Icons.visibility_rounded
|
|
: Icons.visibility_off_rounded,
|
|
),
|
|
tooltip: _obscurePassword ? 'Show password' : 'Hide password',
|
|
onPressed: () {
|
|
setState(() {
|
|
_obscurePassword = !_obscurePassword;
|
|
});
|
|
},
|
|
),
|
|
validator: (value) {
|
|
if (value == null || value.isEmpty) {
|
|
return 'Please enter your password';
|
|
}
|
|
return null;
|
|
},
|
|
),
|
|
const SizedBox(height: 28),
|
|
Skeletonizer(
|
|
enabled: _loading,
|
|
child: OnboardingPrimaryButton(
|
|
label: 'Sign in',
|
|
icon: Icons.arrow_forward_rounded,
|
|
onPressed: _onLogin,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|