diff --git a/lib/core/di/injection_container.dart b/lib/core/di/injection_container.dart index 3bb5b7e..11b6b25 100644 --- a/lib/core/di/injection_container.dart +++ b/lib/core/di/injection_container.dart @@ -7,6 +7,7 @@ import '../../data/datasources/user_local_data_source.dart'; import '../../data/repositories/auth_repository_impl.dart'; import '../../domain/repositories/auth_repository.dart'; import '../../domain/usecases/login_usecase.dart'; +import '../../presentation/blocs/login/login_bloc.dart'; final sl = GetIt.instance; @@ -43,7 +44,6 @@ Future initializeDependencies() async { // Use cases sl.registerLazySingleton(() => LoginUseCase(repository: sl())); - // Blocs will be registered here - // Example: - // sl.registerFactory(() => LoginBloc(loginUseCase: sl())); + // Blocs + sl.registerFactory(() => LoginBloc(loginUseCase: sl())); } diff --git a/lib/presentation/blocs/login/login_bloc.dart b/lib/presentation/blocs/login/login_bloc.dart new file mode 100644 index 0000000..0603344 --- /dev/null +++ b/lib/presentation/blocs/login/login_bloc.dart @@ -0,0 +1,40 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import '../../../domain/usecases/login_usecase.dart'; +import 'login_event.dart'; +import 'login_state.dart'; + +class LoginBloc extends Bloc { + final LoginUseCase loginUseCase; + + LoginBloc({required this.loginUseCase}) : super(const LoginInitial()) { + on(_onLoginSubmitted); + on(_onLoginReset); + } + + Future _onLoginSubmitted( + LoginSubmitted event, + Emitter emit, + ) async { + emit(const LoginLoading()); + + final result = await loginUseCase(event.request); + + result.fold( + (failure) => emit(LoginError(failure.message)), + (response) { + if (response.isSuccess) { + emit(LoginSuccess(response)); + } else { + emit(LoginError(response.message)); + } + }, + ); + } + + void _onLoginReset( + LoginReset event, + Emitter emit, + ) { + emit(const LoginInitial()); + } +} diff --git a/lib/presentation/blocs/login/login_event.dart b/lib/presentation/blocs/login/login_event.dart new file mode 100644 index 0000000..96dd716 --- /dev/null +++ b/lib/presentation/blocs/login/login_event.dart @@ -0,0 +1,22 @@ +import 'package:equatable/equatable.dart'; +import '../../../domain/models/login_request.dart'; + +abstract class LoginEvent extends Equatable { + const LoginEvent(); + + @override + List get props => []; +} + +class LoginSubmitted extends LoginEvent { + final LoginRequest request; + + const LoginSubmitted(this.request); + + @override + List get props => [request]; +} + +class LoginReset extends LoginEvent { + const LoginReset(); +} diff --git a/lib/presentation/blocs/login/login_state.dart b/lib/presentation/blocs/login/login_state.dart new file mode 100644 index 0000000..79c0ab6 --- /dev/null +++ b/lib/presentation/blocs/login/login_state.dart @@ -0,0 +1,35 @@ +import 'package:equatable/equatable.dart'; +import '../../../domain/models/login_response_model.dart'; + +abstract class LoginState extends Equatable { + const LoginState(); + + @override + List get props => []; +} + +class LoginInitial extends LoginState { + const LoginInitial(); +} + +class LoginLoading extends LoginState { + const LoginLoading(); +} + +class LoginSuccess extends LoginState { + final LoginResponseModel response; + + const LoginSuccess(this.response); + + @override + List get props => [response]; +} + +class LoginError extends LoginState { + final String message; + + const LoginError(this.message); + + @override + List get props => [message]; +} diff --git a/lib/screens/auth_screen.dart b/lib/screens/auth_screen.dart index 28a5233..e739f0d 100644 --- a/lib/screens/auth_screen.dart +++ b/lib/screens/auth_screen.dart @@ -1,25 +1,31 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import '../widgets/app_background.dart'; import '../widgets/auth_form.dart'; +import '../core/di/injection_container.dart'; +import '../presentation/blocs/login/login_bloc.dart'; class AuthScreen extends StatelessWidget { const AuthScreen({super.key}); @override Widget build(BuildContext context) { - return Scaffold( - resizeToAvoidBottomInset: false, - body: AppBackground( - child: SafeArea( - child: Column( - children: [ - const SizedBox(height: 60), - // Logo - Center(child: Image.asset("assets/images/logo2.png", width: 200)), - // const SizedBox(height: 15), - // Form - taking remaining space and centered - Expanded(child: Center(child: const AuthForm())), - ], + return BlocProvider( + create: (context) => sl(), + child: Scaffold( + resizeToAvoidBottomInset: false, + body: AppBackground( + child: SafeArea( + child: Column( + children: [ + const SizedBox(height: 60), + // Logo + Center(child: Image.asset("assets/images/logo2.png", width: 200)), + // const SizedBox(height: 15), + // Form - taking remaining space and centered + Expanded(child: Center(child: const AuthForm())), + ], + ), ), ), ), diff --git a/lib/widgets/auth_form.dart b/lib/widgets/auth_form.dart index 99d049e..5f7c3e6 100644 --- a/lib/widgets/auth_form.dart +++ b/lib/widgets/auth_form.dart @@ -1,8 +1,10 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import '../screens/main_screen.dart'; -import '../core/di/injection_container.dart'; -import '../domain/usecases/login_usecase.dart'; import '../domain/models/login_request.dart'; +import '../presentation/blocs/login/login_bloc.dart'; +import '../presentation/blocs/login/login_event.dart'; +import '../presentation/blocs/login/login_state.dart'; import 'onboarding_button.dart'; class AuthForm extends StatefulWidget { @@ -16,7 +18,6 @@ class AuthForm extends StatefulWidget { class _AuthFormState extends State { bool _obscure = true; - bool _isLoading = false; // Text controllers final TextEditingController _phoneNumberController = TextEditingController(); @@ -26,10 +27,7 @@ class _AuthFormState extends State { late FocusNode _phoneNumberFocusNode; late FocusNode _passwordFocusNode; - // Get LoginUseCase from dependency injection - final LoginUseCase _loginUseCase = sl(); - - Future _handleLogin() async { + void _handleLogin() { // Validate inputs if (_phoneNumberController.text.trim().isEmpty) { _showError('الرجاء إدخال رقم الهاتف'); @@ -45,48 +43,13 @@ class _AuthFormState extends State { _phoneNumberFocusNode.unfocus(); _passwordFocusNode.unfocus(); - setState(() { - _isLoading = true; - }); + // Dispatch login event + final request = LoginRequest( + phoneNumber: _phoneNumberController.text.trim(), + password: _passwordController.text.trim(), + ); - try { - final request = LoginRequest( - phoneNumber: _phoneNumberController.text.trim(), - password: _passwordController.text.trim(), - ); - - final result = await _loginUseCase(request); - - result.fold( - (failure) { - _showError(failure.message); - }, - (response) { - if (response.isSuccess) { - // Call the onSubmit callback if provided - if (widget.onSubmit != null) { - widget.onSubmit!(); - } - - // Navigate to the MainPage - Navigator.pushReplacement( - context, - MaterialPageRoute(builder: (context) => const MainPage()), - ); - } else { - _showError(response.message); - } - }, - ); - } catch (e) { - _showError('حدث خطأ غير متوقع: $e'); - } finally { - if (mounted) { - setState(() { - _isLoading = false; - }); - } - } + context.read().add(LoginSubmitted(request)); } void _showError(String message) { @@ -139,10 +102,27 @@ class _AuthFormState extends State { final horizontalPadding = screenWidth > 600 ? 30.0 : 25.0; final verticalPadding = screenHeight > 800 ? 38.0 : 28.0; - return Directionality( - textDirection: TextDirection.rtl, - child: FocusScope( - child: Stack( + return BlocListener( + listener: (context, state) { + if (state is LoginSuccess) { + // Call the onSubmit callback if provided + if (widget.onSubmit != null) { + widget.onSubmit!(); + } + + // Navigate to the MainPage + Navigator.pushReplacement( + context, + MaterialPageRoute(builder: (context) => const MainPage()), + ); + } else if (state is LoginError) { + _showError(state.message); + } + }, + child: Directionality( + textDirection: TextDirection.rtl, + child: FocusScope( + child: Stack( alignment: Alignment.center, children: [ // Border container - decorative element behind the form @@ -251,12 +231,17 @@ class _AuthFormState extends State { SizedBox(height: buttonSpacing), // Responsive spacing - Center( - child: OnboardingButton( - text: _isLoading ? "جاري تسجيل الدخول..." : "تسجيل دخول", - backgroundColor: const Color.fromARGB(239, 35, 87, 74), - onPressed: _isLoading ? null : _handleLogin, - ), + BlocBuilder( + builder: (context, state) { + final isLoading = state is LoginLoading; + return Center( + child: OnboardingButton( + text: isLoading ? "جاري تسجيل الدخول..." : "تسجيل دخول", + backgroundColor: const Color.fromARGB(239, 35, 87, 74), + onPressed: isLoading ? null : _handleLogin, + ), + ); + }, ), SizedBox(height: bottomSpacing), @@ -266,6 +251,7 @@ class _AuthFormState extends State { ], ), ), + ), ); } diff --git a/pubspec.lock b/pubspec.lock index e309ec4..522c33c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -33,6 +33,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.13.0" + bloc: + dependency: transitive + description: + name: bloc + sha256: "106842ad6569f0b60297619e9e0b1885c2fb9bf84812935490e6c5275777804e" + url: "https://pub.dev" + source: hosted + version: "8.1.4" boolean_selector: dependency: transitive description: @@ -206,6 +214,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_bloc: + dependency: "direct main" + description: + name: flutter_bloc + sha256: b594505eac31a0518bdcb4b5b79573b8d9117b193cc80cc12e17d639b10aa27a + url: "https://pub.dev" + source: hosted + version: "8.1.6" flutter_launcher_icons: dependency: "direct dev" description: @@ -368,6 +384,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.0" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" path: dependency: transitive description: @@ -440,6 +464,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.0.3" + provider: + dependency: transitive + description: + name: provider + sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272" + url: "https://pub.dev" + source: hosted + version: "6.1.5+1" shared_preferences: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 547495d..fc826a9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,6 +17,7 @@ dependencies: dartz: ^0.10.1 equatable: ^2.0.5 shared_preferences: ^2.2.2 + flutter_bloc: ^8.1.6 dev_dependencies: flutter_test: