From 2354ee0a1497f09d480041af5cf0f9ce59605fa4 Mon Sep 17 00:00:00 2001 From: mohamadmahdi jebeli Date: Mon, 8 Sep 2025 11:04:57 +0330 Subject: [PATCH] google sign-in/out --- FIREBASE_PHONE_AUTH_README.md | 0 SETUP_INSTRUCTIONS.md | 0 TROUBLESHOOTING_GUIDE.md | 0 lib/auth_gate.dart | 42 +++ lib/auth_navigation_handler.dart | 34 +++ lib/auth_wrapper.dart | 53 ++++ lib/firebase_options.dart | 0 lib/main.dart | 20 +- lib/screens/auth/bloc/auth_bloc.dart | 120 ++++++--- lib/screens/auth/bloc/auth_event.dart | 4 +- lib/screens/auth/bloc/auth_state.dart | 12 +- lib/screens/auth/login_page.dart | 82 +++--- lib/screens/auth/otp_verification_page.dart | 285 ++++++++------------ lib/screens/auth/services/auth_service.dart | 0 lib/screens/mains/discover/discover.dart | 2 +- lib/simple_auth_gate.dart | 53 ++++ lib/utils/global_navigator.dart | 29 ++ lib/widgets/logout_popup.dart | 47 ++-- lib/widgets/openChecker.dart | 1 - 19 files changed, 493 insertions(+), 291 deletions(-) create mode 100644 FIREBASE_PHONE_AUTH_README.md create mode 100644 SETUP_INSTRUCTIONS.md create mode 100644 TROUBLESHOOTING_GUIDE.md create mode 100644 lib/auth_gate.dart create mode 100644 lib/auth_navigation_handler.dart create mode 100644 lib/auth_wrapper.dart create mode 100644 lib/firebase_options.dart create mode 100644 lib/screens/auth/services/auth_service.dart create mode 100644 lib/simple_auth_gate.dart create mode 100644 lib/utils/global_navigator.dart diff --git a/FIREBASE_PHONE_AUTH_README.md b/FIREBASE_PHONE_AUTH_README.md new file mode 100644 index 0000000..e69de29 diff --git a/SETUP_INSTRUCTIONS.md b/SETUP_INSTRUCTIONS.md new file mode 100644 index 0000000..e69de29 diff --git a/TROUBLESHOOTING_GUIDE.md b/TROUBLESHOOTING_GUIDE.md new file mode 100644 index 0000000..e69de29 diff --git a/lib/auth_gate.dart b/lib/auth_gate.dart new file mode 100644 index 0000000..f6ec9c0 --- /dev/null +++ b/lib/auth_gate.dart @@ -0,0 +1,42 @@ +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:lba/screens/auth/login_page.dart'; +import 'package:lba/screens/mains/navigation/navigation.dart'; +import 'package:lba/screens/auth/bloc/auth_bloc.dart'; + +class AuthGate extends StatelessWidget { + const AuthGate({super.key}); + + @override + Widget build(BuildContext context) { + return BlocListener( + listener: (context, state) { + if (state is AuthInitial) { + Navigator.of(context).pushAndRemoveUntil( + MaterialPageRoute(builder: (context) => const LoginPage()), + (route) => false, + ); + } + }, + child: StreamBuilder( + stream: FirebaseAuth.instance.authStateChanges(), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Scaffold( + body: Center( + child: CircularProgressIndicator(), + ), + ); + } + + if (!snapshot.hasData || snapshot.data == null) { + return const LoginPage(); + } + + return const MainScreen(); + }, + ), + ); + } +} \ No newline at end of file diff --git a/lib/auth_navigation_handler.dart b/lib/auth_navigation_handler.dart new file mode 100644 index 0000000..df25b34 --- /dev/null +++ b/lib/auth_navigation_handler.dart @@ -0,0 +1,34 @@ +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter/material.dart'; +import 'package:lba/screens/auth/onboarding_page.dart'; +import 'package:lba/screens/mains/navigation/navigation.dart'; + +class AuthNavigationHandler extends StatelessWidget { + const AuthNavigationHandler({super.key}); + + @override + Widget build(BuildContext context) { + return StreamBuilder( + stream: FirebaseAuth.instance.authStateChanges(), + builder: (context, snapshot) { + print('🔍 Auth State: ${snapshot.connectionState}, hasData: ${snapshot.hasData}'); + + if (snapshot.connectionState == ConnectionState.waiting) { + return const Scaffold( + body: Center( + child: CircularProgressIndicator(), + ), + ); + } + + if (snapshot.hasData && snapshot.data != null) { + print('✅ User is logged in: ${snapshot.data!.uid}'); + return const MainScreen(); + } + + print('❌ User is not logged in'); + return const OnboardingPage(); + }, + ); + } +} diff --git a/lib/auth_wrapper.dart b/lib/auth_wrapper.dart new file mode 100644 index 0000000..9f5b9f3 --- /dev/null +++ b/lib/auth_wrapper.dart @@ -0,0 +1,53 @@ +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:lba/screens/auth/onboarding_page.dart'; +import 'package:lba/screens/mains/navigation/navigation.dart'; +import 'package:lba/screens/auth/bloc/auth_bloc.dart'; + +class AuthWrapper extends StatefulWidget { + const AuthWrapper({super.key}); + + @override + State createState() => _AuthWrapperState(); +} + +class _AuthWrapperState extends State { + final GlobalKey navigatorKey = GlobalKey(); + + @override + Widget build(BuildContext context) { + return BlocListener( + listener: (context, state) { + if (state is AuthInitial) { + if (FirebaseAuth.instance.currentUser == null) { + Navigator.of(context).pushAndRemoveUntil( + MaterialPageRoute(builder: (context) => const OnboardingPage()), + (route) => false, + ); + } + } + }, + child: StreamBuilder( + stream: FirebaseAuth.instance.authStateChanges(), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Scaffold( + body: Center( + child: CircularProgressIndicator(), + ), + ); + } + + final bool isLoggedIn = snapshot.hasData && snapshot.data != null; + + if (isLoggedIn) { + return const MainScreen(); + } else { + return const OnboardingPage(); + } + }, + ), + ); + } +} diff --git a/lib/firebase_options.dart b/lib/firebase_options.dart new file mode 100644 index 0000000..e69de29 diff --git a/lib/main.dart b/lib/main.dart index dd126b8..a866c19 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -3,13 +3,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:provider/provider.dart'; -import 'screens/auth/onboarding_page.dart'; import 'screens/auth/bloc/auth_bloc.dart'; -import 'screens/auth/usecases/send_otp.dart'; -import 'screens/auth/usecases/verify_otp.dart'; import 'widgets/animated_splash_screen.dart'; import 'package:firebase_core/firebase_core.dart'; import 'utils/theme_manager.dart'; +import 'simple_auth_gate.dart'; Future main() async { WidgetsFlutterBinding.ensureInitialized(); @@ -26,10 +24,7 @@ class MyApp extends StatelessWidget { providers: [ ChangeNotifierProvider(create: (context) => ThemeManager()), BlocProvider( - create: (context) => AuthBloc( - sendOTPUseCase: SendOTP(), - verifyOTPUseCase: VerifyOTP(), - ), + create: (context) => AuthBloc(), ), ], child: Consumer( @@ -38,18 +33,17 @@ class MyApp extends StatelessWidget { title: 'LBA', theme: themeManager.lightTheme, darkTheme: themeManager.darkTheme, - themeMode: themeManager.isDarkMode ? ThemeMode.dark : ThemeMode.light, + themeMode: + themeManager.isDarkMode ? ThemeMode.dark : ThemeMode.light, themeAnimationDuration: const Duration(milliseconds: 300), themeAnimationCurve: Curves.easeInOutCubic, home: CoolSplashScreen( - nextScreen: const OnboardingPage(), - duration: const Duration(seconds: 6), + nextScreen: const SimpleAuthGate(), + duration: const Duration(seconds: 3), ), ); }, ), ); } -} - - +} \ No newline at end of file diff --git a/lib/screens/auth/bloc/auth_bloc.dart b/lib/screens/auth/bloc/auth_bloc.dart index 1f832eb..5e3eab5 100644 --- a/lib/screens/auth/bloc/auth_bloc.dart +++ b/lib/screens/auth/bloc/auth_bloc.dart @@ -1,58 +1,90 @@ +import 'dart:async'; +import 'package:flutter/foundation.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:google_sign_in/google_sign_in.dart'; import 'package:firebase_auth/firebase_auth.dart'; -import 'package:lba/screens/auth/usecases/send_otp.dart'; -import 'package:lba/screens/auth/usecases/verify_otp.dart'; part 'auth_event.dart'; part 'auth_state.dart'; class AuthBloc extends Bloc { - final SendOTP sendOTPUseCase; - final VerifyOTP verifyOTPUseCase; final FirebaseAuth _firebaseAuth = FirebaseAuth.instance; + String? _verificationId; - String _timeStamp = ""; - String _timeDue = ""; - - String get timeStamp => _timeStamp; - String get timeDue => _timeDue; - - AuthBloc({ - required this.sendOTPUseCase, - required this.verifyOTPUseCase, - }) : super(AuthInitial()) { + AuthBloc() : super(AuthInitial()) { on(_onSendOTP); on(_onVerifyOTP); on(_onSignInWithGoogle); + on(_onSignOut); } Future _onSendOTP(SendOTPEvent event, Emitter emit) async { emit(AuthLoading()); - final result = await sendOTPUseCase(SendOTPParams(phoneNumber: event.phoneNumber)); - if (result.isSuccess && result.data != null) { - _timeStamp = result.data!.timeStamp; - _timeDue = result.data!.timeDue; - emit(AuthSuccess( + final completer = Completer(); + try { + await _firebaseAuth.verifyPhoneNumber( phoneNumber: event.phoneNumber, - timeStamp: _timeStamp, - timeDue: _timeDue, - )); - } else { - emit(AuthError('Error sending code. Please try again.')); + verificationCompleted: (PhoneAuthCredential credential) async { + debugPrint("✅ Verification Completed (Auto-retrieval)"); + await _firebaseAuth.signInWithCredential(credential); + if (!completer.isCompleted) { + emit(OTPVerified()); + completer.complete(); + } + }, + verificationFailed: (FirebaseAuthException e) { + debugPrint("❌ Verification Failed: ${e.message}"); + if (!completer.isCompleted) { + emit(AuthError(e.message ?? 'Verification Failed')); + completer.complete(); + } + }, + codeSent: (String verificationId, int? resendToken) { + debugPrint("📬 Code Sent! Verification ID: $verificationId"); + _verificationId = verificationId; + if (!completer.isCompleted) { + emit(AuthCodeSent( + phoneNumber: event.phoneNumber, + )); + completer.complete(); + } + }, + codeAutoRetrievalTimeout: (String verificationId) { + debugPrint("⌛️ Code Auto-retrieval Timeout."); + }, + ); + await completer.future; + } catch (e) { + debugPrint("⛔️ General Error in _onSendOTP: ${e.toString()}"); + if (!completer.isCompleted) { + emit(AuthError(e.toString())); + } } } + // ... بقیه کد بدون تغییر ... Future _onVerifyOTP(VerifyOTPEvent event, Emitter emit) async { emit(AuthLoading()); - final result = await verifyOTPUseCase(VerifyOTPParams( - otpCode: event.otpCode, - phoneNumber: event.phoneNumber, - )); - if (result.isSuccess && result.data == true) { - emit(OTPVerified()); - } else { - emit(AuthError('The code entered is incorrect. Please try again.')); + try { + if (_verificationId != null) { + final credential = PhoneAuthProvider.credential( + verificationId: _verificationId!, + smsCode: event.otpCode, + ); + + await _firebaseAuth.signInWithCredential(credential); + emit(OTPVerified()); + } else { + emit(AuthError("Verification ID not found. Please try again.")); + } + } on FirebaseAuthException catch (e) { + if (e.code == 'invalid-verification-code') { + emit(AuthError('The code entered is incorrect. Please try again.')); + } else { + emit(AuthError(e.message ?? 'An error occurred')); + } + } catch (e) { + emit(AuthError(e.toString())); } } @@ -60,7 +92,7 @@ class AuthBloc extends Bloc { SignInWithGoogleEvent event, Emitter emit, ) async { - emit(AuthLoading()); + emit(AuthLoading()); try { final GoogleSignIn googleSignIn = GoogleSignIn( scopes: ['email'], @@ -94,4 +126,24 @@ class AuthBloc extends Bloc { } } -} + Future _onSignOut(SignOutEvent event, Emitter emit) async { + emit(AuthLoading()); + try { + debugPrint('🚪 Starting logout process...'); + + await GoogleSignIn().signOut(); + debugPrint('✅ Google SignOut completed'); + + await _firebaseAuth.signOut(); + debugPrint('✅ Firebase SignOut completed'); + + emit(AuthInitial()); + debugPrint('✅ AuthInitial state emitted'); + + } catch (e) { + debugPrint('❌ Error in logout: $e'); + emit(AuthError("Error signing out: ${e.toString()}")); + } + } + +} \ No newline at end of file diff --git a/lib/screens/auth/bloc/auth_event.dart b/lib/screens/auth/bloc/auth_event.dart index 082f25c..8916b1d 100644 --- a/lib/screens/auth/bloc/auth_event.dart +++ b/lib/screens/auth/bloc/auth_event.dart @@ -18,4 +18,6 @@ class VerifyOTPEvent extends AuthEvent { }); } -class SignInWithGoogleEvent extends AuthEvent {} \ No newline at end of file +class SignInWithGoogleEvent extends AuthEvent {} + +class SignOutEvent extends AuthEvent {} \ No newline at end of file diff --git a/lib/screens/auth/bloc/auth_state.dart b/lib/screens/auth/bloc/auth_state.dart index 5792b44..f2ca9fa 100644 --- a/lib/screens/auth/bloc/auth_state.dart +++ b/lib/screens/auth/bloc/auth_state.dart @@ -6,21 +6,13 @@ class AuthInitial extends AuthState {} class AuthLoading extends AuthState {} -class AuthSuccess extends AuthState { +class AuthCodeSent extends AuthState { final String phoneNumber; - final String timeStamp; - final String timeDue; - - AuthSuccess({ - required this.phoneNumber, - required this.timeStamp, - required this.timeDue, - }); + AuthCodeSent({required this.phoneNumber}); } class AuthError extends AuthState { final String message; - AuthError(this.message); } diff --git a/lib/screens/auth/login_page.dart b/lib/screens/auth/login_page.dart index 5734a24..6ad8cd3 100644 --- a/lib/screens/auth/login_page.dart +++ b/lib/screens/auth/login_page.dart @@ -91,9 +91,7 @@ class _LoginPageState extends State { ), border: OutlineInputBorder( borderRadius: BorderRadius.circular(5), - borderSide: BorderSide( - color: AppColors.borderPrimary, - ), + borderSide: BorderSide(color: AppColors.borderPrimary), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(5), @@ -142,37 +140,40 @@ class _LoginPageState extends State { // keepSignedIn = !keepSignedIn; // }); // }, - // child: Row( - // mainAxisAlignment: MainAxisAlignment.start, - // children: [ - // Checkbox( - // value: keepSignedIn, - // activeColor: AppColors.borderPrimary, - // onChanged: (bool? value) { - // setState(() { - // keepSignedIn = value ?? false; - // }); - // }, - // ), - // const Text( - // "Keep me signed in", - // style: TextStyle(fontSize: 15), - // ), - // ], - // ), + // child: Row( + // mainAxisAlignment: MainAxisAlignment.start, + // children: [ + // Checkbox( + // value: keepSignedIn, + // activeColor: AppColors.borderPrimary, + // onChanged: (bool? value) { + // setState(() { + // keepSignedIn = value ?? false; + // }); + // }, + // ), + // const Text( + // "Keep me signed in", + // style: TextStyle(fontSize: 15), + // ), + // ], + // ), // ), SizedBox(height: height / 60), BlocConsumer( listener: (context, state) { - if (state is AuthSuccess) { + // اینجا رو تغییر بده + if (state is AuthCodeSent) { Navigator.push( context, MaterialPageRoute( builder: (context) => OTPVerificationPage( - phoneNumber: - "+${_selectedCountry.phoneCode}${phoneController.text}", - timeDue: state.timeDue, + phoneNumber: state.phoneNumber, + timeDue: + DateTime.now() + .add(const Duration(minutes: 2)) + .toIso8601String(), ), ), ); @@ -219,7 +220,10 @@ class _LoginPageState extends State { Row( children: [ Expanded( - child: Divider(thickness: 1, color: AppColors.greyBorder), + child: Divider( + thickness: 1, + color: AppColors.greyBorder, + ), ), Padding( padding: EdgeInsets.symmetric( @@ -234,7 +238,10 @@ class _LoginPageState extends State { ), ), Expanded( - child: Divider(thickness: 1, color: AppColors.greyBorder), + child: Divider( + thickness: 1, + color: AppColors.greyBorder, + ), ), ], ), @@ -259,7 +266,9 @@ class _LoginPageState extends State { ), ), onPressed: () { - context.read().add(SignInWithGoogleEvent()); + context.read().add( + SignInWithGoogleEvent(), + ); }, ), ), @@ -277,14 +286,15 @@ class _LoginPageState extends State { void _openCountryPicker(BuildContext context) { showDialog( context: context, - builder: (context) => CustomCountryPicker( - onCountrySelected: (Country country) { - setState(() { - _selectedCountry = country; - countryController.text = country.name; - }); - }, - ), + builder: + (context) => CustomCountryPicker( + onCountrySelected: (Country country) { + setState(() { + _selectedCountry = country; + countryController.text = country.name; + }); + }, + ), ); } } diff --git a/lib/screens/auth/otp_verification_page.dart b/lib/screens/auth/otp_verification_page.dart index f15e598..573ee52 100644 --- a/lib/screens/auth/otp_verification_page.dart +++ b/lib/screens/auth/otp_verification_page.dart @@ -6,7 +6,6 @@ import 'package:lba/screens/auth/bloc/auth_bloc.dart'; import 'package:lba/widgets/app_snackbar.dart'; import '../../gen/assets.gen.dart'; import '../../widgets/button.dart'; -import '../../widgets/remainingTime.dart'; import 'user_info_page.dart'; class OTPVerificationPage extends StatefulWidget { @@ -24,29 +23,8 @@ class OTPVerificationPage extends StatefulWidget { } class _OTPVerificationPageState extends State { - final List _focusNodes = List.generate(5, (_) => FocusNode()); - final List _controllers = - List.generate(5, (_) => TextEditingController()); - late RemainingTime _otpTimer; - - @override - void initState() { - super.initState(); - _otpTimer = RemainingTime(); - _initializeTimer(); - } - - void _initializeTimer() { - _otpTimer.initializeFromExpiry(expiryTime: widget.timeDue); - } - - void _resendOTP() { - if (_otpTimer.canResend.value) { - context - .read() - .add(SendOTPEvent(phoneNumber: widget.phoneNumber)); - } - } + final List _focusNodes = List.generate(6, (_) => FocusNode()); + final List _controllers = List.generate(6, (_) => TextEditingController()); @override void dispose() { @@ -56,16 +34,15 @@ class _OTPVerificationPageState extends State { for (final controller in _controllers) { controller.dispose(); } - _otpTimer.dispose(); super.dispose(); } Widget _buildOTPFields() { return Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: List.generate(5, (index) { + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: List.generate(6, (index) { return SizedBox( - width: 60, + width: 50, child: TextField( controller: _controllers[index], focusNode: _focusNodes[index], @@ -81,14 +58,16 @@ class _OTPVerificationPageState extends State { ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8), - borderSide: BorderSide( - color: AppColors.borderPrimary), + borderSide: BorderSide(color: AppColors.borderPrimary), ), ), onChanged: (value) { - if (value.length == 1 && index < 4) { + if (value.length == 1 && index < 5) { FocusScope.of(context).requestFocus(_focusNodes[index + 1]); } + if (value.isEmpty && index > 0) { + FocusScope.of(context).requestFocus(_focusNodes[index - 1]); + } }, ), ); @@ -100,157 +79,107 @@ class _OTPVerificationPageState extends State { Widget build(BuildContext context) { final height = MediaQuery.of(context).size.height; - return BlocListener( - listener: (context, state) { - if (state is AuthSuccess) { - _otpTimer.initializeFromExpiry(expiryTime: state.timeDue); - } - }, - child: Scaffold( - body: SafeArea( - child: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( + return Scaffold( + body: SafeArea( + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + IconButton( + icon: SvgPicture.asset(Assets.icons.back.path), + onPressed: () => Navigator.pop(context), + ), + ], + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - IconButton( - icon: SvgPicture.asset(Assets.icons.back.path), - onPressed: () => Navigator.pop(context), + Center( + child: Padding( + padding: const EdgeInsets.only(top: 0), + child: SvgPicture.asset(Assets.images.logo.path, height: height / 5.2), + ), + ), + SizedBox(height: height / 20), + const Text("OTP Verification", style: TextStyle(fontSize: 33, fontWeight: FontWeight.bold)), + const SizedBox(height: 8), + Text( + "Enter the 6-digit verification code sent to your device.", + style: const TextStyle(fontSize: 17, fontWeight: FontWeight.w500), + ), + const SizedBox(height: 25), + _buildOTPFields(), + SizedBox(height: height / 7), + BlocConsumer( + listener: (context, state) { + if (state is OTPVerified) { + Navigator.pushAndRemoveUntil( + context, + MaterialPageRoute(builder: (context) => const UserInfoPage()), + (route) => false, + ); + } + if (state is AuthError) { + AppSnackBar.showError( + context: context, + message: state.message, + duration: const Duration(seconds: 3), + ); + } + }, + builder: (context, state) { + if (state is AuthLoading) { + return const Center(child: CircularProgressIndicator()); + } + return SizedBox( + width: double.infinity, + height: 48, + child: Button( + text: "Verify", + onPressed: () { + final otpCode = _controllers.map((c) => c.text).join(); + if (otpCode.length == 6) { + context.read().add(VerifyOTPEvent( + otpCode: otpCode, + phoneNumber: widget.phoneNumber, + )); + } else { + AppSnackBar.showWarning( + context: context, + message: 'Please enter the complete 6-digit OTP code', + ); + } + }, + color: AppColors.buttonPrimary, + ), + ); + }, + ), + SizedBox(height: height / 25), + Center( + child: InkWell( + onTap: () { + context.read().add(SendOTPEvent(phoneNumber: widget.phoneNumber)); + AppSnackBar.showInfo(context: context, message: "A new code has been sent."); + }, + child: Text( + "Resend Code", + style: TextStyle( + fontSize: 16, + color: AppColors.primary, + fontWeight: FontWeight.bold, + ), + ), + ), ), ], ), - Padding( - padding: - const EdgeInsets.symmetric(horizontal: 24, vertical: 24), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Center( - child: Padding( - padding: const EdgeInsets.only(top: 0), - child: SvgPicture.asset(Assets.images.logo.path, - height: height / 5.2), - ), - ), - SizedBox(height: height / 20), - const Text("OTP Verification", - style: TextStyle( - fontSize: 33, fontWeight: FontWeight.bold)), - const SizedBox(height: 8), - const Text( - "Enter the verification code we just sent to your device.", - style: TextStyle( - fontSize: 17, fontWeight: FontWeight.w500), - ), - const SizedBox(height: 25), - _buildOTPFields(), - SizedBox(height: height / 7), - BlocConsumer( - listener: (context, state) { - if (state is OTPVerified) { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => const UserInfoPage()), - ); - } - if (state is AuthError) { - AppSnackBar.showError( - context: context, - message: state.message, - duration: const Duration(seconds: 2), - ); - } - }, - builder: (context, state) { - if (state is AuthLoading) { - return const Center( - child: CircularProgressIndicator()); - } - return SizedBox( - width: double.infinity, - height: 48, - child: Button( - text: "Verify", - onPressed: () { - final otpCode = - _controllers.map((c) => c.text).join(); - if (otpCode.length >= 4) { - context.read().add(VerifyOTPEvent( - otpCode: otpCode, - phoneNumber: widget.phoneNumber, - )); - } else { - AppSnackBar.showWarning( - context: context, - message: 'Please enter the complete OTP code', - ); - } - }, - color: AppColors.buttonPrimary, - ), - ); - }, - ), - SizedBox(height: height / 25), - Center( - child: Column( - children: [ - Row( - children: [ - Expanded( - child: Divider( - thickness: 1, color: AppColors.greyBorder), - ), - ValueListenableBuilder( - valueListenable: _otpTimer.remainingSeconds, - builder: (context, seconds, _) { - return Padding( - padding: const EdgeInsets.symmetric( - horizontal: 8.0), - child: Text( - "Resend OTP in ${_otpTimer.formatTime()}", - style: const TextStyle( - fontWeight: FontWeight.w500, - fontSize: 18), - ), - ); - }, - ), - Expanded( - child: Divider( - thickness: 1, color: AppColors.greyBorder), - ), - ], - ), - const SizedBox(height: 8), - ValueListenableBuilder( - valueListenable: _otpTimer.canResend, - builder: (context, canResend, _) { - return GestureDetector( - onTap: canResend ? _resendOTP : null, - child: Text( - "Resend OTP", - style: TextStyle( - fontSize: 16, - color: canResend - ? const Color.fromARGB( - 255, 0, 0, 0) - : AppColors.greyBorder, - ), - ), - ); - }, - ), - ], - ), - ), - ], - ), - ), - ], - ), + ), + ], ), ), ), diff --git a/lib/screens/auth/services/auth_service.dart b/lib/screens/auth/services/auth_service.dart new file mode 100644 index 0000000..e69de29 diff --git a/lib/screens/mains/discover/discover.dart b/lib/screens/mains/discover/discover.dart index 2e96363..8b21278 100644 --- a/lib/screens/mains/discover/discover.dart +++ b/lib/screens/mains/discover/discover.dart @@ -1224,7 +1224,7 @@ class FirstPurchaseCard extends StatelessWidget { Row( children: [ SvgPicture.asset(Assets.icons.star.path, - width: 16, color: AppColors.textSecondary), + width: 16,), const SizedBox(width: 4), Text( rating.toString(), diff --git a/lib/simple_auth_gate.dart b/lib/simple_auth_gate.dart new file mode 100644 index 0000000..9200f22 --- /dev/null +++ b/lib/simple_auth_gate.dart @@ -0,0 +1,53 @@ +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter/material.dart'; +import 'package:lba/screens/auth/login_page.dart'; +import 'package:lba/screens/auth/onboarding_page.dart'; +import 'package:lba/screens/mains/navigation/navigation.dart'; + +class SimpleAuthGate extends StatelessWidget { + const SimpleAuthGate({super.key}); + + @override + Widget build(BuildContext context) { + return StreamBuilder( + stream: FirebaseAuth.instance.authStateChanges(), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Scaffold( + body: Center( + child: CircularProgressIndicator(), + ), + ); + } + + if (snapshot.hasError) { + return const Scaffold( + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.error, size: 64, color: Colors.red), + SizedBox(height: 16), + Text('An error occurred. Please try again later.'), + ], + ), + ), + ); + } + + final user = snapshot.data; + final isLoggedIn = user != null; + + if (isLoggedIn) { + return const MainScreen(); + } + + return _shouldShowOnboarding() ? const OnboardingPage() : const LoginPage(); + }, + ); + } + + bool _shouldShowOnboarding() { + return false; + } +} diff --git a/lib/utils/global_navigator.dart b/lib/utils/global_navigator.dart new file mode 100644 index 0000000..88abec8 --- /dev/null +++ b/lib/utils/global_navigator.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; + +class GlobalNavigator { + static final GlobalKey navigatorKey = GlobalKey(); + + static NavigatorState? get navigator => navigatorKey.currentState; + + static void navigateToLogin() { + print('🚀 GlobalNavigator: Navigating to login...'); + + if (navigator != null) { + navigator!.pushNamedAndRemoveUntil( + '/login', + (Route route) => false, + ); + } + } + + static void navigateToMain() { + print('🚀 GlobalNavigator: Navigating to main...'); + + if (navigator != null) { + navigator!.pushNamedAndRemoveUntil( + '/main', + (Route route) => false, + ); + } + } +} diff --git a/lib/widgets/logout_popup.dart b/lib/widgets/logout_popup.dart index 75a449f..eee01c1 100644 --- a/lib/widgets/logout_popup.dart +++ b/lib/widgets/logout_popup.dart @@ -2,6 +2,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:lba/gen/assets.gen.dart'; import 'package:lba/res/colors.dart'; +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:google_sign_in/google_sign_in.dart'; Future showLogoutDialog(BuildContext context) async { showDialog( @@ -39,10 +41,7 @@ class _AnimatedLogoutDialogState extends State<_AnimatedLogoutDialog> curve: Curves.elasticOut, ); - _fadeAnimation = CurvedAnimation( - parent: _controller, - curve: Curves.easeIn, - ); + _fadeAnimation = CurvedAnimation(parent: _controller, curve: Curves.easeIn); _controller.forward(); } @@ -60,8 +59,9 @@ class _AnimatedLogoutDialogState extends State<_AnimatedLogoutDialog> child: ScaleTransition( scale: _scaleAnimation, child: Dialog( - shape: - RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(15), + ), elevation: 10, backgroundColor: AppColors.surface, child: Stack( @@ -69,8 +69,12 @@ class _AnimatedLogoutDialogState extends State<_AnimatedLogoutDialog> alignment: Alignment.topCenter, children: [ Padding( - padding: const EdgeInsets.only( - top: 50, left: 20, right: 20, bottom: 20), + padding: EdgeInsets.only( + top: 50, + left: 20, + right: 20, + bottom: 20, + ), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, @@ -109,12 +113,20 @@ class _AnimatedLogoutDialogState extends State<_AnimatedLogoutDialog> borderRadius: BorderRadius.circular(8), side: BorderSide(color: AppColors.offerTimer), ), - padding: - const EdgeInsets.symmetric(vertical: 12), + padding: const EdgeInsets.symmetric(vertical: 12), ), - onPressed: () { - // TODO: Add actual logout logic here - Navigator.of(context).pop(); + onPressed: () async { + Navigator.of(context).pop(); + try { + await GoogleSignIn().signOut(); + await FirebaseAuth.instance.signOut(); + print('✅ Logout successful'); + } catch (e) { + print('❌ Logout error: $e'); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('خطا در خروج: $e')), + ); + } }, child: Text( "Log Out", @@ -132,8 +144,7 @@ class _AnimatedLogoutDialogState extends State<_AnimatedLogoutDialog> borderRadius: BorderRadius.circular(8), ), side: BorderSide(color: AppColors.greyBorder), - padding: - const EdgeInsets.symmetric(vertical: 12), + padding: const EdgeInsets.symmetric(vertical: 12), ), onPressed: () => Navigator.of(context).pop(), child: const Text("Cancel"), @@ -163,7 +174,9 @@ class _AnimatedLogoutDialogState extends State<_AnimatedLogoutDialog> radius: 40, child: Padding( padding: const EdgeInsets.all(12.0), - child: SvgPicture.asset(Assets.icons.solarLogout3BoldDuotone.path), + child: SvgPicture.asset( + Assets.icons.solarLogout3BoldDuotone.path, + ), ), ), ), @@ -174,4 +187,4 @@ class _AnimatedLogoutDialogState extends State<_AnimatedLogoutDialog> ), ); } -} \ No newline at end of file +} diff --git a/lib/widgets/openChecker.dart b/lib/widgets/openChecker.dart index a22ba07..68e0d74 100644 --- a/lib/widgets/openChecker.dart +++ b/lib/widgets/openChecker.dart @@ -49,7 +49,6 @@ class IsOpenChecker { final nowTime = Duration(hours: now.hour, minutes: now.minute); if (closeTime <= openTime) { - // شیفت شبانه (مثلاً 22:00 تا 02:00) return nowTime >= openTime || nowTime <= closeTime; }