Files
crm_mobile/lib/screens/login_screen.dart

267 lines
7.8 KiB
Dart

import 'package:flutter/material.dart';
import '../services/auth_service.dart';
import '../theme/app_theme.dart';
import '../utils/snackbar_utils.dart';
class LoginScreen extends StatefulWidget {
final void Function(AuthUser user) onLoggedIn;
const LoginScreen({super.key, required this.onLoggedIn});
@override
State<LoginScreen> createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> {
final _auth = AuthService();
final _emailCtrl = TextEditingController();
final _passwordCtrl = TextEditingController();
bool _busy = false;
@override
void dispose() {
_emailCtrl.dispose();
_passwordCtrl.dispose();
super.dispose();
}
Future<void> _login() async {
final email = _emailCtrl.text.trim();
final password = _passwordCtrl.text;
if (email.isEmpty || password.isEmpty) {
SnackbarUtils.showError(context, 'Enter your email and password');
return;
}
setState(() {
_busy = true;
});
try {
final user = await _auth.login(email, password);
if (!mounted) return;
if (user == null) {
setState(() {
_busy = false;
});
SnackbarUtils.showError(context, 'Invalid email or password');
return;
}
widget.onLoggedIn(user);
} catch (_) {
if (!mounted) return;
setState(() {
_busy = false;
});
SnackbarUtils.showError(context, 'Could not reach server. Check your connection.');
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.primary,
body: SafeArea(
child: Align(
alignment: const Alignment(0, -0.4),
child: SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 32),
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 420),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
_logo(),
const SizedBox(height: 20),
const Text(
'Doormile CRM',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.w800,
color: Colors.white,
letterSpacing: 0.2,
),
),
const SizedBox(height: 6),
Text(
'FIELD SALES PORTAL',
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.w700,
letterSpacing: 2,
color: Colors.white.withValues(alpha: 0.7),
),
),
const SizedBox(height: 28),
_card(),
],
),
),
),
),
),
);
}
Widget _logo() {
return Container(
width: 92,
height: 92,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(24),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.18),
blurRadius: 24,
offset: const Offset(0, 10),
),
],
),
child: Padding(
padding: const EdgeInsets.all(14),
child: Image.asset('assets/doormilelogoround.png', fit: BoxFit.contain),
),
);
}
Widget _card() {
return Container(
width: double.infinity,
padding: const EdgeInsets.all(22),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.12),
blurRadius: 30,
offset: const Offset(0, 12),
),
],
),
child: _loginForm(),
);
}
Widget _loginForm() {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Text(
'Sign In',
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.w800,
color: AppColors.darkBlue,
),
),
const SizedBox(height: 6),
const Text(
'Welcome back! Please enter your details.',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 13, color: AppColors.textSecondary),
),
const SizedBox(height: 24),
TextField(
controller: _emailCtrl,
keyboardType: TextInputType.emailAddress,
autocorrect: false,
textInputAction: TextInputAction.next,
decoration: InputDecoration(
hintText: 'Email',
prefixIcon: const Icon(Icons.email_outlined, color: AppColors.muted),
filled: true,
fillColor: Colors.grey.shade50,
contentPadding: const EdgeInsets.symmetric(vertical: 16),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: Colors.grey.shade200),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: Colors.grey.shade200),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: AppColors.primary, width: 2),
),
),
),
const SizedBox(height: 16),
TextField(
controller: _passwordCtrl,
obscureText: true,
textInputAction: TextInputAction.go,
onSubmitted: (_) => _login(),
decoration: InputDecoration(
hintText: 'Password',
prefixIcon: const Icon(Icons.lock_outline, color: AppColors.muted),
filled: true,
fillColor: Colors.grey.shade50,
contentPadding: const EdgeInsets.symmetric(vertical: 16),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: Colors.grey.shade200),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: Colors.grey.shade200),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: AppColors.primary, width: 2),
),
),
),
const SizedBox(height: 24),
_primaryButton(
label: 'Continue',
onPressed: _busy ? null : _login,
),
],
);
}
Widget _primaryButton({
required String label,
required VoidCallback? onPressed,
}) {
return SizedBox(
width: double.infinity,
height: 50,
child: ElevatedButton(
onPressed: onPressed,
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primary,
foregroundColor: Colors.white,
disabledBackgroundColor: AppColors.primary.withValues(alpha: 0.5),
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: _busy
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2.2,
color: Colors.white,
),
)
: Text(
label,
style: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.w800,
),
),
),
);
}
}