feat:login page connected with backend

This commit is contained in:
jahongireshonqulov
2025-10-28 20:08:53 +05:00
parent b23808c731
commit d3ad5b8ddd
12 changed files with 216 additions and 22 deletions

View File

@@ -1,3 +1,5 @@
abstract class ApiConst { abstract class ApiConst {
static const String baseUrl = "https://superapp.felixits.uz/api/v1"; static const String baseUrl = "https://superapp.felixits.uz/api/v1";
static const String login = "/auth/login";
} }

View File

@@ -13,6 +13,9 @@ import 'package:dio/dio.dart' as _i361;
import 'package:get_it/get_it.dart' as _i174; import 'package:get_it/get_it.dart' as _i174;
import 'package:injectable/injectable.dart' as _i526; 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' import '../../feature/auth/presentation/blocs/login_bloc/login_bloc.dart'
as _i1065; as _i1065;
import '../../feature/basket/presentation/blocs/basket_bloc.dart' as _i728; import '../../feature/basket/presentation/blocs/basket_bloc.dart' as _i728;
@@ -42,7 +45,6 @@ extension GetItInjectableX on _i174.GetIt {
final dioModule = _$DioModule(); final dioModule = _$DioModule();
gh.factory<_i1007.HomeBloc>(() => _i1007.HomeBloc()); gh.factory<_i1007.HomeBloc>(() => _i1007.HomeBloc());
gh.factory<_i728.BasketBloc>(() => _i728.BasketBloc()); gh.factory<_i728.BasketBloc>(() => _i728.BasketBloc());
gh.factory<_i1065.LoginBloc>(() => _i1065.LoginBloc());
gh.factory<_i991.BrowseBloc>(() => _i991.BrowseBloc()); gh.factory<_i991.BrowseBloc>(() => _i991.BrowseBloc());
gh.factory<_i580.MainBloc>(() => _i580.MainBloc()); gh.factory<_i580.MainBloc>(() => _i580.MainBloc());
gh.factory<_i311.SplashBloc>(() => _i311.SplashBloc()); gh.factory<_i311.SplashBloc>(() => _i311.SplashBloc());
@@ -56,6 +58,21 @@ extension GetItInjectableX on _i174.GetIt {
gh.singleton<_i354.RequestHandlerService>( gh.singleton<_i354.RequestHandlerService>(
() => _i354.RequestHandlerService(gh<_i361.Dio>()), () => _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; return this;
} }
} }

View File

@@ -32,11 +32,6 @@ class DioClient {
responseBody: true, responseBody: true,
error: true, error: true,
request: true, request: true,
logPrint: (object) {
if (kDebugMode) {
log('Error ${object.toString()}');
}
},
), ),
); );
return dio; return dio;

View File

@@ -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<LoginResponseModel> login({
required String phoneNumber,
required String password,
});
}
@LazySingleton(as: AuthDatasource)
class AuthDatasourceImpl implements AuthDatasource {
final RequestHandlerService _requestHandlerService;
AuthDatasourceImpl(this._requestHandlerService);
@override
Future<LoginResponseModel> 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),
);
}
}

View File

@@ -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<String, dynamic> json) {
return LoginResponseModel(
token: json['token'] as String,
);
}
Map<String, dynamic> toJson() => {
'token': token,
};
LoginResponseModel copyWith({String? token}) {
return LoginResponseModel(
token: token ?? this.token,
);
}
@override
List<Object?> get props => [token];
}

View File

@@ -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<Either<Failure, LoginResponseModel>> 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<Either<Failure, LoginResponseModel>> login({
required String phoneNumber,
required String password,
}) async {
return _requestHandlerService.handleRequestInRepository(
onRequest: () async {
return _authDatasource.login(
phoneNumber: phoneNumber,
password: password,
);
},
);
}
}

View File

@@ -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<LoginResponseModel, LoginParams> {
final AuthRepository _authRepository;
LoginUseCase(this._authRepository);
@override
Future<Either<Failure, LoginResponseModel>> 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});
}

View File

@@ -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'; import 'package:food_delivery_client/food_delivery_client.dart';
part 'login_event.dart'; part 'login_event.dart';
@@ -8,11 +9,26 @@ part 'login_bloc.freezed.dart';
@injectable @injectable
class LoginBloc extends Bloc<LoginEvent, LoginState> { class LoginBloc extends Bloc<LoginEvent, LoginState> {
LoginBloc() : super(const LoginState()) { final LoginUseCase _loginUseCase;
LoginBloc(this._loginUseCase) : super(const LoginState()) {
on<_Login>(_onLogin); on<_Login>(_onLogin);
} }
Future<void> _onLogin(_Login event, Emitter<LoginState> emit) async { Future<void> _onLogin(_Login event, Emitter<LoginState> emit) async {
emit(state.copyWith(status: RequestStatus.loading)); 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));
},
);
} }
} }

