diff --git a/lib/core/constants/api_const.dart b/lib/core/constants/api_const.dart new file mode 100644 index 0000000..1aecd81 --- /dev/null +++ b/lib/core/constants/api_const.dart @@ -0,0 +1,3 @@ +abstract class ApiConst { + static const String baseUrl = "https://superapp.felixits.uz/api/v1"; +} diff --git a/lib/core/constants/constants.dart b/lib/core/constants/constants.dart index 876da0a..af060e9 100644 --- a/lib/core/constants/constants.dart +++ b/lib/core/constants/constants.dart @@ -1,4 +1,5 @@ export 'app_locale_keys.dart'; export 'sizes_consts.dart'; export 'l10n.dart'; -export 'time_delay_cons.dart'; \ No newline at end of file +export 'time_delay_cons.dart'; +export 'api_const.dart'; \ No newline at end of file diff --git a/lib/core/constants/time_delay_cons.dart b/lib/core/constants/time_delay_cons.dart index b270c00..20f86b4 100644 --- a/lib/core/constants/time_delay_cons.dart +++ b/lib/core/constants/time_delay_cons.dart @@ -2,6 +2,6 @@ abstract class TimeDelayConst { static const Duration durationMill150 = Duration(milliseconds: 150); static const Duration durationMill300 = Duration(milliseconds: 300); static const Duration durationMill800 = Duration(milliseconds: 800); - + static const Duration durationMill3500 = Duration(milliseconds: 3500); static const Duration duration3 = Duration(seconds: 3); } diff --git a/lib/core/core.dart b/lib/core/core.dart index 23b99b1..b1336f3 100644 --- a/lib/core/core.dart +++ b/lib/core/core.dart @@ -11,4 +11,6 @@ export 'l10n/app_localizations_en.dart'; export 'l10n/app_localizations_ru.dart'; export 'l10n/app_localizations_uz.dart'; export 'services/services.dart'; -export 'utils/app_utils.dart'; \ No newline at end of file +export 'utils/app_utils.dart'; +export 'exceptions/exceptions.dart'; +export 'exceptions/failure.dart'; diff --git a/lib/core/di/injection_container.config.dart b/lib/core/di/injection_container.config.dart index d6ffb51..01c978e 100644 --- a/lib/core/di/injection_container.config.dart +++ b/lib/core/di/injection_container.config.dart @@ -9,6 +9,7 @@ // coverage:ignore-file // ignore_for_file: no_leading_underscores_for_library_prefixes +import 'package:dio/dio.dart' as _i361; import 'package:get_it/get_it.dart' as _i174; import 'package:injectable/injectable.dart' as _i526; @@ -24,7 +25,9 @@ import '../../feature/main/presentation/blocs/main_bloc/main_bloc.dart' import '../../feature/on_boarding/presentation/blocs/splash_bloc/splash_bloc.dart' as _i311; import '../../food_delivery_client.dart' as _i321; +import '../network/dio_client.dart' as _i667; import '../router/app_routes.dart' as _i152; +import '../services/request_handler_service.dart' as _i354; import '../services/storage_service.dart' as _i306; extension GetItInjectableX on _i174.GetIt { @@ -34,6 +37,7 @@ extension GetItInjectableX on _i174.GetIt { _i526.EnvironmentFilter? environmentFilter, }) { final gh = _i526.GetItHelper(this, environment, environmentFilter); + final dioModule = _$DioModule(); gh.factory<_i1007.HomeBloc>(() => _i1007.HomeBloc()); gh.factory<_i728.BasketBloc>(() => _i728.BasketBloc()); gh.factory<_i991.BrowseBloc>(() => _i991.BrowseBloc()); @@ -41,9 +45,16 @@ extension GetItInjectableX on _i174.GetIt { gh.factory<_i311.SplashBloc>(() => _i311.SplashBloc()); gh.singleton<_i306.StorageService>(() => _i306.StorageService()); gh.singleton<_i152.AppRoutes>(() => _i152.AppRoutes()); + gh.lazySingleton<_i667.DioClient>(() => _i667.DioClient()); gh.factory<_i942.LanguageBloc>( () => _i942.LanguageBloc(gh<_i321.StorageService>()), ); + gh.lazySingleton<_i361.Dio>(() => dioModule.dio(gh<_i667.DioClient>())); + gh.singleton<_i354.RequestHandlerService>( + () => _i354.RequestHandlerService(gh<_i361.Dio>()), + ); return this; } } + +class _$DioModule extends _i667.DioModule {} diff --git a/lib/core/exceptions/exceptions.dart b/lib/core/exceptions/exceptions.dart new file mode 100644 index 0000000..c8654e7 --- /dev/null +++ b/lib/core/exceptions/exceptions.dart @@ -0,0 +1,32 @@ +import 'package:dio/dio.dart'; + +class ServerException implements Exception { + final String errorMessage; + final String errorKey; + final num statusCode; + + const ServerException({ + required this.statusCode, + required this.errorMessage, + required this.errorKey, + }); + + @override + String toString() { + return 'ServerException(statusCode: $statusCode, errorMessage: $errorMessage, errorKey: $errorKey)'; + } +} + +class CustomDioException implements Exception { + final String errorMessage; + final DioExceptionType type; + final int? statusCode; + + CustomDioException({required this.errorMessage, required this.type, this.statusCode}); +} + +class ParsingException implements Exception { + final String errorMessage; + + const ParsingException({required this.errorMessage}); +} \ No newline at end of file diff --git a/lib/core/exceptions/failure.dart b/lib/core/exceptions/failure.dart new file mode 100644 index 0000000..bdd793a --- /dev/null +++ b/lib/core/exceptions/failure.dart @@ -0,0 +1,44 @@ +import 'package:equatable/equatable.dart'; +import 'package:dio/dio.dart'; + +class Failure extends Equatable { + final String? errorMessage; + final String? errorKey; + + const Failure({ + this.errorMessage, + this.errorKey, + }); //error key kere bomasa required qilish shartamas + @override + List get props => [ + errorMessage, + errorKey, + ]; +} + +class ServerFailure extends Failure { + final num statusCode; + + const ServerFailure({ + required super.errorMessage, + required this.statusCode, + required super.errorKey, + }); +} + +class DioFailure extends Failure { + final DioExceptionType type; + final int? statusCode; + + const DioFailure({ + required super.errorMessage, + this.type = DioExceptionType.badResponse, + this.statusCode, + }); +} + +class ParsingFailure extends Failure { + const ParsingFailure({required super.errorMessage}); +} + +class CacheFailure extends Failure {} \ No newline at end of file diff --git a/lib/core/network/dio_client.dart b/lib/core/network/dio_client.dart new file mode 100644 index 0000000..717f331 --- /dev/null +++ b/lib/core/network/dio_client.dart @@ -0,0 +1,44 @@ +import 'package:dio/dio.dart'; +import 'package:flutter/foundation.dart'; +import 'package:food_delivery_client/core/network/header_interceptors.dart'; +import 'package:food_delivery_client/food_delivery_client.dart'; +import 'error_handler_interceptor.dart'; + +@module +abstract class DioModule { + @lazySingleton + Dio dio(DioClient dioClient) => dioClient.dio; +} + +@lazySingleton +class DioClient { + final BaseOptions _dioBaseOptions = BaseOptions( + baseUrl: ApiConst.baseUrl, + connectTimeout: TimeDelayConst.durationMill3500, + receiveTimeout: TimeDelayConst.durationMill3500, + followRedirects: false, + validateStatus: (status) => status != null && status >= 200 && status < 300, + ); + + Dio get dio { + final Dio dio = Dio(_dioBaseOptions); + dio.interceptors + ..add(HeaderInterceptors()) + ..add(ErrorHandlerInterceptors()) + ..add( + LogInterceptor( + requestHeader: true, + requestBody: true, + responseBody: true, + error: true, + request: true, + logPrint: (object) { + if (kDebugMode) { + log('Error ${object.toString()}'); + } + }, + ), + ); + return dio; + } +} diff --git a/lib/core/network/error_handler_interceptor.dart b/lib/core/network/error_handler_interceptor.dart new file mode 100644 index 0000000..7ed39ec --- /dev/null +++ b/lib/core/network/error_handler_interceptor.dart @@ -0,0 +1,41 @@ +import 'package:dio/dio.dart'; + +import '../../feature/common/data/models/error_model.dart'; + +class ErrorHandlerInterceptors extends Interceptor { + @override + void onError(DioException err, ErrorInterceptorHandler handler) { + if (err.response != null && err.response!.statusCode != null) { + if (err.response?.data is String) { + final errorMessage = err.response?.data as String?; + final error = ErrorModel( + detail: errorMessage?.replaceAll(RegExp(r'<[^>]+>'), '') ?? '', + ); + handler.next( + DioException( + requestOptions: err.requestOptions, + response: Response( + requestOptions: err.requestOptions, + data: error.toJson(), + ), + ), + ); + return; + } else { + handler.next(err); + return; + } + } + const error = ErrorModel(detail: ""); + handler.next( + DioException( + requestOptions: err.requestOptions, + response: Response( + requestOptions: err.requestOptions, + data: error.toJson(), + ), + ), + ); + return; + } +} diff --git a/lib/core/network/header_interceptors.dart b/lib/core/network/header_interceptors.dart new file mode 100644 index 0000000..0cd0646 --- /dev/null +++ b/lib/core/network/header_interceptors.dart @@ -0,0 +1,17 @@ +import 'package:dio/dio.dart'; + +class HeaderInterceptors extends Interceptor { + HeaderInterceptors._internal(); + + static final HeaderInterceptors _interceptors = + HeaderInterceptors._internal(); + + factory HeaderInterceptors() { + return _interceptors; + } + + @override + void onRequest(RequestOptions options, RequestInterceptorHandler handler) { + handler.next(options); + } +} diff --git a/lib/core/services/request_handler_service.dart b/lib/core/services/request_handler_service.dart new file mode 100644 index 0000000..02dcdff --- /dev/null +++ b/lib/core/services/request_handler_service.dart @@ -0,0 +1,77 @@ +import 'dart:developer'; + +import 'package:dartz/dartz.dart'; +import 'package:dio/dio.dart'; +import 'package:food_delivery_client/food_delivery_client.dart'; + +import '../../feature/common/data/models/error_model.dart'; + +enum RequestMethodEnum { get, post, put, delete, patch } + +@singleton +class RequestHandlerService { + final Dio dio; + + const RequestHandlerService(this.dio); + + Future handleRequest({ + required Future Function(Response) fromJson, + required String path, + RequestMethodEnum? method, + Options? options, + Map? queryParameters, + Object? data, + Dio? newDio, + }) async { + try { + final response = await (newDio ?? dio).request( + path, + options: + options ?? + Options(method: method?.name ?? RequestMethodEnum.get.name), + queryParameters: queryParameters, + data: data, + ); + final result = fromJson.call(response); + return result; + } on DioException catch (e) { + final errorResponse = ErrorModel.fromJson(e.response?.data); + throw CustomDioException( + errorMessage: errorResponse.detail, + type: e.type, + statusCode: e.response?.statusCode, + ); + } on ParsingException { + rethrow; + } on Exception catch (e) { + throw ParsingException(errorMessage: e.toString()); + } catch (e) { + throw ParsingException(errorMessage: e.toString()); + } + } + + Future> handleRequestInRepository({ + required Future Function() onRequest, + String debugLabel = '', + }) async { + try { + final result = await onRequest.call(); + return Right(result); + } on ParsingException catch (e) { + log("ParsingException in $debugLabel: ${e.errorMessage}"); + return Left(ParsingFailure(errorMessage: e.errorMessage)); + } on CustomDioException catch (e) { + log("CustomDioException in $debugLabel: ${e.errorMessage}"); + return Left( + DioFailure( + errorMessage: e.errorMessage, + type: e.type, + statusCode: e.statusCode, + ), + ); + } on Exception catch (e) { + log("Exception in $debugLabel: ${e.toString()}"); + return Left(ParsingFailure(errorMessage: e.toString())); + } + } +} diff --git a/lib/core/usecase/usecase.dart b/lib/core/usecase/usecase.dart new file mode 100644 index 0000000..3e31d61 --- /dev/null +++ b/lib/core/usecase/usecase.dart @@ -0,0 +1,15 @@ +import 'package:dartz/dartz.dart'; +import 'package:food_delivery_client/food_delivery_client.dart'; + +mixin UseCase { + Future> call(Params params); +} + +mixin StreamUseCase { + Stream> call(Params params); +} + +class NoParams extends Equatable { + @override + List get props => []; +} diff --git a/lib/feature/auth/data/datasource/auth_datasource.dart b/lib/feature/auth/data/datasource/auth_datasource.dart new file mode 100644 index 0000000..e69de29 diff --git a/lib/feature/common/data/models/error_model.dart b/lib/feature/common/data/models/error_model.dart new file mode 100644 index 0000000..0dc6f40 --- /dev/null +++ b/lib/feature/common/data/models/error_model.dart @@ -0,0 +1,20 @@ +import '../../domain/entities/error_entity.dart'; + +class ErrorModel extends ErrorEntity { + const ErrorModel({super.detail}); + + Map toJson() { + return {"detail": detail}; + } + + factory ErrorModel.fromJson(Map data) { + if (data.containsKey("detail")) { + if (data['detail'] is List) { + return ErrorModel(detail: data['detail'][0]["msg"]); + } else { + return ErrorModel(detail: data['detail']); + } + } + return ErrorModel(detail: data['message']); + } +} diff --git a/lib/feature/common/domain/entities/error_entity.dart b/lib/feature/common/domain/entities/error_entity.dart new file mode 100644 index 0000000..4dad844 --- /dev/null +++ b/lib/feature/common/domain/entities/error_entity.dart @@ -0,0 +1,5 @@ +class ErrorEntity { + final String detail; + + const ErrorEntity({this.detail = ''}); +} diff --git a/lib/food_delivery_client.dart b/lib/food_delivery_client.dart index 392a676..3231f2f 100644 --- a/lib/food_delivery_client.dart +++ b/lib/food_delivery_client.dart @@ -22,3 +22,4 @@ export 'package:carousel_slider/carousel_slider.dart'; export 'package:flutter_bounceable/flutter_bounceable.dart'; export 'package:food_delivery_client/feature/on_boarding/presentation/pages/splash_page/splash_page.dart'; export 'package:skeletonizer/skeletonizer.dart'; +export 'package:equatable/equatable.dart'; diff --git a/pubspec.lock b/pubspec.lock index 5640884..b02516c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -201,6 +201,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.2" + dartz: + dependency: "direct main" + description: + name: dartz + sha256: e6acf34ad2e31b1eb00948692468c30ab48ac8250e0f0df661e29f12dd252168 + url: "https://pub.dev" + source: hosted + version: "0.10.1" dio: dependency: "direct main" description: @@ -217,6 +225,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" + equatable: + dependency: "direct main" + description: + name: equatable + sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7" + url: "https://pub.dev" + source: hosted + version: "2.0.7" fake_async: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 4bbdb20..24c0f24 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -20,6 +20,8 @@ dependencies: flutter_bounceable: ^1.2.0 cached_network_image: ^3.4.1 carousel_slider: ^5.1.1 + equatable: ^2.0.7 + dartz: ^0.10.1 #DI get_it: ^8.2.0 @@ -29,6 +31,8 @@ dependencies: #network dio: ^5.9.0 + + #to use svgs flutter_svg: ^2.2.1