This commit is contained in:
Mohammed Al-Samarraie
2026-01-13 15:06:59 +03:00
parent fa4bee4771
commit cefd2397fe
8 changed files with 196 additions and 74 deletions

View File

@@ -7,6 +7,7 @@ import '../../data/datasources/user_local_data_source.dart';
import '../../data/repositories/auth_repository_impl.dart'; import '../../data/repositories/auth_repository_impl.dart';
import '../../domain/repositories/auth_repository.dart'; import '../../domain/repositories/auth_repository.dart';
import '../../domain/usecases/login_usecase.dart'; import '../../domain/usecases/login_usecase.dart';
import '../../presentation/blocs/login/login_bloc.dart';
final sl = GetIt.instance; final sl = GetIt.instance;
@@ -43,7 +44,6 @@ Future<void> initializeDependencies() async {
// Use cases // Use cases
sl.registerLazySingleton(() => LoginUseCase(repository: sl())); sl.registerLazySingleton(() => LoginUseCase(repository: sl()));
// Blocs will be registered here // Blocs
// Example: sl.registerFactory(() => LoginBloc(loginUseCase: sl()));
// sl.registerFactory(() => LoginBloc(loginUseCase: sl()));
} }

View File

@@ -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<LoginEvent, LoginState> {
final LoginUseCase loginUseCase;
LoginBloc({required this.loginUseCase}) : super(const LoginInitial()) {
on<LoginSubmitted>(_onLoginSubmitted);
on<LoginReset>(_onLoginReset);
}
Future<void> _onLoginSubmitted(
LoginSubmitted event,
Emitter<LoginState> 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<LoginState> emit,
) {
emit(const LoginInitial());
}
}

View File

@@ -0,0 +1,22 @@
import 'package:equatable/equatable.dart';
import '../../../domain/models/login_request.dart';
abstract class LoginEvent extends Equatable {
const LoginEvent();
@override
List<Object> get props => [];
}
class LoginSubmitted extends LoginEvent {
final LoginRequest request;
const LoginSubmitted(this.request);
@override
List<Object> get props => [request];
}
class LoginReset extends LoginEvent {
const LoginReset();
}

View File

@@ -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<Object> 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<Object> get props => [response];
}
class LoginError extends LoginState {
final String message;
const LoginError(this.message);
@override
List<Object> get props => [message];
}

View File

