Files
velocity-pay-flutter/lib/screens/onboarding/login/login_screen.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,
),
),
],
),
),
),
);
}
}