From d3ad5b8dddc9b4570d681078108c51998709ab36 Mon Sep 17 00:00:00 2001 From: jahongireshonqulov Date: Tue, 28 Oct 2025 20:08:53 +0500 Subject: [PATCH] feat:login page connected with backend --- lib/core/constants/api_const.dart | 2 + lib/core/di/injection_container.config.dart | 19 ++++++- lib/core/network/dio_client.dart | 5 -- .../auth/data/datasource/auth_datasource.dart | 30 ++++++++++ .../data/models/response/login_response.dart | 26 +++++++++ .../domain/repository/auth_repository.dart | 35 ++++++++++++ .../auth/domain/usecases/login_usecase.dart | 26 +++++++++ .../blocs/login_bloc/login_bloc.dart | 18 +++++- .../blocs/login_bloc/login_bloc.freezed.dart | 56 +++++++++++++++---- .../blocs/login_bloc/login_event.dart | 2 +- .../pages/login_page/widgets/login_body.dart | 17 +++++- .../common/data/models/error_model.dart | 2 +- 12 files changed, 216 insertions(+), 22 deletions(-) create mode 100644 lib/feature/auth/data/models/response/login_response.dart create mode 100644 lib/feature/auth/domain/repository/auth_repository.dart create mode 100644 lib/feature/auth/domain/usecases/login_usecase.dart diff --git a/lib/core/constants/api_const.dart b/lib/core/constants/api_const.dart index 1aecd81..9dff8c0 100644 --- a/lib/core/constants/api_const.dart +++ b/lib/core/constants/api_const.dart @@ -1,3 +1,5 @@ abstract class ApiConst { static const String baseUrl = "https://superapp.felixits.uz/api/v1"; + + static const String login = "/auth/login"; } diff --git a/lib/core/di/injection_container.config.dart b/lib/core/di/injection_container.config.dart index 97ab938..a95edaf 100644 --- a/lib/core/di/injection_container.config.dart +++ b/lib/core/di/injection_container.config.dart @@ -13,6 +13,9 @@ import 'package:dio/dio.dart' as _i361; import 'package:get_it/get_it.dart' as _i174; import 'package:injectable/injectable.dart' as _i526; +import '../../feature/auth/data/datasource/auth_datasource.dart' as _i246; +import '../../feature/auth/domain/repository/auth_repository.dart' as _i884; +import '../../feature/auth/domain/usecases/login_usecase.dart' as _i241; import '../../feature/auth/presentation/blocs/login_bloc/login_bloc.dart' as _i1065; import '../../feature/basket/presentation/blocs/basket_bloc.dart' as _i728; @@ -42,7 +45,6 @@ extension GetItInjectableX on _i174.GetIt { final dioModule = _$DioModule(); gh.factory<_i1007.HomeBloc>(() => _i1007.HomeBloc()); gh.factory<_i728.BasketBloc>(() => _i728.BasketBloc()); - gh.factory<_i1065.LoginBloc>(() => _i1065.LoginBloc()); gh.factory<_i991.BrowseBloc>(() => _i991.BrowseBloc()); gh.factory<_i580.MainBloc>(() => _i580.MainBloc()); gh.factory<_i311.SplashBloc>(() => _i311.SplashBloc()); @@ -56,6 +58,21 @@ extension GetItInjectableX on _i174.GetIt { gh.singleton<_i354.RequestHandlerService>( () => _i354.RequestHandlerService(gh<_i361.Dio>()), ); + gh.lazySingleton<_i246.AuthDatasource>( + () => _i246.AuthDatasourceImpl(gh<_i354.RequestHandlerService>()), + ); + gh.lazySingleton<_i884.AuthRepository>( + () => _i884.AuthRepositoryImpl( + gh<_i354.RequestHandlerService>(), + gh<_i246.AuthDatasource>(), + ), + ); + gh.factory<_i241.LoginUseCase>( + () => _i241.LoginUseCase(gh<_i884.AuthRepository>()), + ); + gh.factory<_i1065.LoginBloc>( + () => _i1065.LoginBloc(gh<_i241.LoginUseCase>()), + ); return this; } } diff --git a/lib/core/network/dio_client.dart b/lib/core/network/dio_client.dart index 717f331..f2adb50 100644 --- a/lib/core/network/dio_client.dart +++ b/lib/core/network/dio_client.dart @@ -32,11 +32,6 @@ class DioClient { responseBody: true, error: true, request: true, - logPrint: (object) { - if (kDebugMode) { - log('Error ${object.toString()}'); - } - }, ), ); return dio; diff --git a/lib/feature/auth/data/datasource/auth_datasource.dart b/lib/feature/auth/data/datasource/auth_datasource.dart index e69de29..b621d6b 100644 --- a/lib/feature/auth/data/datasource/auth_datasource.dart +++ b/lib/feature/auth/data/datasource/auth_datasource.dart @@ -0,0 +1,30 @@ +import 'package:food_delivery_client/core/services/request_handler_service.dart'; +import 'package:food_delivery_client/feature/auth/data/models/response/login_response.dart'; +import 'package:food_delivery_client/food_delivery_client.dart'; + +abstract class AuthDatasource { + Future login({ + required String phoneNumber, + required String password, + }); +} + +@LazySingleton(as: AuthDatasource) +class AuthDatasourceImpl implements AuthDatasource { + final RequestHandlerService _requestHandlerService; + + AuthDatasourceImpl(this._requestHandlerService); + + @override + Future login({ + required String phoneNumber, + required String password, + }) async { + return _requestHandlerService.handleRequest( + path: ApiConst.login, + method: RequestMethodEnum.post, + data: {"password": password, "phoneNumber": phoneNumber}, + fromJson: (response) async => LoginResponseModel.fromJson(response.data), + ); + } +} diff --git a/lib/feature/auth/data/models/response/login_response.dart b/lib/feature/auth/data/models/response/login_response.dart new file mode 100644 index 0000000..a68951c --- /dev/null +++ b/lib/feature/auth/data/models/response/login_response.dart @@ -0,0 +1,26 @@ +import 'package:equatable/equatable.dart'; + +class LoginResponseModel extends Equatable { + final String token; + + const LoginResponseModel({required this.token}); + + factory LoginResponseModel.fromJson(Map json) { + return LoginResponseModel( + token: json['token'] as String, + ); + } + + Map toJson() => { + 'token': token, + }; + + LoginResponseModel copyWith({String? token}) { + return LoginResponseModel( + token: token ?? this.token, + ); + } + + @override + List get props => [token]; +} diff --git a/lib/feature/auth/domain/repository/auth_repository.dart b/lib/feature/auth/domain/repository/auth_repository.dart new file mode 100644 index 0000000..7ef9f16 --- /dev/null +++ b/lib/feature/auth/domain/repository/auth_repository.dart @@ -0,0 +1,35 @@ +import 'package:dartz/dartz.dart'; +import 'package:food_delivery_client/core/services/request_handler_service.dart'; +import 'package:food_delivery_client/feature/auth/data/datasource/auth_datasource.dart'; +import 'package:food_delivery_client/feature/auth/data/models/response/login_response.dart'; +import 'package:food_delivery_client/food_delivery_client.dart'; + +abstract class AuthRepository { + Future> login({ + required String phoneNumber, + required String password, + }); +} + +@LazySingleton(as: AuthRepository) +class AuthRepositoryImpl implements AuthRepository { + final RequestHandlerService _requestHandlerService; + final AuthDatasource _authDatasource; + + AuthRepositoryImpl(this._requestHandlerService, this._authDatasource); + + @override + Future> login({ + required String phoneNumber, + required String password, + }) async { + return _requestHandlerService.handleRequestInRepository( + onRequest: () async { + return _authDatasource.login( + phoneNumber: phoneNumber, + password: password, + ); + }, + ); + } +} diff --git a/lib/feature/auth/domain/usecases/login_usecase.dart b/lib/feature/auth/domain/usecases/login_usecase.dart new file mode 100644 index 0000000..cd9b0c9 --- /dev/null +++ b/lib/feature/auth/domain/usecases/login_usecase.dart @@ -0,0 +1,26 @@ +import 'package:dartz/dartz.dart'; +import 'package:food_delivery_client/core/usecase/usecase.dart'; +import 'package:food_delivery_client/feature/auth/data/models/response/login_response.dart'; +import 'package:food_delivery_client/feature/auth/domain/repository/auth_repository.dart'; +import 'package:food_delivery_client/food_delivery_client.dart'; +@injectable +class LoginUseCase implements UseCase { + final AuthRepository _authRepository; + + LoginUseCase(this._authRepository); + + @override + Future> call(LoginParams params) async { + return _authRepository.login( + phoneNumber: params.phoneNumber, + password: params.password, + ); + } +} + +class LoginParams { + final String phoneNumber; + final String password; + + LoginParams({required this.phoneNumber, required this.password}); +} diff --git a/lib/feature/auth/presentation/blocs/login_bloc/login_bloc.dart b/lib/feature/auth/presentation/blocs/login_bloc/login_bloc.dart index 4fb0d6d..492f067 100644 --- a/lib/feature/auth/presentation/blocs/login_bloc/login_bloc.dart +++ b/lib/feature/auth/presentation/blocs/login_bloc/login_bloc.dart @@ -1,3 +1,4 @@ +import 'package:food_delivery_client/feature/auth/domain/usecases/login_usecase.dart'; import 'package:food_delivery_client/food_delivery_client.dart'; part 'login_event.dart'; @@ -8,11 +9,26 @@ part 'login_bloc.freezed.dart'; @injectable class LoginBloc extends Bloc { - LoginBloc() : super(const LoginState()) { + final LoginUseCase _loginUseCase; + + LoginBloc(this._loginUseCase) : super(const LoginState()) { on<_Login>(_onLogin); } Future _onLogin(_Login event, Emitter emit) async { emit(state.copyWith(status: RequestStatus.loading)); + + final response = await _loginUseCase.call(event.params); + + response.fold( + (l) { + log("${l.errorMessage}"); + emit(state.copyWith(status: RequestStatus.error)); + }, + (r) { + log(r.token); + emit(state.copyWith(status: RequestStatus.loaded)); + }, + ); } } diff --git a/lib/feature/auth/presentation/blocs/login_bloc/login_bloc.freezed.dart b/lib/feature/auth/presentation/blocs/login_bloc/login_bloc.freezed.dart index 4f3ae37..1058e55 100644 --- a/lib/feature/auth/presentation/blocs/login_bloc/login_bloc.freezed.dart +++ b/lib/feature/auth/presentation/blocs/login_bloc/login_bloc.freezed.dart @@ -122,11 +122,11 @@ return login(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen({TResult Function()? checked,TResult Function()? login,required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen({TResult Function()? checked,TResult Function( LoginParams params)? login,required TResult orElse(),}) {final _that = this; switch (_that) { case _Checked() when checked != null: return checked();case _Login() when login != null: -return login();case _: +return login(_that.params);case _: return orElse(); } @@ -144,11 +144,11 @@ return login();case _: /// } /// ``` -@optionalTypeArgs TResult when({required TResult Function() checked,required TResult Function() login,}) {final _that = this; +@optionalTypeArgs TResult when({required TResult Function() checked,required TResult Function( LoginParams params) login,}) {final _that = this; switch (_that) { case _Checked(): return checked();case _Login(): -return login();case _: +return login(_that.params);case _: throw StateError('Unexpected subclass'); } @@ -165,11 +165,11 @@ return login();case _: /// } /// ``` -@optionalTypeArgs TResult? whenOrNull({TResult? Function()? checked,TResult? Function()? login,}) {final _that = this; +@optionalTypeArgs TResult? whenOrNull({TResult? Function()? checked,TResult? Function( LoginParams params)? login,}) {final _that = this; switch (_that) { case _Checked() when checked != null: return checked();case _Login() when login != null: -return login();case _: +return login(_that.params);case _: return null; } @@ -213,34 +213,68 @@ String toString() { class _Login implements LoginEvent { - const _Login(); + const _Login(this.params); + final LoginParams params; - +/// Create a copy of LoginEvent +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$LoginCopyWith<_Login> get copyWith => __$LoginCopyWithImpl<_Login>(this, _$identity); @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _Login); + return identical(this, other) || (other.runtimeType == runtimeType&&other is _Login&&(identical(other.params, params) || other.params == params)); } @override -int get hashCode => runtimeType.hashCode; +int get hashCode => Object.hash(runtimeType,params); @override String toString() { - return 'LoginEvent.login()'; + return 'LoginEvent.login(params: $params)'; } } +/// @nodoc +abstract mixin class _$LoginCopyWith<$Res> implements $LoginEventCopyWith<$Res> { + factory _$LoginCopyWith(_Login value, $Res Function(_Login) _then) = __$LoginCopyWithImpl; +@useResult +$Res call({ + LoginParams params +}); + +} +/// @nodoc +class __$LoginCopyWithImpl<$Res> + implements _$LoginCopyWith<$Res> { + __$LoginCopyWithImpl(this._self, this._then); + + final _Login _self; + final $Res Function(_Login) _then; + +/// Create a copy of LoginEvent +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') $Res call({Object? params = null,}) { + return _then(_Login( +null == params ? _self.params : params // ignore: cast_nullable_to_non_nullable +as LoginParams, + )); +} + + +} + /// @nodoc mixin _$LoginState { diff --git a/lib/feature/auth/presentation/blocs/login_bloc/login_event.dart b/lib/feature/auth/presentation/blocs/login_bloc/login_event.dart index 184525e..c8f3962 100644 --- a/lib/feature/auth/presentation/blocs/login_bloc/login_event.dart +++ b/lib/feature/auth/presentation/blocs/login_bloc/login_event.dart @@ -4,5 +4,5 @@ part of 'login_bloc.dart'; class LoginEvent with _$LoginEvent { const factory LoginEvent.checked() = _Checked; - const factory LoginEvent.login() = _Login; + const factory LoginEvent.login(LoginParams params) = _Login; } diff --git a/lib/feature/auth/presentation/pages/login_page/widgets/login_body.dart b/lib/feature/auth/presentation/pages/login_page/widgets/login_body.dart index 57c67ba..69bec77 100644 --- a/lib/feature/auth/presentation/pages/login_page/widgets/login_body.dart +++ b/lib/feature/auth/presentation/pages/login_page/widgets/login_body.dart @@ -1,5 +1,6 @@ import 'package:flutter/cupertino.dart'; import 'package:food_delivery_client/core/helpers/formatters.dart'; +import 'package:food_delivery_client/feature/auth/domain/usecases/login_usecase.dart'; import '../../../../../../food_delivery_client.dart'; import '../../../blocs/login_bloc/login_bloc.dart'; @@ -14,7 +15,7 @@ class WLoginBody extends StatefulWidget { class _WLoginBodyState extends State { late final TextEditingController _phoneController; late final TextEditingController _passwordController; - final GlobalKey _formKey = GlobalKey(); + final _formKey = GlobalKey(); @override void initState() { @@ -98,7 +99,19 @@ class _WLoginBodyState extends State { AppButton( name: "Continue", isLoading: state.status.isLoading(), - onPressed: () {}, + onPressed: () { + if (_formKey.currentState?.validate() ?? false) { + context.read().add( + LoginEvent.login( + LoginParams( + phoneNumber: + "+998${_phoneController.text.trim().replaceAll(" ", "")}", + password: _passwordController.text.trim(), + ), + ), + ); + } + }, borderRadius: 15, backgroundColor: AppColors.c34A853, ), diff --git a/lib/feature/common/data/models/error_model.dart b/lib/feature/common/data/models/error_model.dart index 0dc6f40..b9a9723 100644 --- a/lib/feature/common/data/models/error_model.dart +++ b/lib/feature/common/data/models/error_model.dart @@ -15,6 +15,6 @@ class ErrorModel extends ErrorEntity { return ErrorModel(detail: data['detail']); } } - return ErrorModel(detail: data['message']); + return ErrorModel(detail: data['error']); } }