@@ -1,25 +1,31 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../widgets/app_background.dart'; import '../widgets/app_background.dart';
import '../widgets/auth_form.dart'; import '../widgets/auth_form.dart';
import '../core/di/injection_container.dart';
import '../presentation/blocs/login/login_bloc.dart';
class AuthScreen extends StatelessWidget { class AuthScreen extends StatelessWidget {
const AuthScreen({super.key}); const AuthScreen({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return BlocProvider(
resizeToAvoidBottomInset: false, create: (context) => sl<LoginBloc>(),
body: AppBackground( child: Scaffold(
child: SafeArea( resizeToAvoidBottomInset: false,
child: Column( body: AppBackground(
children: [ child: SafeArea(
const SizedBox(height: 60), child: Column(
// Logo children: [
Center(child: Image.asset("assets/images/logo2.png", width: 200)), const SizedBox(height: 60),
// const SizedBox(height: 15), // Logo
// Form - taking remaining space and centered Center(child: Image.asset("assets/images/logo2.png", width: 200)),
Expanded(child: Center(child: const AuthForm())), // const SizedBox(height: 15),
], // Form - taking remaining space and centered
Expanded(child: Center(child: const AuthForm())),
],
),
), ),
), ),
), ),

View File

@@ -1,8 +1,10 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../screens/main_screen.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 '../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'; import 'onboarding_button.dart';
class AuthForm extends StatefulWidget { class AuthForm extends StatefulWidget {
@@ -16,7 +18,6 @@ class AuthForm extends StatefulWidget {
class _AuthFormState extends State<AuthForm> { class _AuthFormState extends State<AuthForm> {
bool _obscure = true; bool _obscure = true;
bool _isLoading = false;
// Text controllers // Text controllers
final TextEditingController _phoneNumberController = TextEditingController(); final TextEditingController _phoneNumberController = TextEditingController();
@@ -26,10 +27,7 @@ class _AuthFormState extends State<AuthForm> {
late FocusNode _phoneNumberFocusNode; late FocusNode _phoneNumberFocusNode;
late FocusNode _passwordFocusNode; late FocusNode _passwordFocusNode;
// Get LoginUseCase from dependency injection void _handleLogin() {
final LoginUseCase _loginUseCase = sl<LoginUseCase>();
Future<void> _handleLogin() async {
// Validate inputs // Validate inputs
if (_phoneNumberController.text.trim().isEmpty) { if (_phoneNumberController.text.trim().isEmpty) {
_showError('الرجاء إدخال رقم الهاتف'); _showError('الرجاء إدخال رقم الهاتف');
@@ -45,48 +43,13 @@ class _AuthFormState extends State<AuthForm> {
_phoneNumberFocusNode.unfocus(); _phoneNumberFocusNode.unfocus();
_passwordFocusNode.unfocus(); _passwordFocusNode.unfocus();
setState(() { // Dispatch login event
_isLoading = true; final request = LoginRequest(
}); phoneNumber: _phoneNumberController.text.trim(),
password: _passwordController.text.trim(),
);
try { context.read<LoginBloc>().add(LoginSubmitted(request));
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;
});
}
}
} }
void _showError(String message) { void _showError(String message) {
@@ -139,10 +102,27 @@ class _AuthFormState extends State<AuthForm> {
final horizontalPadding = screenWidth > 600 ? 30.0 : 25.0; final horizontalPadding = screenWidth > 600 ? 30.0 : 25.0;
final verticalPadding = screenHeight > 800 ? 38.0 : 28.0; final verticalPadding = screenHeight > 800 ? 38.0 : 28.0;
return Directionality( return BlocListener<LoginBloc, LoginState>(
textDirection: TextDirection.rtl, listener: (context, state) {
child: FocusScope( if (state is LoginSuccess) {
child: Stack( // 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, alignment: Alignment.center,
children: [ children: [
// Border container - decorative element behind the form // Border container - decorative element behind the form
@@ -251,12 +231,17 @@ class _AuthFormState extends State<AuthForm> {
SizedBox(height: buttonSpacing), // Responsive spacing SizedBox(height: buttonSpacing), // Responsive spacing
Center( BlocBuilder<LoginBloc, LoginState>(
child: OnboardingButton( builder: (context, state) {
text: _isLoading ? "جاري تسجيل الدخول..." : "تسجيل دخول", final isLoading = state is LoginLoading;
backgroundColor: const Color.fromARGB(239, 35, 87, 74), return Center(
onPressed: _isLoading ? null : _handleLogin, child: OnboardingButton(
), text: isLoading ? "جاري تسجيل الدخول..." : "تسجيل دخول",
backgroundColor: const Color.fromARGB(239, 35, 87, 74),
onPressed: isLoading ? null : _handleLogin,
),
);
},
), ),
SizedBox(height: bottomSpacing), SizedBox(height: bottomSpacing),
@@ -266,6 +251,7 @@ class _AuthFormState extends State<AuthForm> {
], ],
), ),
), ),
),
); );
} }

View File

@@ -33,6 +33,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.13.0" version: "2.13.0"
bloc:
dependency: transitive
description:
name: bloc
sha256: "106842ad6569f0b60297619e9e0b1885c2fb9bf84812935490e6c5275777804e"
url: "https://pub.dev"
source: hosted
version: "8.1.4"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive
description: description:
@@ -206,6 +214,14 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" 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: flutter_launcher_icons:
dependency: "direct dev" dependency: "direct dev"
description: description:
@@ -368,6 +384,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.0" version: "2.0.0"
nested:
dependency: transitive
description:
name: nested
sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
path: path:
dependency: transitive dependency: transitive
description: description:
@@ -440,6 +464,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.0.3" 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: shared_preferences:
dependency: "direct main" dependency: "direct main"
description: description:

View File

@@ -17,6 +17,7 @@ dependencies:
dartz: ^0.10.1 dartz: ^0.10.1
equatable: ^2.0.5 equatable: ^2.0.5
shared_preferences: ^2.2.2 shared_preferences: ^2.2.2
flutter_bloc: ^8.1.6
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: