Files
CityCards_Partner_Flutter/lib/login/views/login_page.dart
2026-04-24 10:37:05 +05:30

302 lines
12 KiB
Dart

import 'package:citycards_partner_flutter/core/app_router.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:google_fonts/google_fonts.dart';
import '../../constants/app_assets.dart';
import '../../constants/app_colors.dart';
import '../../custome_widgets/custom_button.dart';
import '../../custome_widgets/custom_textfield.dart';
import '../blocs/login/login_bloc.dart';
class LoginPage extends StatefulWidget {
const LoginPage({super.key});
@override
State<LoginPage> createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
final _emailFocusNode = FocusNode();
final _passwordFocusNode = FocusNode();
@override
void dispose() {
_emailController.dispose();
_passwordController.dispose();
_emailFocusNode.dispose();
_passwordFocusNode.dispose();
super.dispose();
}
bool _isEmailValid(String email) {
return RegExp(
r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$')
.hasMatch(email);
}
void _onLoginPressed(BuildContext context) {
FocusManager.instance.primaryFocus?.unfocus();
final email = _emailController.text.trim();
final password = _passwordController.text.trim();
final isEmailValid = _isEmailValid(email);
final isPasswordValid = password.length >= 8;
context.read<LoginBloc>().add(LoginEmailErrorToggled(!isEmailValid));
context.read<LoginBloc>().add(LoginPasswordErrorToggled(!isPasswordValid));
if (isEmailValid && isPasswordValid) {
context.read<LoginBloc>().add(
LoginSubmitted(
emailAddress: email,
password: password,
rememberMe: context.read<LoginBloc>().state.rememberMe,
),
);
}
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => FocusManager.instance.primaryFocus?.unfocus(),
behavior: HitTestBehavior.translucent,
child: Scaffold(
backgroundColor: AppColors.backgroundWhite,
resizeToAvoidBottomInset: true,
body: BlocConsumer<LoginBloc, LoginState>(
listener: (context, state) {
if (state.status == LoginStatus.success) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text("Login Successful."),
backgroundColor: Colors.green,
),
);
Navigator.pushNamedAndRemoveUntil(
context,
AppRouter.qrScanScreen,
(route) => false, // removes all previous routes
);
}
},
builder: (context, state) {
final isLoading = state.status == LoginStatus.loading;
return SafeArea(
child: SingleChildScrollView(
padding: EdgeInsets.symmetric(horizontal: 24.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 40.h),
// ===== LOGO SECTION =====
Center(
child: Column(
children: [
Image.asset(
AppAssets.appIcon,
height: 60.h,
),
SizedBox(height: 8.h),
Text(
"Partner's App",
style: GoogleFonts.poppins(
color: AppColors.primaryRed,
fontSize: 20.sp,
fontWeight: FontWeight.w500,
),
),
],
),
),
SizedBox(height: 60.h),
// ===== WELCOME TEXT =====
Center(
child: Column(
children: [
Text(
"Welcome Back",
style: GoogleFonts.poppins(
color: AppColors.black,
fontSize: 24.sp,
fontWeight: FontWeight.w600,
),
),
SizedBox(height: 8.h),
Text(
"Sign in to your account",
style: GoogleFonts.poppins(
color: AppColors.textGrey,
fontSize: 16.sp,
),
),
],
),
),
SizedBox(height: 32.h),
// ===== ERROR BANNER =====
if (state.status == LoginStatus.failure)
Container(
margin: EdgeInsets.only(bottom: 24.h),
padding: EdgeInsets.symmetric(
horizontal: 16.w, vertical: 12.h),
decoration: BoxDecoration(
color: AppColors.errorLight,
borderRadius: BorderRadius.circular(12.r),
border:
Border.all(color: AppColors.primaryRed.withOpacity(0.5)),
),
child: Row(
children: [
Icon(Icons.error_outline,
color: AppColors.primaryRed, size: 20.sp),
SizedBox(width: 12.w),
Expanded(
child: Text(
state.errorMessage ??
"Invalid email or password. Please try again.",
style: GoogleFonts.poppins(
color: AppColors.primaryRed,
fontSize: 13.sp,
fontWeight: FontWeight.w400,
),
),
),
],
),
),
// ===== EMAIL FIELD =====
CustomTextField(
label: 'Email Address',
hintText: 'Enter your email',
controller: _emailController,
focusNode: _emailFocusNode,
prefixIcon: Icons.email_outlined,
hasError: state.showEmailError,
errorText: 'Please enter a valid email address',
keyboardType: TextInputType.emailAddress,
textInputAction: TextInputAction.next,
readOnly: isLoading,
onChanged: (val) {
if (state.showEmailError) {
context.read<LoginBloc>().add(
LoginEmailErrorToggled(!_isEmailValid(val.trim())),
);
}
},
),
SizedBox(height: 20.h),
// ===== PASSWORD FIELD =====
CustomTextField(
label: 'Password',
hintText: 'Enter your password',
controller: _passwordController,
focusNode: _passwordFocusNode,
prefixIcon: Icons.lock_outline,
isPassword: true,
isPasswordVisible: state.isPasswordVisible,
onTogglePasswordVisibility: () {
context.read<LoginBloc>().add(
const LoginPasswordVisibilityToggled(),
);
},
hasError: state.showPasswordError,
errorText: 'Password must be at least 8 characters.',
textInputAction: TextInputAction.done,
readOnly: isLoading,
onChanged: (val) {
if (state.showPasswordError) {
context.read<LoginBloc>().add(
LoginPasswordErrorToggled(val.length < 8),
);
}
},
onSubmitted: (_) => _onLoginPressed(context),
),
SizedBox(height: 16.h),
// ===== REMEMBER ME + FORGOT PASSWORD =====
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
SizedBox(
height: 24.h,
width: 24.w,
child: Checkbox(
value: state.rememberMe,
onChanged: isLoading
? null
: (value) {
context.read<LoginBloc>().add(
LoginRememberMeToggled(
value ?? false),
);
},
activeColor: AppColors.primaryRed,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4.r),
),
side: BorderSide(color: Colors.grey[300]!),
),
),
SizedBox(width: 8.w),
Text(
"Remember me",
style: GoogleFonts.poppins(
color: AppColors.textGrey,
fontSize: 14.sp,
),
),
],
),
GestureDetector(
onTap: isLoading
? null
: () {
Navigator.pushNamed(
context,
AppRouter.forgotPassword,
);
},
child: Text(
"Forgot Password?",
style: GoogleFonts.poppins(
color: AppColors.primaryRed,
fontSize: 14.sp,
fontWeight: FontWeight.w500,
),
),
),
],
),
SizedBox(height: 80.h),
// ===== LOGIN BUTTON =====
CustomButton(
text: "Log in",
isLoading: isLoading,
onPressed: () => _onLoginPressed(context),
),
SizedBox(height: 24.h),
],
),
),
);
},
),
),
);
}
}