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/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'screens/auth/onboarding_page.dart';
|
|
||||||
import 'screens/auth/bloc/auth_bloc.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 'widgets/animated_splash_screen.dart';
|
||||||
import 'package:firebase_core/firebase_core.dart';
|
import 'package:firebase_core/firebase_core.dart';
|
||||||
import 'utils/theme_manager.dart';
|
import 'utils/theme_manager.dart';
|
||||||
|
import 'simple_auth_gate.dart';
|
||||||
|
|
||||||
Future<void> main() async {
|
Future<void> main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
@ -26,10 +24,7 @@ class MyApp extends StatelessWidget {
|
||||||
providers: [
|
providers: [
|
||||||
ChangeNotifierProvider(create: (context) => ThemeManager()),
|
ChangeNotifierProvider(create: (context) => ThemeManager()),
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (context) => AuthBloc(
|
create: (context) => AuthBloc(),
|
||||||
sendOTPUseCase: SendOTP(),
|
|
||||||
verifyOTPUseCase: VerifyOTP(),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
child: Consumer<ThemeManager>(
|
child: Consumer<ThemeManager>(
|
||||||
|
|
@ -38,18 +33,17 @@ class MyApp extends StatelessWidget {
|
||||||
title: 'LBA',
|
title: 'LBA',
|
||||||
theme: themeManager.lightTheme,
|
theme: themeManager.lightTheme,
|
||||||
darkTheme: themeManager.darkTheme,
|
darkTheme: themeManager.darkTheme,
|
||||||
themeMode: themeManager.isDarkMode ? ThemeMode.dark : ThemeMode.light,
|
themeMode:
|
||||||
|
themeManager.isDarkMode ? ThemeMode.dark : ThemeMode.light,
|
||||||
themeAnimationDuration: const Duration(milliseconds: 300),
|
themeAnimationDuration: const Duration(milliseconds: 300),
|
||||||
themeAnimationCurve: Curves.easeInOutCubic,
|
themeAnimationCurve: Curves.easeInOutCubic,
|
||||||
home: CoolSplashScreen(
|
home: CoolSplashScreen(
|
||||||
nextScreen: const OnboardingPage(),
|
nextScreen: const SimpleAuthGate(),
|
||||||
duration: const Duration(seconds: 6),
|
duration: const Duration(seconds: 3),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,58 +1,90 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:google_sign_in/google_sign_in.dart';
|
import 'package:google_sign_in/google_sign_in.dart';
|
||||||
import 'package:firebase_auth/firebase_auth.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_event.dart';
|
||||||
part 'auth_state.dart';
|
part 'auth_state.dart';
|
||||||
|
|
||||||
class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
||||||
final SendOTP sendOTPUseCase;
|
|
||||||
final VerifyOTP verifyOTPUseCase;
|
|
||||||
final FirebaseAuth _firebaseAuth = FirebaseAuth.instance;
|
final FirebaseAuth _firebaseAuth = FirebaseAuth.instance;
|
||||||
|
String? _verificationId;
|
||||||
|
|
||||||
String _timeStamp = "";
|
AuthBloc() : super(AuthInitial()) {
|
||||||
String _timeDue = "";
|
|
||||||
|
|
||||||
String get timeStamp => _timeStamp;
|
|
||||||
String get timeDue => _timeDue;
|
|
||||||
|
|
||||||
AuthBloc({
|
|
||||||
required this.sendOTPUseCase,
|
|
||||||
required this.verifyOTPUseCase,
|
|
||||||
}) : super(AuthInitial()) {
|
|
||||||
on<SendOTPEvent>(_onSendOTP);
|
on<SendOTPEvent>(_onSendOTP);
|
||||||
on<VerifyOTPEvent>(_onVerifyOTP);
|
on<VerifyOTPEvent>(_onVerifyOTP);
|
||||||
on<SignInWithGoogleEvent>(_onSignInWithGoogle);
|
on<SignInWithGoogleEvent>(_onSignInWithGoogle);
|
||||||
|
on<SignOutEvent>(_onSignOut);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onSendOTP(SendOTPEvent event, Emitter<AuthState> emit) async {
|
Future<void> _onSendOTP(SendOTPEvent event, Emitter<AuthState> emit) async {
|
||||||
emit(AuthLoading());
|
emit(AuthLoading());
|
||||||
final result = await sendOTPUseCase(SendOTPParams(phoneNumber: event.phoneNumber));
|
final completer = Completer<void>();
|
||||||
if (result.isSuccess && result.data != null) {
|
try {
|
||||||
_timeStamp = result.data!.timeStamp;
|
await _firebaseAuth.verifyPhoneNumber(
|
||||||
_timeDue = result.data!.timeDue;
|
|
||||||
emit(AuthSuccess(
|
|
||||||
phoneNumber: event.phoneNumber,
|
phoneNumber: event.phoneNumber,
|
||||||
timeStamp: _timeStamp,
|
verificationCompleted: (PhoneAuthCredential credential) async {
|
||||||
timeDue: _timeDue,
|
debugPrint("✅ Verification Completed (Auto-retrieval)");
|
||||||
));
|
await _firebaseAuth.signInWithCredential(credential);
|
||||||
} else {
|
if (!completer.isCompleted) {
|
||||||
emit(AuthError('Error sending code. Please try again.'));
|
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<void> _onVerifyOTP(VerifyOTPEvent event, Emitter<AuthState> emit) async {
|
Future<void> _onVerifyOTP(VerifyOTPEvent event, Emitter<AuthState> emit) async {
|
||||||
emit(AuthLoading());
|
emit(AuthLoading());
|
||||||
final result = await verifyOTPUseCase(VerifyOTPParams(
|
try {
|
||||||
otpCode: event.otpCode,
|
if (_verificationId != null) {
|
||||||
phoneNumber: event.phoneNumber,
|
final credential = PhoneAuthProvider.credential(
|
||||||
));
|
verificationId: _verificationId!,
|
||||||
if (result.isSuccess && result.data == true) {
|
smsCode: event.otpCode,
|
||||||
emit(OTPVerified());
|
);
|
||||||
} else {
|
|
||||||
emit(AuthError('The code entered is incorrect. Please try again.'));
|
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<AuthEvent, AuthState> {
|
||||||
SignInWithGoogleEvent event,
|
SignInWithGoogleEvent event,
|
||||||
Emitter<AuthState> emit,
|
Emitter<AuthState> emit,
|
||||||
) async {
|
) async {
|
||||||
emit(AuthLoading());
|
emit(AuthLoading());
|
||||||
try {
|
try {
|
||||||
final GoogleSignIn googleSignIn = GoogleSignIn(
|
final GoogleSignIn googleSignIn = GoogleSignIn(
|
||||||
scopes: ['email'],
|
scopes: ['email'],
|
||||||
|
|
@ -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()}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -18,4 +18,6 @@ class VerifyOTPEvent extends AuthEvent {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
class SignInWithGoogleEvent extends AuthEvent {}
|
class SignInWithGoogleEvent extends AuthEvent {}
|
||||||
|
|
||||||
|
class SignOutEvent extends AuthEvent {}
|
||||||
|
|
@ -6,21 +6,13 @@ class AuthInitial extends AuthState {}
|
||||||
|
|
||||||
class AuthLoading extends AuthState {}
|
class AuthLoading extends AuthState {}
|
||||||
|
|
||||||
class AuthSuccess extends AuthState {
|
class AuthCodeSent extends AuthState {
|
||||||
final String phoneNumber;
|
final String phoneNumber;
|
||||||
final String timeStamp;
|
AuthCodeSent({required this.phoneNumber});
|
||||||
final String timeDue;
|
|
||||||
|
|
||||||
AuthSuccess({
|
|
||||||
required this.phoneNumber,
|
|
||||||
required this.timeStamp,
|
|
||||||
required this.timeDue,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class AuthError extends AuthState {
|
class AuthError extends AuthState {
|
||||||
final String message;
|
final String message;
|
||||||
|
|
||||||
AuthError(this.message);
|
AuthError(this.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -91,9 +91,7 @@ class _LoginPageState extends State<LoginPage> {
|
||||||
),
|
),
|
||||||
border: OutlineInputBorder(
|
border: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(5),
|
borderRadius: BorderRadius.circular(5),
|
||||||
borderSide: BorderSide(
|
borderSide: BorderSide(color: AppColors.borderPrimary),
|
||||||
color: AppColors.borderPrimary,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
focusedBorder: OutlineInputBorder(
|
focusedBorder: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(5),
|
borderRadius: BorderRadius.circular(5),
|
||||||
|
|
@ -142,37 +140,40 @@ class _LoginPageState extends State<LoginPage> {
|
||||||
// keepSignedIn = !keepSignedIn;
|
// keepSignedIn = !keepSignedIn;
|
||||||
// });
|
// });
|
||||||
// },
|
// },
|
||||||
// child: Row(
|
// child: Row(
|
||||||
// mainAxisAlignment: MainAxisAlignment.start,
|
// mainAxisAlignment: MainAxisAlignment.start,
|
||||||
// children: [
|
// children: [
|
||||||
// Checkbox(
|
// Checkbox(
|
||||||
// value: keepSignedIn,
|
// value: keepSignedIn,
|
||||||
// activeColor: AppColors.borderPrimary,
|
// activeColor: AppColors.borderPrimary,
|
||||||
// onChanged: (bool? value) {
|
// onChanged: (bool? value) {
|
||||||
// setState(() {
|
// setState(() {
|
||||||
// keepSignedIn = value ?? false;
|
// keepSignedIn = value ?? false;
|
||||||
// });
|
// });
|
||||||
// },
|
// },
|
||||||
// ),
|
// ),
|
||||||
// const Text(
|
// const Text(
|
||||||
// "Keep me signed in",
|
// "Keep me signed in",
|
||||||
// style: TextStyle(fontSize: 15),
|
// style: TextStyle(fontSize: 15),
|
||||||
// ),
|
// ),
|
||||||
// ],
|
// ],
|
||||||
// ),
|
// ),
|
||||||
// ),
|
// ),
|
||||||
SizedBox(height: height / 60),
|
SizedBox(height: height / 60),
|
||||||
BlocConsumer<AuthBloc, AuthState>(
|
BlocConsumer<AuthBloc, AuthState>(
|
||||||
listener: (context, state) {
|
listener: (context, state) {
|
||||||
if (state is AuthSuccess) {
|
// اینجا رو تغییر بده
|
||||||
|
if (state is AuthCodeSent) {
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder:
|
builder:
|
||||||
(context) => OTPVerificationPage(
|
(context) => OTPVerificationPage(
|
||||||
phoneNumber:
|
phoneNumber: state.phoneNumber,
|
||||||
"+${_selectedCountry.phoneCode}${phoneController.text}",
|
timeDue:
|
||||||
timeDue: state.timeDue,
|
DateTime.now()
|
||||||
|
.add(const Duration(minutes: 2))
|
||||||
|
.toIso8601String(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -219,7 +220,10 @@ class _LoginPageState extends State<LoginPage> {
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Divider(thickness: 1, color: AppColors.greyBorder),
|
child: Divider(
|
||||||
|
thickness: 1,
|
||||||
|
color: AppColors.greyBorder,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.symmetric(
|
padding: EdgeInsets.symmetric(
|
||||||
|
|
@ -234,7 +238,10 @@ class _LoginPageState extends State<LoginPage> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
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: () {
|
onPressed: () {
|
||||||
context.read<AuthBloc>().add(SignInWithGoogleEvent());
|
context.read<AuthBloc>().add(
|
||||||
|
SignInWithGoogleEvent(),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -277,14 +286,15 @@ class _LoginPageState extends State<LoginPage> {
|
||||||
void _openCountryPicker(BuildContext context) {
|
void _openCountryPicker(BuildContext context) {
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => CustomCountryPicker(
|
builder:
|
||||||
onCountrySelected: (Country country) {
|
(context) => CustomCountryPicker(
|
||||||
setState(() {
|
onCountrySelected: (Country country) {
|
||||||
_selectedCountry = country;
|
setState(() {
|
||||||
countryController.text = country.name;
|
_selectedCountry = country;
|
||||||
});
|
countryController.text = country.name;
|
||||||
},
|
});
|
||||||
),
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ import 'package:lba/screens/auth/bloc/auth_bloc.dart';
|
||||||
import 'package:lba/widgets/app_snackbar.dart';
|
import 'package:lba/widgets/app_snackbar.dart';
|
||||||
import '../../gen/assets.gen.dart';
|
import '../../gen/assets.gen.dart';
|
||||||
import '../../widgets/button.dart';
|
import '../../widgets/button.dart';
|
||||||
import '../../widgets/remainingTime.dart';
|
|
||||||
import 'user_info_page.dart';
|
import 'user_info_page.dart';
|
||||||
|
|
||||||
class OTPVerificationPage extends StatefulWidget {
|
class OTPVerificationPage extends StatefulWidget {
|
||||||
|
|
@ -24,29 +23,8 @@ class OTPVerificationPage extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _OTPVerificationPageState extends State<OTPVerificationPage> {
|
class _OTPVerificationPageState extends State<OTPVerificationPage> {
|
||||||
final List<FocusNode> _focusNodes = List.generate(5, (_) => FocusNode());
|
final List<FocusNode> _focusNodes = List.generate(6, (_) => FocusNode());
|
||||||
final List<TextEditingController> _controllers =
|
final List<TextEditingController> _controllers = List.generate(6, (_) => TextEditingController());
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
|
@ -56,16 +34,15 @@ class _OTPVerificationPageState extends State<OTPVerificationPage> {
|
||||||
for (final controller in _controllers) {
|
for (final controller in _controllers) {
|
||||||
controller.dispose();
|
controller.dispose();
|
||||||
}
|
}
|
||||||
_otpTimer.dispose();
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildOTPFields() {
|
Widget _buildOTPFields() {
|
||||||
return Row(
|
return Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: List.generate(5, (index) {
|
children: List.generate(6, (index) {
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
width: 60,
|
width: 50,
|
||||||
child: TextField(
|
child: TextField(
|
||||||
controller: _controllers[index],
|
controller: _controllers[index],
|
||||||
focusNode: _focusNodes[index],
|
focusNode: _focusNodes[index],
|
||||||
|
|
@ -81,14 +58,16 @@ class _OTPVerificationPageState extends State<OTPVerificationPage> {
|
||||||
),
|
),
|
||||||
focusedBorder: OutlineInputBorder(
|
focusedBorder: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
borderSide: BorderSide(
|
borderSide: BorderSide(color: AppColors.borderPrimary),
|
||||||
color: AppColors.borderPrimary),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
if (value.length == 1 && index < 4) {
|
if (value.length == 1 && index < 5) {
|
||||||
FocusScope.of(context).requestFocus(_focusNodes[index + 1]);
|
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<OTPVerificationPage> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final height = MediaQuery.of(context).size.height;
|
final height = MediaQuery.of(context).size.height;
|
||||||
|
|
||||||
return BlocListener<AuthBloc, AuthState>(
|
return Scaffold(
|
||||||
listener: (context, state) {
|
body: SafeArea(
|
||||||
if (state is AuthSuccess) {
|
child: SingleChildScrollView(
|
||||||
_otpTimer.initializeFromExpiry(expiryTime: state.timeDue);
|
child: Column(
|
||||||
}
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
},
|
children: [
|
||||||
child: Scaffold(
|
Row(
|
||||||
body: SafeArea(
|
children: [
|
||||||
child: SingleChildScrollView(
|
IconButton(
|
||||||
child: Column(
|
icon: SvgPicture.asset(Assets.icons.back.path),
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
onPressed: () => Navigator.pop(context),
|
||||||
children: [
|
),
|
||||||
Row(
|
],
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 24),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
IconButton(
|
Center(
|
||||||
icon: SvgPicture.asset(Assets.icons.back.path),
|
child: Padding(
|
||||||
onPressed: () => Navigator.pop(context),
|
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<AuthBloc, AuthState>(
|
||||||
|
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<AuthBloc>().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<AuthBloc>().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<AuthBloc, AuthState>(
|
|
||||||
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<AuthBloc>().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<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),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
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",
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
color: canResend
|
|
||||||
? const Color.fromARGB(
|
|
||||||
255, 0, 0, 0)
|
|
||||||
: AppColors.greyBorder,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -1224,7 +1224,7 @@ class FirstPurchaseCard extends StatelessWidget {
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
SvgPicture.asset(Assets.icons.star.path,
|
SvgPicture.asset(Assets.icons.star.path,
|
||||||
width: 16, color: AppColors.textSecondary),
|
width: 16,),
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
Text(
|
Text(
|
||||||
rating.toString(),
|
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:flutter_svg/flutter_svg.dart';
|
||||||
import 'package:lba/gen/assets.gen.dart';
|
import 'package:lba/gen/assets.gen.dart';
|
||||||
import 'package:lba/res/colors.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 {
|
Future<void> showLogoutDialog(BuildContext context) async {
|
||||||
showDialog(
|
showDialog(
|
||||||
|
|
@ -39,10 +41,7 @@ class _AnimatedLogoutDialogState extends State<_AnimatedLogoutDialog>
|
||||||
curve: Curves.elasticOut,
|
curve: Curves.elasticOut,
|
||||||
);
|
);
|
||||||
|
|
||||||
_fadeAnimation = CurvedAnimation(
|
_fadeAnimation = CurvedAnimation(parent: _controller, curve: Curves.easeIn);
|
||||||
parent: _controller,
|
|
||||||
curve: Curves.easeIn,
|
|
||||||
);
|
|
||||||
|
|
||||||
_controller.forward();
|
_controller.forward();
|
||||||
}
|
}
|
||||||
|
|
@ -60,8 +59,9 @@ class _AnimatedLogoutDialogState extends State<_AnimatedLogoutDialog>
|
||||||
child: ScaleTransition(
|
child: ScaleTransition(
|
||||||
scale: _scaleAnimation,
|
scale: _scaleAnimation,
|
||||||
child: Dialog(
|
child: Dialog(
|
||||||
shape:
|
shape: RoundedRectangleBorder(
|
||||||
RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)),
|
borderRadius: BorderRadius.circular(15),
|
||||||
|
),
|
||||||
elevation: 10,
|
elevation: 10,
|
||||||
backgroundColor: AppColors.surface,
|
backgroundColor: AppColors.surface,
|
||||||
child: Stack(
|
child: Stack(
|
||||||
|
|
@ -69,8 +69,12 @@ class _AnimatedLogoutDialogState extends State<_AnimatedLogoutDialog>
|
||||||
alignment: Alignment.topCenter,
|
alignment: Alignment.topCenter,
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(
|
padding: EdgeInsets.only(
|
||||||
top: 50, left: 20, right: 20, bottom: 20),
|
top: 50,
|
||||||
|
left: 20,
|
||||||
|
right: 20,
|
||||||
|
bottom: 20,
|
||||||
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
|
@ -109,12 +113,20 @@ class _AnimatedLogoutDialogState extends State<_AnimatedLogoutDialog>
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
side: BorderSide(color: AppColors.offerTimer),
|
side: BorderSide(color: AppColors.offerTimer),
|
||||||
),
|
),
|
||||||
padding:
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
const EdgeInsets.symmetric(vertical: 12),
|
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () async {
|
||||||
// TODO: Add actual logout logic here
|
Navigator.of(context).pop();
|
||||||
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(
|
child: Text(
|
||||||
"Log Out",
|
"Log Out",
|
||||||
|
|
@ -132,8 +144,7 @@ class _AnimatedLogoutDialogState extends State<_AnimatedLogoutDialog>
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
),
|
),
|
||||||
side: BorderSide(color: AppColors.greyBorder),
|
side: BorderSide(color: AppColors.greyBorder),
|
||||||
padding:
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
const EdgeInsets.symmetric(vertical: 12),
|
|
||||||
),
|
),
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
child: const Text("Cancel"),
|
child: const Text("Cancel"),
|
||||||
|
|
@ -163,7 +174,9 @@ class _AnimatedLogoutDialogState extends State<_AnimatedLogoutDialog>
|
||||||
radius: 40,
|
radius: 40,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(12.0),
|
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>
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,6 @@ class IsOpenChecker {
|
||||||
final nowTime = Duration(hours: now.hour, minutes: now.minute);
|
final nowTime = Duration(hours: now.hour, minutes: now.minute);
|
||||||
|
|
||||||
if (closeTime <= openTime) {
|
if (closeTime <= openTime) {
|
||||||
// شیفت شبانه (مثلاً 22:00 تا 02:00)
|
|
||||||
return nowTime >= openTime || nowTime <= closeTime;
|
return nowTime >= openTime || nowTime <= closeTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue