diff --git a/lib/login/blocs/forgot_password/forgot_password_bloc.dart b/lib/login/blocs/forgot_password/forgot_password_bloc.dart index 656038c..1d2a914 100644 --- a/lib/login/blocs/forgot_password/forgot_password_bloc.dart +++ b/lib/login/blocs/forgot_password/forgot_password_bloc.dart @@ -13,12 +13,13 @@ class ForgotPasswordBloc extends Bloc super(const ForgotPasswordState()) { on(_onForgotPasswordSubmitted); on(_onEmailErrorToggled); + on(_onReset); } Future _onForgotPasswordSubmitted( - ForgotPasswordSubmitted event, - Emitter emit, - ) async { + ForgotPasswordSubmitted event, + Emitter emit, + ) async { emit(state.copyWith(status: ForgotPasswordStatus.loading, errorMessage: null)); try { @@ -36,9 +37,16 @@ class ForgotPasswordBloc extends Bloc } void _onEmailErrorToggled( - ForgotPasswordEmailErrorToggled event, - Emitter emit, - ) { + ForgotPasswordEmailErrorToggled event, + Emitter emit, + ) { emit(state.copyWith(showEmailError: event.show)); } + + Future _onReset( + ForgotPasswordReset event, + Emitter emit, + ) async { + emit(const ForgotPasswordState()); + } } \ No newline at end of file diff --git a/lib/login/blocs/forgot_password/forgot_password_event.dart b/lib/login/blocs/forgot_password/forgot_password_event.dart index eb166c7..abcd532 100644 --- a/lib/login/blocs/forgot_password/forgot_password_event.dart +++ b/lib/login/blocs/forgot_password/forgot_password_event.dart @@ -18,8 +18,16 @@ class ForgotPasswordSubmitted extends ForgotPasswordEvent { class ForgotPasswordEmailErrorToggled extends ForgotPasswordEvent { final bool show; + const ForgotPasswordEmailErrorToggled(this.show); @override List get props => [show]; +} + +class ForgotPasswordReset extends ForgotPasswordEvent { + const ForgotPasswordReset(); + + @override + List get props => []; } \ No newline at end of file diff --git a/lib/login/blocs/verify_otp/verify_otp_bloc.dart b/lib/login/blocs/verify_otp/verify_otp_bloc.dart index d5fd623..1d46a94 100644 --- a/lib/login/blocs/verify_otp/verify_otp_bloc.dart +++ b/lib/login/blocs/verify_otp/verify_otp_bloc.dart @@ -16,14 +16,20 @@ class VerifyOtpBloc extends Bloc { } void _onOtpChanged(OtpChanged event, Emitter emit) { - emit(state.copyWith(otp: event.otp)); + emit(state.copyWith( + otp: event.otp, + status: VerifyOtpStatus.initial, + clearError: true, + )); } Future _onVerifyOtpSubmitted( VerifyOtpSubmitted event, Emitter emit, ) async { - emit(state.copyWith(status: VerifyOtpStatus.loading, errorMessage: null)); + if (state.status == VerifyOtpStatus.loading) return; + + emit(state.copyWith(status: VerifyOtpStatus.loading, clearError: true)); try { await _otpRepository.verifyOtp( diff --git a/lib/login/blocs/verify_otp/verify_otp_state.dart b/lib/login/blocs/verify_otp/verify_otp_state.dart index 16cb891..a3f5daa 100644 --- a/lib/login/blocs/verify_otp/verify_otp_state.dart +++ b/lib/login/blocs/verify_otp/verify_otp_state.dart @@ -16,11 +16,12 @@ class VerifyOtpState extends Equatable { VerifyOtpState copyWith({ VerifyOtpStatus? status, String? errorMessage, + bool clearError = false, String? otp, }) { return VerifyOtpState( status: status ?? this.status, - errorMessage: errorMessage ?? this.errorMessage, + errorMessage: clearError ? null : (errorMessage ?? this.errorMessage), otp: otp ?? this.otp, ); } diff --git a/lib/login/repositories/forgot_password_repository.dart b/lib/login/repositories/forgot_password_repository.dart index 27dbcb6..8e4eed4 100644 --- a/lib/login/repositories/forgot_password_repository.dart +++ b/lib/login/repositories/forgot_password_repository.dart @@ -11,7 +11,7 @@ class ForgotPasswordRepository { data: {"emailAddress": emailAddress}, ); } catch (e) { - throw Exception('Failed to send forgot password request: $e'); + throw Exception('$e'); } } } \ No newline at end of file diff --git a/lib/login/repositories/otp_repository.dart b/lib/login/repositories/otp_repository.dart index 8dd3b4c..5163ad1 100644 --- a/lib/login/repositories/otp_repository.dart +++ b/lib/login/repositories/otp_repository.dart @@ -17,7 +17,7 @@ class OtpRepository { }, ); } catch (e) { - throw Exception('Failed to verify OTP: $e'); + throw Exception('$e'); } } } \ No newline at end of file diff --git a/lib/login/views/forgot_password_page.dart b/lib/login/views/forgot_password_page.dart index abe6dd4..7a8ba2f 100644 --- a/lib/login/views/forgot_password_page.dart +++ b/lib/login/views/forgot_password_page.dart @@ -20,6 +20,13 @@ class _ForgotPasswordPageState extends State { final _emailController = TextEditingController(); final _emailFocusNode = FocusNode(); + @override + void initState() { + super.initState(); + // Reset the form state when entering the page + context.read().add(ForgotPasswordReset()); + } + @override void dispose() { _emailController.dispose(); @@ -28,7 +35,8 @@ class _ForgotPasswordPageState extends State { } bool _isEmailValid(String email) { - return RegExp(r"^[a-zA-Z0-9.a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]+@[a-zA-Z0-9]+\.[a-zA-Z]+") + 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); } @@ -100,7 +108,7 @@ class _ForgotPasswordPageState extends State { ), SizedBox(height: 8.h), Text( - "Partner’s App", + "Partner's App", style: GoogleFonts.poppins( color: AppColors.primaryRed, fontSize: 20.sp, @@ -124,11 +132,11 @@ class _ForgotPasswordPageState extends State { ), SizedBox(height: 12.h), Text( - "Enter your email to update your password\nand secure your account", + "Enter your email to update your password and secure your account", textAlign: TextAlign.center, style: GoogleFonts.poppins( color: AppColors.textGrey, - fontSize: 16.sp, + fontSize: 15.sp, height: 1.4, ), ), @@ -147,6 +155,7 @@ class _ForgotPasswordPageState extends State { textInputAction: TextInputAction.done, readOnly: isLoading, onChanged: (val) { + // Only validate and update error if user has attempted submission if (state.showEmailError) { context.read().add( ForgotPasswordEmailErrorToggled(!_isEmailValid(val.trim())), diff --git a/lib/login/views/login_page.dart b/lib/login/views/login_page.dart index 26d41ab..6d4c432 100644 --- a/lib/login/views/login_page.dart +++ b/lib/login/views/login_page.dart @@ -32,7 +32,8 @@ class _LoginPageState extends State { } bool _isEmailValid(String email) { - return RegExp(r"^[a-zA-Z0-9.a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]+@[a-zA-Z0-9]+\.[a-zA-Z]+") + 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); } @@ -70,6 +71,12 @@ class _LoginPageState extends State { body: BlocConsumer( 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, @@ -292,4 +299,4 @@ class _LoginPageState extends State { ), ); } -} +} \ No newline at end of file diff --git a/lib/login/views/otp_verification_page.dart b/lib/login/views/otp_verification_page.dart index ae0ca44..01e1e08 100644 --- a/lib/login/views/otp_verification_page.dart +++ b/lib/login/views/otp_verification_page.dart @@ -30,6 +30,7 @@ class OtpVerificationPage extends StatelessWidget { arguments: email, ); } else if (state.status == VerifyOtpStatus.failure) { + ScaffoldMessenger.of(context).hideCurrentSnackBar(); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(state.errorMessage ?? "Verification failed"), @@ -116,6 +117,7 @@ class OtpVerificationPage extends StatelessWidget { ); }, onSubmit: (String verificationCode) { + if (isLoading) return; context.read().add( OtpChanged(otp: verificationCode), ); @@ -136,7 +138,7 @@ class OtpVerificationPage extends StatelessWidget { CustomButton( text: "Verify", isLoading: isLoading, - onPressed: state.otp.length == 6 + onPressed: state.otp.length == 6 && !isLoading ? () { context.read().add( VerifyOtpSubmitted( diff --git a/lib/onboarding/views/onboarding_page.dart b/lib/onboarding/views/onboarding_page.dart index bc26c4d..852185e 100644 --- a/lib/onboarding/views/onboarding_page.dart +++ b/lib/onboarding/views/onboarding_page.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; import '../../core/app_router.dart'; import '../blocs/onboarding_bloc.dart'; import '../models/onboarding_model.dart'; @@ -58,7 +59,7 @@ class OnboardingPage extends StatelessWidget { if (current.title != "Manage Booking Seamlessly") SafeArea( child: Padding( - padding: const EdgeInsets.all(16.0), + padding: EdgeInsets.all(16.r), child: Align( alignment: Alignment.topRight, child: OutlinedButton( @@ -66,14 +67,23 @@ class OnboardingPage extends StatelessWidget { context.read().add(OnboardingSkipPressed()); }, style: OutlinedButton.styleFrom( - side: const BorderSide(color: Colors.white), + side: BorderSide(color: Colors.white, width: 2.w), foregroundColor: Colors.white, - padding: const EdgeInsets.symmetric( - horizontal: 20, - vertical: 8, + padding: EdgeInsets.symmetric( + horizontal: 34.w, + vertical: 10.h, + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16.r), + ), + ), + child: Text( + "Skip", + style: TextStyle( + fontSize: 14.sp, + fontWeight: FontWeight.w500, ), ), - child: const Text("Skip"), ), ), ), @@ -83,7 +93,7 @@ class OnboardingPage extends StatelessWidget { Align( alignment: Alignment.bottomCenter, child: Padding( - padding: const EdgeInsets.all(16.0), + padding: EdgeInsets.all(16.r), child: AnimatedSwitcher( duration: const Duration(milliseconds: 500), transitionBuilder: (Widget child, Animation animation) { diff --git a/lib/onboarding/views/widgets/glass_card.dart b/lib/onboarding/views/widgets/glass_card.dart index 944b9cd..bdd2209 100644 --- a/lib/onboarding/views/widgets/glass_card.dart +++ b/lib/onboarding/views/widgets/glass_card.dart @@ -1,5 +1,6 @@ import 'dart:ui'; import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; class GlassCard extends StatelessWidget { final String title; @@ -20,15 +21,15 @@ class GlassCard extends StatelessWidget { @override Widget build(BuildContext context) { return ClipRRect( - borderRadius: BorderRadius.circular(10), + borderRadius: BorderRadius.circular(10.r), child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 6, sigmaY: 6), child: Container( width: double.infinity, - padding: const EdgeInsets.all(20), + padding: EdgeInsets.all(20.r), decoration: BoxDecoration( color: Colors.white.withOpacity(0.3), - borderRadius: BorderRadius.circular(10), + borderRadius: BorderRadius.circular(10.r), border: Border.all(color: Colors.white.withOpacity(0.2)), ), child: Column( @@ -37,52 +38,52 @@ class GlassCard extends StatelessWidget { Text( title, textAlign: TextAlign.center, - style: const TextStyle( + style: TextStyle( color: Colors.white, - fontSize: 20, + fontSize: 20.sp, fontWeight: FontWeight.bold), ), - const SizedBox(height: 8), + SizedBox(height: 8.h), Text( description, textAlign: TextAlign.center, style: TextStyle( color: Colors.white.withOpacity(0.9), - fontSize: 14, + fontSize: 14.sp, ), ), - const SizedBox(height: 16), + SizedBox(height: 16.h), Row( mainAxisAlignment: MainAxisAlignment.center, children: List.generate( total, - (index) => AnimatedContainer( + (index) => AnimatedContainer( duration: const Duration(milliseconds: 300), - margin: const EdgeInsets.symmetric(horizontal: 4), - height: 6, - width: currentIndex == index ? 24 : 12, + margin: EdgeInsets.symmetric(horizontal: 4.w), + height: 6.h, + width: currentIndex == index ? 24.w : 12.w, decoration: BoxDecoration( color: currentIndex == index ? const Color(0xFFE25E5E) : Colors.white24, - borderRadius: BorderRadius.circular(3), + borderRadius: BorderRadius.circular(3.r), ), ), ), ), - const SizedBox(height: 20), + SizedBox(height: 20.h), ElevatedButton( onPressed: onContinue, style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFFE25E5E), shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), + borderRadius: BorderRadius.circular(12.r), ), - minimumSize: const Size(double.infinity, 48), + minimumSize: Size(double.infinity, 48.h), ), - child: const Text( + child: Text( 'Continue', - style: TextStyle(fontSize: 16, color: Colors.white), + style: TextStyle(fontSize: 16.sp, color: Colors.white), ), ), ], diff --git a/lib/scan/bloc/submit_qr_code/submit_qr_code_bloc.dart b/lib/scan/bloc/submit_qr_code/submit_qr_code_bloc.dart index 7f028b1..02a5a85 100644 --- a/lib/scan/bloc/submit_qr_code/submit_qr_code_bloc.dart +++ b/lib/scan/bloc/submit_qr_code/submit_qr_code_bloc.dart @@ -36,7 +36,7 @@ class SubmitQrCodeBloc final success = response['success'] == true; final message = response['message']?.toString(); - final error = response['error']?.toString(); + final error = response['suggestedReason']?.toString(); if (success) { emit(SubmitQrCodeSuccess( diff --git a/lib/scan/view/qr_scan_screen.dart b/lib/scan/view/qr_scan_screen.dart index fe55d1d..a4c70ca 100644 --- a/lib/scan/view/qr_scan_screen.dart +++ b/lib/scan/view/qr_scan_screen.dart @@ -6,6 +6,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:mobile_scanner/mobile_scanner.dart'; import 'package:intl/intl.dart'; +import '../../local_peference/local_preference.dart'; import '../bloc/submit_qr_code/submit_qr_code_bloc.dart'; import '../bloc/recent_scan_history/recent_scan_history_bloc.dart'; import '../models/recent_scan_history_model.dart'; @@ -599,7 +600,7 @@ class _QrScanScreenState extends State SizedBox(height: 25.h), Text( "Quick Links", - style: TextStyle(fontWeight: FontWeight.w600, fontSize: 16.sp), + style: TextStyle(fontWeight: FontWeight.w600, fontSize: 18.sp), ), SizedBox(height: 10.h), Row( @@ -611,6 +612,7 @@ class _QrScanScreenState extends State "assets/scan/ticket-fill.png", AppRouter.ticketRedemptionScreen, initFraction, + 10.h, // Added padding ), ), SizedBox(width: 8.w), @@ -621,6 +623,7 @@ class _QrScanScreenState extends State "assets/scan/support.png", AppRouter.helpSupportPage, initFraction, + 10.h, // Added padding ), ), SizedBox(width: 8.w), @@ -631,10 +634,12 @@ class _QrScanScreenState extends State "assets/scan/page.png", AppRouter.bookingPage, initFraction, + 10.h, // Added padding ), ), ], ), + SizedBox(height: 10.h), // Empty space at bottom ], ); } @@ -722,7 +727,7 @@ class _QrScanScreenState extends State final index = entry.key; final item = entry.value; final isSuccess = item.status.toLowerCase() == "success"; - final label = index == 0 ? "Last Scan" : "Previous"; + final label = index == 0 ? "Last Scan" : "Previous Scan"; return GestureDetector( onTap: () { @@ -751,7 +756,7 @@ class _QrScanScreenState extends State Text( "Quick Links", - style: TextStyle(fontWeight: FontWeight.w600, fontSize: 16.sp), + style: TextStyle(fontWeight: FontWeight.w600, fontSize: 18.sp), ), SizedBox(height: 10.h), Row( @@ -763,6 +768,7 @@ class _QrScanScreenState extends State "assets/scan/support.png", AppRouter.helpSupportPage, initFraction, + 22.h, // Added padding ), ), SizedBox(width: 10.w), @@ -773,6 +779,7 @@ class _QrScanScreenState extends State "assets/scan/page.png", AppRouter.bookingPage, initFraction, + 22.h, // Added padding ), ), ], @@ -787,6 +794,7 @@ class _QrScanScreenState extends State "assets/scan/ticket-fill.png", AppRouter.ticketRedemptionScreen, initFraction, + 22.h, // Added padding ), ), SizedBox(width: 10.w), @@ -797,6 +805,7 @@ class _QrScanScreenState extends State "assets/scan/qr.png", AppRouter.qrScanScreen, initFraction, + 22.h, // Added padding ), ), ], @@ -865,7 +874,8 @@ class _QrScanScreenState extends State "Reason: $reason", style: TextStyle( fontSize: 12.sp, - color: Colors.red[700], + // color: Colors.red[700], + color: Colors.black87, ), ), ), @@ -885,14 +895,16 @@ class _QrScanScreenState extends State } Widget _quickLink( - BuildContext context, - String label, - String icon, - String route, - double initFraction, - ) { + BuildContext context, + String label, + String icon, + String route, + double initFraction, + double? verticalPadding, // Add this parameter + ) { return InkWell( onTap: () async { + // await LocalPreference.clearAccessToken(); if (label == "Scan Image") { if (sheetController.isAttached) { await sheetController.animateTo( @@ -909,10 +921,12 @@ class _QrScanScreenState extends State }, child: Container( decoration: BoxDecoration( - color: Colors.red[100], + color: Colors.red[100]?.withValues(alpha: 0.6), borderRadius: BorderRadius.circular(10.r), ), - padding: EdgeInsets.symmetric(vertical: 10.h), + padding: EdgeInsets.symmetric( + vertical: verticalPadding ?? 12.h, // Use parameter, default to 12 if not provided + ), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ diff --git a/lib/scan_history/models/scan_history_model.dart b/lib/scan_history/models/scan_history_model.dart index ce3c20c..56f4b5a 100644 --- a/lib/scan_history/models/scan_history_model.dart +++ b/lib/scan_history/models/scan_history_model.dart @@ -82,12 +82,22 @@ class ScanHistory { ); } - /// 🔥 Safe Date Parser + /// 🔥 Safe Date Parser - Handles multiple formats static DateTime? _parseDate(dynamic date) { if (date == null) return null; try { - return DateTime.parse(date.toString()); + final dateStr = date.toString().trim(); + + // Try ISO 8601 format first (2026-04-20T16:21:00) + try { + return DateTime.parse(dateStr); + } catch (e) { + // If that fails, try DD-MM-YYYY HH:MM format (20-04-2026 16:21) + final dateFormat = DateFormat('dd-MM-yyyy HH:mm'); + return dateFormat.parse(dateStr); + } } catch (e) { + print('❌ Failed to parse date: $date, Error: $e'); return null; } } @@ -143,6 +153,6 @@ class ScanHistory { String get statusColorHex { if (isSuccess) return '#4CAF50'; // green if (isFailed) return '#F44336'; // red - return '#9E9E9E'; // grey + return '#9E9E9E'; // gre } } \ No newline at end of file diff --git a/lib/scan_history/views/scan_history_detail_page.dart b/lib/scan_history/views/scan_history_detail_page.dart index c327d4f..b3e18f8 100644 --- a/lib/scan_history/views/scan_history_detail_page.dart +++ b/lib/scan_history/views/scan_history_detail_page.dart @@ -10,23 +10,20 @@ class ScanHistoryDetailPage extends StatelessWidget { const ScanHistoryDetailPage({super.key, required this.passId}); TextStyle _headerStyle() => const TextStyle( - fontWeight: FontWeight.w600, - fontSize: 24, + fontSize: 28, height: 1.0, ); TextStyle _labelStyle() => const TextStyle( - fontWeight: FontWeight.w500, - fontSize: 14, + fontSize: 18, color: Color(0xFF9E9E9E), ); TextStyle _valueStyle() => const TextStyle( - fontWeight: FontWeight.w400, - fontSize: 12, + fontSize: 16, color: Colors.black, ); @@ -100,10 +97,10 @@ class ScanHistoryDetailPage extends StatelessWidget { Expanded( child: Center( child: Text( - '#${data.bookingNumber}', + data.bookingNumber, style: const TextStyle( fontWeight: FontWeight.w700, - fontSize: 28, + fontSize: 22, ), maxLines: 1, overflow: TextOverflow.ellipsis, @@ -175,10 +172,4 @@ class ScanHistoryDetailPage extends StatelessWidget { ], ); } - - - -} - - - +} \ No newline at end of file