View File

@@ -122,11 +122,11 @@ return login(_that);case _:
/// } /// }
/// ``` /// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>({TResult Function()? checked,TResult Function()? login,required TResult orElse(),}) {final _that = this; @optionalTypeArgs TResult maybeWhen<TResult extends Object?>({TResult Function()? checked,TResult Function( LoginParams params)? login,required TResult orElse(),}) {final _that = this;
switch (_that) { switch (_that) {
case _Checked() when checked != null: case _Checked() when checked != null:
return checked();case _Login() when login != null: return checked();case _Login() when login != null:
return login();case _: return login(_that.params);case _:
return orElse(); return orElse();
} }
@@ -144,11 +144,11 @@ return login();case _:
/// } /// }
/// ``` /// ```
@optionalTypeArgs TResult when<TResult extends Object?>({required TResult Function() checked,required TResult Function() login,}) {final _that = this; @optionalTypeArgs TResult when<TResult extends Object?>({required TResult Function() checked,required TResult Function( LoginParams params) login,}) {final _that = this;
switch (_that) { switch (_that) {
case _Checked(): case _Checked():
return checked();case _Login(): return checked();case _Login():
return login();case _: return login(_that.params);case _:
throw StateError('Unexpected subclass'); throw StateError('Unexpected subclass');
} }
@@ -165,11 +165,11 @@ return login();case _:
/// } /// }
/// ``` /// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>({TResult? Function()? checked,TResult? Function()? login,}) {final _that = this; @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>({TResult? Function()? checked,TResult? Function( LoginParams params)? login,}) {final _that = this;
switch (_that) { switch (_that) {
case _Checked() when checked != null: case _Checked() when checked != null:
return checked();case _Login() when login != null: return checked();case _Login() when login != null:
return login();case _: return login(_that.params);case _:
return null; return null;
} }
@@ -213,34 +213,68 @@ String toString() {
class _Login implements LoginEvent { 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 @override
bool operator ==(Object other) { 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 @override
int get hashCode => runtimeType.hashCode; int get hashCode => Object.hash(runtimeType,params);
@override @override
String toString() { 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 /// @nodoc
mixin _$LoginState { mixin _$LoginState {

View File

@@ -4,5 +4,5 @@ part of 'login_bloc.dart';
class LoginEvent with _$LoginEvent { class LoginEvent with _$LoginEvent {
const factory LoginEvent.checked() = _Checked; const factory LoginEvent.checked() = _Checked;
const factory LoginEvent.login() = _Login; const factory LoginEvent.login(LoginParams params) = _Login;
} }

View File

@@ -1,5 +1,6 @@
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:food_delivery_client/core/helpers/formatters.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 '../../../../../../food_delivery_client.dart';
import '../../../blocs/login_bloc/login_bloc.dart'; import '../../../blocs/login_bloc/login_bloc.dart';
@@ -14,7 +15,7 @@ class WLoginBody extends StatefulWidget {
class _WLoginBodyState extends State<WLoginBody> { class _WLoginBodyState extends State<WLoginBody> {
late final TextEditingController _phoneController; late final TextEditingController _phoneController;
late final TextEditingController _passwordController; late final TextEditingController _passwordController;
final GlobalKey _formKey = GlobalKey<FormState>(); final _formKey = GlobalKey<FormState>();
@override @override
void initState() { void initState() {
@@ -98,7 +99,19 @@ class _WLoginBodyState extends State<WLoginBody> {
AppButton( AppButton(
name: "Continue", name: "Continue",
isLoading: state.status.isLoading(), isLoading: state.status.isLoading(),
onPressed: () {}, onPressed: () {
if (_formKey.currentState?.validate() ?? false) {
context.read<LoginBloc>().add(
LoginEvent.login(
LoginParams(
phoneNumber:
"+998${_phoneController.text.trim().replaceAll(" ", "")}",
password: _passwordController.text.trim(),
),
),
);
}
},
borderRadius: 15, borderRadius: 15,
backgroundColor: AppColors.c34A853, backgroundColor: AppColors.c34A853,
), ),

View File

@@ -15,6 +15,6 @@ class ErrorModel extends ErrorEntity {
return ErrorModel(detail: data['detail']); return ErrorModel(detail: data['detail']);
} }
} }
return ErrorModel(detail: data['message']); return ErrorModel(detail: data['error']);
} }
} }