google sign-in/out
This commit is contained in:
parent
049e037933
commit
2354ee0a14
|
|
@ -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<AuthBloc, AuthState>(
|
||||
listener: (context, state) {
|
||||
if (state is AuthInitial) {
|
||||
Navigator.of(context).pushAndRemoveUntil(
|
||||
MaterialPageRoute(builder: (context) => const LoginPage()),
|
||||
(route) => false,
|
||||
);
|
||||
}
|
||||
},
|
||||
child: StreamBuilder<User?>(
|
||||
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();
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<User?>(
|
||||
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();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<AuthWrapper> createState() => _AuthWrapperState();
|
||||
}
|
||||
|
||||
class _AuthWrapperState extends State<AuthWrapper> {
|
||||
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocListener<AuthBloc, AuthState>(
|
||||
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<User?>(
|
||||
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();
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<void> 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<ThemeManager>(
|
||||
|
|
@ -38,12 +33,13 @@ 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),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
|
@ -51,5 +47,3 @@ class MyApp extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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<AuthEvent, AuthState> {
|
||||
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<SendOTPEvent>(_onSendOTP);
|
||||
on<VerifyOTPEvent>(_onVerifyOTP);
|
||||
on<SignInWithGoogleEvent>(_onSignInWithGoogle);
|
||||
on<SignOutEvent>(_onSignOut);
|
||||
}
|
||||
|
||||
Future<void> _onSendOTP(SendOTPEvent event, Emitter<AuthState> 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<void>();
|
||||
try {
|
||||
await _firebaseAuth.verifyPhoneNumber(
|
||||
phoneNumber: event.phoneNumber,
|
||||
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,
|
||||
timeStamp: _timeStamp,
|
||||
timeDue: _timeDue,
|
||||
));
|
||||
} else {
|
||||
emit(AuthError('Error sending code. Please try again.'));
|
||||
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<void> _onVerifyOTP(VerifyOTPEvent event, Emitter<AuthState> emit) async {
|
||||
emit(AuthLoading());
|
||||
final result = await verifyOTPUseCase(VerifyOTPParams(
|
||||
otpCode: event.otpCode,
|
||||
phoneNumber: event.phoneNumber,
|
||||
));
|
||||
if (result.isSuccess && result.data == true) {
|
||||
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()));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -94,4 +126,24 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> _onSignOut(SignOutEvent event, Emitter<AuthState> 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()}"));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -19,3 +19,5 @@ class VerifyOTPEvent extends AuthEvent {
|
|||
}
|
||||
|
||||
class SignInWithGoogleEvent extends AuthEvent {}
|
||||
|
||||
class SignOutEvent extends AuthEvent {}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -91,9 +91,7 @@ class _LoginPageState extends State<LoginPage> {
|
|||
),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
borderSide: BorderSide(
|
||||
color: AppColors.borderPrimary,
|
||||
),
|
||||
borderSide: BorderSide(color: AppColors.borderPrimary),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
|
|
@ -164,15 +162,18 @@ class _LoginPageState extends State<LoginPage> {
|
|||
SizedBox(height: height / 60),
|
||||
BlocConsumer<AuthBloc, AuthState>(
|
||||
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<LoginPage> {
|
|||
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<LoginPage> {
|
|||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Divider(thickness: 1, color: AppColors.greyBorder),
|
||||
child: Divider(
|
||||
thickness: 1,
|
||||
color: AppColors.greyBorder,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
@ -259,7 +266,9 @@ class _LoginPageState extends State<LoginPage> {
|
|||
),
|
||||
),
|
||||
onPressed: () {
|
||||
context.read<AuthBloc>().add(SignInWithGoogleEvent());
|
||||
context.read<AuthBloc>().add(
|
||||
SignInWithGoogleEvent(),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
|
|
@ -277,7 +286,8 @@ class _LoginPageState extends State<LoginPage> {
|
|||
void _openCountryPicker(BuildContext context) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => CustomCountryPicker(
|
||||
builder:
|
||||
(context) => CustomCountryPicker(
|
||||
onCountrySelected: (Country country) {
|
||||
setState(() {
|
||||
_selectedCountry = country;
|
||||
|
|
|
|||
|
|
@ -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<OTPVerificationPage> {
|
||||
final List<FocusNode> _focusNodes = List.generate(5, (_) => FocusNode());
|
||||
final List<TextEditingController> _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<AuthBloc>()
|
||||
.add(SendOTPEvent(phoneNumber: widget.phoneNumber));
|
||||
}
|
||||
}
|
||||
final List<FocusNode> _focusNodes = List.generate(6, (_) => FocusNode());
|
||||
final List<TextEditingController> _controllers = List.generate(6, (_) => TextEditingController());
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
|
|
@ -56,16 +34,15 @@ class _OTPVerificationPageState extends State<OTPVerificationPage> {
|
|||
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<OTPVerificationPage> {
|
|||
),
|
||||
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,13 +79,7 @@ class _OTPVerificationPageState extends State<OTPVerificationPage> {
|
|||
Widget build(BuildContext context) {
|
||||
final height = MediaQuery.of(context).size.height;
|
||||
|
||||
return BlocListener<AuthBloc, AuthState>(
|
||||
listener: (context, state) {
|
||||
if (state is AuthSuccess) {
|
||||
_otpTimer.initializeFromExpiry(expiryTime: state.timeDue);
|
||||
}
|
||||
},
|
||||
child: Scaffold(
|
||||
return Scaffold(
|
||||
body: SafeArea(
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
|
|
@ -121,27 +94,22 @@ class _OTPVerificationPageState extends State<OTPVerificationPage> {
|
|||
],
|
||||
),
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 24, vertical: 24),
|
||||
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),
|
||||
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 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),
|
||||
Text(
|
||||
"Enter the 6-digit verification code sent to your device.",
|
||||
style: const TextStyle(fontSize: 17, fontWeight: FontWeight.w500),
|
||||
),
|
||||
const SizedBox(height: 25),
|
||||
_buildOTPFields(),
|
||||
|
|
@ -149,24 +117,23 @@ class _OTPVerificationPageState extends State<OTPVerificationPage> {
|
|||
BlocConsumer<AuthBloc, AuthState>(
|
||||
listener: (context, state) {
|
||||
if (state is OTPVerified) {
|
||||
Navigator.push(
|
||||
Navigator.pushAndRemoveUntil(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const UserInfoPage()),
|
||||
MaterialPageRoute(builder: (context) => const UserInfoPage()),
|
||||
(route) => false,
|
||||
);
|
||||
}
|
||||
if (state is AuthError) {
|
||||
AppSnackBar.showError(
|
||||
context: context,
|
||||
message: state.message,
|
||||
duration: const Duration(seconds: 2),
|
||||
duration: const Duration(seconds: 3),
|
||||
);
|
||||
}
|
||||
},
|
||||
builder: (context, state) {
|
||||
if (state is AuthLoading) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator());
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
return SizedBox(
|
||||
width: double.infinity,
|
||||
|
|
@ -174,9 +141,8 @@ class _OTPVerificationPageState extends State<OTPVerificationPage> {
|
|||
child: Button(
|
||||
text: "Verify",
|
||||
onPressed: () {
|
||||
final otpCode =
|
||||
_controllers.map((c) => c.text).join();
|
||||
if (otpCode.length >= 4) {
|
||||
final otpCode = _controllers.map((c) => c.text).join();
|
||||
if (otpCode.length == 6) {
|
||||
context.read<AuthBloc>().add(VerifyOTPEvent(
|
||||
otpCode: otpCode,
|
||||
phoneNumber: widget.phoneNumber,
|
||||
|
|
@ -184,7 +150,7 @@ class _OTPVerificationPageState extends State<OTPVerificationPage> {
|
|||
} else {
|
||||
AppSnackBar.showWarning(
|
||||
context: context,
|
||||
message: 'Please enter the complete OTP code',
|
||||
message: 'Please enter the complete 6-digit OTP code',
|
||||
);
|
||||
}
|
||||
},
|
||||
|
|
@ -195,55 +161,19 @@ class _OTPVerificationPageState extends State<OTPVerificationPage> {
|
|||
),
|
||||
SizedBox(height: height / 25),
|
||||
Center(
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Divider(
|
||||
thickness: 1, color: AppColors.greyBorder),
|
||||
),
|
||||
ValueListenableBuilder<int>(
|
||||
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),
|
||||
),
|
||||
);
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
context.read<AuthBloc>().add(SendOTPEvent(phoneNumber: widget.phoneNumber));
|
||||
AppSnackBar.showInfo(context: context, message: "A new code has been sent.");
|
||||
},
|
||||
),
|
||||
Expanded(
|
||||
child: Divider(
|
||||
thickness: 1, color: AppColors.greyBorder),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
ValueListenableBuilder<bool>(
|
||||
valueListenable: _otpTimer.canResend,
|
||||
builder: (context, canResend, _) {
|
||||
return GestureDetector(
|
||||
onTap: canResend ? _resendOTP : null,
|
||||
child: Text(
|
||||
"Resend OTP",
|
||||
"Resend Code",
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: canResend
|
||||
? const Color.fromARGB(
|
||||
255, 0, 0, 0)
|
||||
: AppColors.greyBorder,
|
||||
color: AppColors.primary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
@ -253,7 +183,6 @@ class _OTPVerificationPageState extends State<OTPVerificationPage> {
|
|||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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<User?>(
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
class GlobalNavigator {
|
||||
static final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
||||
|
||||
static NavigatorState? get navigator => navigatorKey.currentState;
|
||||
|
||||
static void navigateToLogin() {
|
||||
print('🚀 GlobalNavigator: Navigating to login...');
|
||||
|
||||
if (navigator != null) {
|
||||
navigator!.pushNamedAndRemoveUntil(
|
||||
'/login',
|
||||
(Route<dynamic> route) => false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static void navigateToMain() {
|
||||
print('🚀 GlobalNavigator: Navigating to main...');
|
||||
|
||||
if (navigator != null) {
|
||||
navigator!.pushNamedAndRemoveUntil(
|
||||
'/main',
|
||||
(Route<dynamic> route) => false,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<void> 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
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue