From 42422658302c1f5fbb1e55028b78ff6840af16e5 Mon Sep 17 00:00:00 2001 From: Username1548 Date: Wed, 29 Jan 2025 14:47:27 +0700 Subject: [PATCH] new test cases --- assets/images/icn_dev_tools.svg | 6 + lib/core/app_router.dart | 5 +- lib/core/locator_service.dart | 4 +- lib/core/theme/theme_helper.dart | 2 + lib/core/utils/image_constant.dart | 2 + .../chat/presentation/pages/chat_screen.dart | 12 +- .../presentation/dev_tools_screen.dart | 2 +- lib/features/dev_tools/test_cases.dart | 104 ++++++++++ .../presentation/cubit/about_diet_cubit.dart | 83 +++++++- .../presentation/cubit/about_diet_state.dart | 6 +- .../cubit/about_diet_state.freezed.dart | 90 +++++--- .../presentation/pages/about_diet_screen.dart | 38 +++- .../presentation/widgets/calendar_widget.dart | 19 +- .../widgets/cancel_diet_bottom_sheet.dart | 79 +++++++ .../widgets/start_diet_bottom_sheet.dart | 26 ++- .../diets/repositories/diet_repository.dart | 16 ++ .../bloc/plans_cubit/plans_cubit.dart | 2 +- .../feed/presentation/pages/feed_screen.dart | 96 ++------- .../feed/presentation/widgets/chip_group.dart | 44 ++++ .../menu/presentation/pages/menu_screen.dart | 2 +- .../presentation/pages/profile_screen.dart | 2 +- .../repositories/profile_repository.dart | 9 + .../entities/recipe_create_entity.dart | 89 +++++++- .../recipe_create_entity.freezed.dart | 193 ++---------------- lib/features/recipes/models/ingredient.dart | 2 +- lib/features/recipes/models/recipe_model.dart | 6 +- .../recipes/models/recipe_model.freezed.dart | 136 ++++++++++-- .../recipes/models/recipe_model.g.dart | 12 +- .../create_recipe_cubit.dart | 22 +- .../pages/recipe_create_screen.dart | 47 +++-- .../widgets/info_about_recipe_widget.dart | 18 +- .../widgets/ingredients_widget.dart | 4 +- .../reporitories/recipe_repository.dart | 52 +++-- .../recipes/services/recipe_service.dart | 23 ++- .../recipes/services/recipe_service.g.dart | 88 +++++--- .../registration_cubit.dart | 4 +- .../settings/services/settings_service.dart | 4 +- .../settings/services/settings_service.g.dart | 4 +- lib/l10n/intl_en.arb | 6 +- 39 files changed, 898 insertions(+), 461 deletions(-) create mode 100644 assets/images/icn_dev_tools.svg create mode 100644 lib/features/diets/presentation/widgets/cancel_diet_bottom_sheet.dart create mode 100644 lib/features/feed/presentation/widgets/chip_group.dart diff --git a/assets/images/icn_dev_tools.svg b/assets/images/icn_dev_tools.svg new file mode 100644 index 00000000..1377ef9d --- /dev/null +++ b/assets/images/icn_dev_tools.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lib/core/app_router.dart b/lib/core/app_router.dart index ff26e3bb..39daded4 100644 --- a/lib/core/app_router.dart +++ b/lib/core/app_router.dart @@ -58,6 +58,7 @@ class AppRouter { path: '/auth-screen', name: 'authScreen', redirect: (context, state) async { + // TokenStorage.removeTokenInStorage(); if (await TokenStorage.haveTokenInStorage()) { return '/my-profile-screen'; } @@ -133,8 +134,8 @@ class AppRouter { ), ), GoRoute( - path: '/recipe-create-screen', - name: 'recipeCreateScreen', + path: RecipeCreateScreen.path, + name: RecipeCreateScreen.name, builder: (context, state) => RecipeCreateScreen(), ), GoRoute( diff --git a/lib/core/locator_service.dart b/lib/core/locator_service.dart index 6c62f438..e258d702 100644 --- a/lib/core/locator_service.dart +++ b/lib/core/locator_service.dart @@ -91,14 +91,14 @@ Future init() async { sl.registerFactory(() => SettingsCubit(profileRepository: sl(), settingsRepository: sl())); sl.registerFactory(() => RegisterConsultantCubit(consultantRepository: sl())); sl.registerFactory(() => DiaryCubit(diaryRepository: sl(), glossaryRepository: sl())); - sl.registerFactory(() => AboutDietCubit(dietsRepository: sl())); + sl.registerFactory(() => AboutDietCubit(dietsRepository: sl(), profileRepository: sl())); sl.registerFactory(() => BlogPostCreateCubit(blogPostRepository: sl())); sl.registerFactory(() => SubCubit(profileRepository: sl())); sl.registerFactory(() => CreateDietCubit(profileRepository: sl(), createDietRepository: sl(), glossaryRepository: sl())); sl.registerFactory(() => CreatePlanCubit()); sl.registerFactory(() => FeedProfileCubit(profileRepository: sl())); sl.registerFactory(() => AboutPlanCubit(planRepository: sl())); - sl.registerFactory(() => CreateRecipeCubit()); + sl.registerFactory(() => CreateRecipeCubit(recipeRepository: sl())); sl.registerFactory(() => MyDietsCubit(myDietsRepository: sl())); sl.registerFactory(() => MyPlansCubit(myPlansRepository: sl())); sl.registerFactory(() => UserDetailsCubit(profileRepository: sl())); diff --git a/lib/core/theme/theme_helper.dart b/lib/core/theme/theme_helper.dart index 5abec056..31fbd60b 100644 --- a/lib/core/theme/theme_helper.dart +++ b/lib/core/theme/theme_helper.dart @@ -185,6 +185,8 @@ class PrimaryColors { Color get red => Color(0xFFCD4141); + Color get transparentRed => Color(0xFFCD4141).withValues(alpha: 0.75); + Color get transparentBlack => Color(0xFF1313264).withValues(alpha: 0.3); Color get gray60004 => Color(0xFFD4DAE2).withValues(alpha: 0.2); diff --git a/lib/core/utils/image_constant.dart b/lib/core/utils/image_constant.dart index 1353a5cf..73bdf50a 100644 --- a/lib/core/utils/image_constant.dart +++ b/lib/core/utils/image_constant.dart @@ -218,5 +218,7 @@ class ImageConstant { static String icnSupport = '$imagePath/icn_support.svg'; + static String icnDevTools = '$imagePath/icn_dev_tools.svg'; + static String icnVeggies = '$imagePath/icn_veggies.svg'; } diff --git a/lib/features/chat/presentation/pages/chat_screen.dart b/lib/features/chat/presentation/pages/chat_screen.dart index 3b76cb7c..d446e4b2 100644 --- a/lib/features/chat/presentation/pages/chat_screen.dart +++ b/lib/features/chat/presentation/pages/chat_screen.dart @@ -154,7 +154,17 @@ class _ChatScreenState extends State { if ((index + 1 != state.chatMessages.length && DateTime.parse(state.chatMessages[index].updatedAt).day != DateTime.parse(state.chatMessages[index + 1].updatedAt).day) || index + 1 == state.chatMessages.length) { - return _messageDate(state.chatMessages[index].updatedAt); + return Column( + children: [ + _messageDate(state.chatMessages[index].updatedAt), + ChatBubbleWidget( + sendTime: state.chatMessages[index].updatedAt, + text: state.chatMessages[index].data.trim(), + isCurrentUser: state.chatRoom.users['to_chat_user']!.id != state.chatMessages[index].authorId, + mediaData: state.chatMessages[index].mediaData ?? [], + ), + ], + ); } return ChatBubbleWidget( sendTime: state.chatMessages[index].updatedAt, diff --git a/lib/features/dev_tools/presentation/dev_tools_screen.dart b/lib/features/dev_tools/presentation/dev_tools_screen.dart index 9b18107a..ea6c3817 100644 --- a/lib/features/dev_tools/presentation/dev_tools_screen.dart +++ b/lib/features/dev_tools/presentation/dev_tools_screen.dart @@ -25,7 +25,7 @@ class DevToolsScreenContent extends StatefulWidget { class _DevToolsScreenContentState extends State { String baseUrlForTest = ''; - final List testCases = [Test850task()]; + final List testCases = [Test850task(), GetChatRoomPreviews(), GetChatRoom(), SendMessage(), GetChatMessages(), GetChatRoomIdByUserSlug()]; final controller = TextEditingController(); late final List testsStates; @override diff --git a/lib/features/dev_tools/test_cases.dart b/lib/features/dev_tools/test_cases.dart index 3f0891b7..cb812174 100644 --- a/lib/features/dev_tools/test_cases.dart +++ b/lib/features/dev_tools/test_cases.dart @@ -4,6 +4,7 @@ import 'dart:developer'; import 'package:dio/dio.dart'; import 'package:eatvio_app/core/locator_service.dart'; import 'package:eatvio_app/core/utils/date_time_utils.dart'; +import 'package:eatvio_app/features/chat/services/chat_service.dart'; import 'package:eatvio_app/features/diets/services/diet_service.dart'; import 'package:eatvio_app/features/feed/services/feed_service.dart'; import 'package:eatvio_app/features/profile/services/profile_service.dart'; @@ -113,3 +114,106 @@ class Test850task implements TestCases { } } } + +class GetChatRoomPreviews implements TestCases { + @override + String get name => '/api/chat/index'; + + @override + Future run({required String baseUrl}) async { + try { + // get all of needed services + final dio = sl(); + final chatService = ChatService(dio, baseUrl: baseUrl); + + await chatService.getChatRoomPreviews(); + return true; + } catch (e) { + return false; + } + } +} + +class GetChatRoom implements TestCases { + @override + String get name => '/api/chat/show/{id}'; + + @override + Future run({required String baseUrl}) async { + try { + final dio = sl(); + final chatService = ChatService(dio, baseUrl: baseUrl); + + final chatRoomsModels = await chatService.getChatRoomPreviews(); + await chatService.getChatRoom(id: chatRoomsModels.chatRooms.firstOrNull?.id ?? ''); + return true; + } catch (e) { + return false; + } + } +} + +class SendMessage implements TestCases { + @override + String get name => '/api/chat/{id}/message/send'; + + @override + Future run({required String baseUrl}) async { + try { + final dio = sl(); + final chatService = ChatService(dio, baseUrl: baseUrl); + + final chatRoomsModels = await chatService.getChatRoomPreviews(); + await chatService.sendMessage(id: chatRoomsModels.chatRooms.firstOrNull?.id ?? '', data: 'test message'); + return true; + } catch (e) { + return false; + } + } +} + +class GetChatMessages implements TestCases { + @override + String get name => '/api/chat/show/{id}/messages?page={page}'; + + @override + Future run({required String baseUrl}) async { + try { + final dio = sl(); + final chatService = ChatService(dio, baseUrl: baseUrl); + + final chatRoomsModels = await chatService.getChatRoomPreviews(); + await chatService.getChatMessages(id: chatRoomsModels.chatRooms.firstOrNull?.id ?? '', page: 1); + return true; + } catch (e) { + return false; + } + } +} + +class GetChatRoomIdByUserSlug implements TestCases { + @override + String get name => '/api/chat/get_by_user/{user}'; + + @override + Future run({required String baseUrl}) async { + try { + final dio = sl(); + final chatService = ChatService(dio, baseUrl: baseUrl); + final consultantService = FeedService(dio, baseUrl: baseUrl); + final profileService = ProfileService(dio, baseUrl: baseUrl); + final myProfile = await profileService.getMyProfile(); + final consultantsModel = await consultantService.getConsultants(); + + for (var consultant in consultantsModel.data) { + if (consultant.slug != myProfile.slug) { + await chatService.getChatRoomIdByUserSlug(user: consultant.slug); + break; + } + } + return true; + } catch (e) { + return false; + } + } +} diff --git a/lib/features/diets/presentation/cubit/about_diet_cubit.dart b/lib/features/diets/presentation/cubit/about_diet_cubit.dart index 54215fe9..ea57351d 100644 --- a/lib/features/diets/presentation/cubit/about_diet_cubit.dart +++ b/lib/features/diets/presentation/cubit/about_diet_cubit.dart @@ -1,14 +1,21 @@ import 'dart:async'; import 'dart:developer'; +import 'package:dio/dio.dart'; import 'package:easy_debounce/easy_debounce.dart'; +import 'package:eatvio_app/core/utils/date_time_utils.dart'; import 'package:eatvio_app/core/utils/toasts.dart'; import 'package:eatvio_app/features/diets/repositories/diet_repository.dart'; import 'package:eatvio_app/features/diets/presentation/cubit/about_diet_state.dart'; +import 'package:eatvio_app/features/profile/repositories/profile_repository.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; class AboutDietCubit extends Cubit { - AboutDietCubit({required this.dietsRepository}) : super(AboutDietState.initial()); + AboutDietCubit({required this.dietsRepository, required this.profileRepository}) : super(AboutDietState.initial()); final DietRepository dietsRepository; + final ProfileRepository profileRepository; + bool isCurrentDietStarted = false; + bool hasStartedDiet = false; + String? userStartedDietName; Future getDiet(int id, {int? index}) async { try { @@ -16,17 +23,30 @@ class AboutDietCubit extends Cubit { if (index != null) { dietsRepository.emitCommentsStateToFeed(CommentsState(index: index, totalComments: diet.totalComments)); } - emit(AboutDietState.loaded(diet)); + emit(AboutDietState.loaded(diet: diet, isStarted: isCurrentDietStarted, hasStartedDiet: hasStartedDiet)); } catch (e) { log(e.toString()); } } - Future getDietWithLoading(int id) async { + Future initDietPage(int id) async { try { emit(AboutDietState.loading()); final diet = await dietsRepository.getDiet(id); - emit(AboutDietState.loaded(diet)); + final chosenDiets = await profileRepository.getChosenDiets(); + final lastChosenDiet = chosenDiets.firstOrNull; + if (lastChosenDiet != null && ['inited', 'started'].contains(lastChosenDiet.state)) { + hasStartedDiet = true; + if (diet.id == lastChosenDiet.dietId) { + isCurrentDietStarted = true; + } else { + if (lastChosenDiet.dietId != null) { + final chosenDiet = await dietsRepository.getDiet(lastChosenDiet.dietId!); + userStartedDietName = chosenDiet.name; + } + } + } + emit(AboutDietState.loaded(diet: diet, isStarted: isCurrentDietStarted, hasStartedDiet: hasStartedDiet)); } catch (e) { log(e.toString()); } @@ -45,7 +65,7 @@ class AboutDietCubit extends Cubit { if (dietIndexInFeed != null) { dietsRepository.emitLikesStateToFeed(LikesState(wasLikedByMe: updatedDiet.wasLikedByMe, totalLikes: updatedDiet.totalLikes, index: dietIndexInFeed)); } - emit(AboutDietState.loaded(updatedDiet)); + emit(AboutDietState.loaded(diet: updatedDiet, isStarted: isCurrentDietStarted, hasStartedDiet: hasStartedDiet)); } else { final updatedDiet = currentDiet.copyWith( totalLikes: currentDiet.totalLikes + 1, @@ -54,7 +74,7 @@ class AboutDietCubit extends Cubit { if (dietIndexInFeed != null) { dietsRepository.emitLikesStateToFeed(LikesState(wasLikedByMe: updatedDiet.wasLikedByMe, totalLikes: updatedDiet.totalLikes, index: dietIndexInFeed)); } - emit(AboutDietState.loaded(updatedDiet)); + emit(AboutDietState.loaded(diet: updatedDiet, isStarted: isCurrentDietStarted, hasStartedDiet: hasStartedDiet)); } EasyDebounce.debounce( @@ -69,7 +89,7 @@ class AboutDietCubit extends Cubit { dietsRepository .emitLikesStateToFeed(LikesState(wasLikedByMe: currentDiet.wasLikedByMe, totalLikes: currentDiet.totalLikes, index: dietIndexInFeed)); } - emit(AboutDietState.loaded(currentDiet)); + emit(AboutDietState.loaded(diet: currentDiet, isStarted: isCurrentDietStarted, hasStartedDiet: hasStartedDiet)); } }, ); @@ -86,7 +106,7 @@ class AboutDietCubit extends Cubit { if (dietIndex != null) { dietsRepository.emitFavoriteStateToFeed(FavoriteState(isFavorite: updatedDiet.isFavorite, index: dietIndex)); } - emit(AboutDietState.loaded(updatedDiet)); + emit(AboutDietState.loaded(diet: updatedDiet, isStarted: isCurrentDietStarted, hasStartedDiet: hasStartedDiet)); EasyDebounce.debounce( 'add_or_remove_diet_to_favorites', @@ -100,7 +120,7 @@ class AboutDietCubit extends Cubit { if (dietIndex != null) { dietsRepository.emitFavoriteStateToFeed(FavoriteState(isFavorite: currentDiet.isFavorite, index: dietIndex)); } - emit(AboutDietState.loaded(currentDiet)); + emit(AboutDietState.loaded(diet: currentDiet, isStarted: isCurrentDietStarted, hasStartedDiet: hasStartedDiet)); } }, ); @@ -117,7 +137,7 @@ class AboutDietCubit extends Cubit { if (dietIndex != null) { dietsRepository.emitFavoriteStateToFeed(FavoriteState(isFavorite: updatedDiet.isFavorite, index: dietIndex)); } - emit(AboutDietState.loaded(updatedDiet)); + emit(AboutDietState.loaded(diet: updatedDiet, isStarted: isCurrentDietStarted, hasStartedDiet: hasStartedDiet)); EasyDebounce.debounce( 'add_or_remove_diet_to_favorites', @@ -131,9 +151,50 @@ class AboutDietCubit extends Cubit { if (dietIndex != null) { dietsRepository.emitFavoriteStateToFeed(FavoriteState(isFavorite: currentDiet.isFavorite, index: dietIndex)); } - emit(AboutDietState.loaded(currentDiet)); + emit(AboutDietState.loaded(diet: currentDiet, isStarted: isCurrentDietStarted, hasStartedDiet: hasStartedDiet)); } }, ); } + + Future startDiet(DateTime date) async { + try { + if (state is! DietStateLoaded) return false; + + final id = (state as DietStateLoaded).diet.id; + final stringDate = date.format('yyyy-MM-dd'); + await dietsRepository.startDiet(id, stringDate); + return true; + } catch (e, st) { + log(e.toString(), stackTrace: st); + return false; + } + } + + Future cancelDiet() async { + try { + if (state is! DietStateLoaded) return false; + final id = (state as DietStateLoaded).diet.id; + await dietsRepository.cancelDiet(id); + return true; + } on DioException catch (e) { + log(e.toString()); + return false; + } catch (e) { + log(e.toString()); + return false; + } + } + + void clearStartedDietsState() { + if (state is! DietStateLoaded) return; + final diet = (state as DietStateLoaded).diet; + emit(AboutDietState.loaded(diet: diet)); + } + + void emitCurrentStartedDietsState() { + if (state is! DietStateLoaded) return; + final diet = (state as DietStateLoaded).diet; + emit(AboutDietState.loaded(diet: diet, isStarted: true)); + } } diff --git a/lib/features/diets/presentation/cubit/about_diet_state.dart b/lib/features/diets/presentation/cubit/about_diet_state.dart index 36a78a31..ba9234e8 100644 --- a/lib/features/diets/presentation/cubit/about_diet_state.dart +++ b/lib/features/diets/presentation/cubit/about_diet_state.dart @@ -9,6 +9,10 @@ class AboutDietState with _$AboutDietState { const factory AboutDietState.initial() = DietStateInitial; const factory AboutDietState.loading() = DietStateLoading; - const factory AboutDietState.loaded(Diet diet) = DietStateLoaded; + const factory AboutDietState.loaded({ + required Diet diet, + @Default(false) bool isStarted, + @Default(false) bool hasStartedDiet, + }) = DietStateLoaded; const factory AboutDietState.error({required String message}) = DietStateError; } diff --git a/lib/features/diets/presentation/cubit/about_diet_state.freezed.dart b/lib/features/diets/presentation/cubit/about_diet_state.freezed.dart index ce2270dc..493edb32 100644 --- a/lib/features/diets/presentation/cubit/about_diet_state.freezed.dart +++ b/lib/features/diets/presentation/cubit/about_diet_state.freezed.dart @@ -21,7 +21,8 @@ mixin _$AboutDietState { TResult Function() $default, { required TResult Function() initial, required TResult Function() loading, - required TResult Function(Diet diet) loaded, + required TResult Function(Diet diet, bool isStarted, bool hasStartedDiet) + loaded, required TResult Function(String message) error, }) => throw _privateConstructorUsedError; @@ -30,7 +31,7 @@ mixin _$AboutDietState { TResult? Function()? $default, { TResult? Function()? initial, TResult? Function()? loading, - TResult? Function(Diet diet)? loaded, + TResult? Function(Diet diet, bool isStarted, bool hasStartedDiet)? loaded, TResult? Function(String message)? error, }) => throw _privateConstructorUsedError; @@ -39,7 +40,7 @@ mixin _$AboutDietState { TResult Function()? $default, { TResult Function()? initial, TResult Function()? loading, - TResult Function(Diet diet)? loaded, + TResult Function(Diet diet, bool isStarted, bool hasStartedDiet)? loaded, TResult Function(String message)? error, required TResult orElse(), }) => @@ -139,7 +140,8 @@ class _$AboutDietStateImpl implements _AboutDietState { TResult Function() $default, { required TResult Function() initial, required TResult Function() loading, - required TResult Function(Diet diet) loaded, + required TResult Function(Diet diet, bool isStarted, bool hasStartedDiet) + loaded, required TResult Function(String message) error, }) { return $default(); @@ -151,7 +153,7 @@ class _$AboutDietStateImpl implements _AboutDietState { TResult? Function()? $default, { TResult? Function()? initial, TResult? Function()? loading, - TResult? Function(Diet diet)? loaded, + TResult? Function(Diet diet, bool isStarted, bool hasStartedDiet)? loaded, TResult? Function(String message)? error, }) { return $default?.call(); @@ -163,7 +165,7 @@ class _$AboutDietStateImpl implements _AboutDietState { TResult Function()? $default, { TResult Function()? initial, TResult Function()? loading, - TResult Function(Diet diet)? loaded, + TResult Function(Diet diet, bool isStarted, bool hasStartedDiet)? loaded, TResult Function(String message)? error, required TResult orElse(), }) { @@ -262,7 +264,8 @@ class _$DietStateInitialImpl implements DietStateInitial { TResult Function() $default, { required TResult Function() initial, required TResult Function() loading, - required TResult Function(Diet diet) loaded, + required TResult Function(Diet diet, bool isStarted, bool hasStartedDiet) + loaded, required TResult Function(String message) error, }) { return initial(); @@ -274,7 +277,7 @@ class _$DietStateInitialImpl implements DietStateInitial { TResult? Function()? $default, { TResult? Function()? initial, TResult? Function()? loading, - TResult? Function(Diet diet)? loaded, + TResult? Function(Diet diet, bool isStarted, bool hasStartedDiet)? loaded, TResult? Function(String message)? error, }) { return initial?.call(); @@ -286,7 +289,7 @@ class _$DietStateInitialImpl implements DietStateInitial { TResult Function()? $default, { TResult Function()? initial, TResult Function()? loading, - TResult Function(Diet diet)? loaded, + TResult Function(Diet diet, bool isStarted, bool hasStartedDiet)? loaded, TResult Function(String message)? error, required TResult orElse(), }) { @@ -385,7 +388,8 @@ class _$DietStateLoadingImpl implements DietStateLoading { TResult Function() $default, { required TResult Function() initial, required TResult Function() loading, - required TResult Function(Diet diet) loaded, + required TResult Function(Diet diet, bool isStarted, bool hasStartedDiet) + loaded, required TResult Function(String message) error, }) { return loading(); @@ -397,7 +401,7 @@ class _$DietStateLoadingImpl implements DietStateLoading { TResult? Function()? $default, { TResult? Function()? initial, TResult? Function()? loading, - TResult? Function(Diet diet)? loaded, + TResult? Function(Diet diet, bool isStarted, bool hasStartedDiet)? loaded, TResult? Function(String message)? error, }) { return loading?.call(); @@ -409,7 +413,7 @@ class _$DietStateLoadingImpl implements DietStateLoading { TResult Function()? $default, { TResult Function()? initial, TResult Function()? loading, - TResult Function(Diet diet)? loaded, + TResult Function(Diet diet, bool isStarted, bool hasStartedDiet)? loaded, TResult Function(String message)? error, required TResult orElse(), }) { @@ -470,7 +474,7 @@ abstract class _$$DietStateLoadedImplCopyWith<$Res> { $Res Function(_$DietStateLoadedImpl) then) = __$$DietStateLoadedImplCopyWithImpl<$Res>; @useResult - $Res call({Diet diet}); + $Res call({Diet diet, bool isStarted, bool hasStartedDiet}); $DietCopyWith<$Res> get diet; } @@ -489,12 +493,22 @@ class __$$DietStateLoadedImplCopyWithImpl<$Res> @override $Res call({ Object? diet = null, + Object? isStarted = null, + Object? hasStartedDiet = null, }) { return _then(_$DietStateLoadedImpl( - null == diet + diet: null == diet ? _value.diet : diet // ignore: cast_nullable_to_non_nullable as Diet, + isStarted: null == isStarted + ? _value.isStarted + : isStarted // ignore: cast_nullable_to_non_nullable + as bool, + hasStartedDiet: null == hasStartedDiet + ? _value.hasStartedDiet + : hasStartedDiet // ignore: cast_nullable_to_non_nullable + as bool, )); } @@ -512,14 +526,23 @@ class __$$DietStateLoadedImplCopyWithImpl<$Res> /// @nodoc class _$DietStateLoadedImpl implements DietStateLoaded { - const _$DietStateLoadedImpl(this.diet); + const _$DietStateLoadedImpl( + {required this.diet, + this.isStarted = false, + this.hasStartedDiet = false}); @override final Diet diet; + @override + @JsonKey() + final bool isStarted; + @override + @JsonKey() + final bool hasStartedDiet; @override String toString() { - return 'AboutDietState.loaded(diet: $diet)'; + return 'AboutDietState.loaded(diet: $diet, isStarted: $isStarted, hasStartedDiet: $hasStartedDiet)'; } @override @@ -527,11 +550,15 @@ class _$DietStateLoadedImpl implements DietStateLoaded { return identical(this, other) || (other.runtimeType == runtimeType && other is _$DietStateLoadedImpl && - (identical(other.diet, diet) || other.diet == diet)); + (identical(other.diet, diet) || other.diet == diet) && + (identical(other.isStarted, isStarted) || + other.isStarted == isStarted) && + (identical(other.hasStartedDiet, hasStartedDiet) || + other.hasStartedDiet == hasStartedDiet)); } @override - int get hashCode => Object.hash(runtimeType, diet); + int get hashCode => Object.hash(runtimeType, diet, isStarted, hasStartedDiet); /// Create a copy of AboutDietState /// with the given fields replaced by the non-null parameter values. @@ -548,10 +575,11 @@ class _$DietStateLoadedImpl implements DietStateLoaded { TResult Function() $default, { required TResult Function() initial, required TResult Function() loading, - required TResult Function(Diet diet) loaded, + required TResult Function(Diet diet, bool isStarted, bool hasStartedDiet) + loaded, required TResult Function(String message) error, }) { - return loaded(diet); + return loaded(diet, isStarted, hasStartedDiet); } @override @@ -560,10 +588,10 @@ class _$DietStateLoadedImpl implements DietStateLoaded { TResult? Function()? $default, { TResult? Function()? initial, TResult? Function()? loading, - TResult? Function(Diet diet)? loaded, + TResult? Function(Diet diet, bool isStarted, bool hasStartedDiet)? loaded, TResult? Function(String message)? error, }) { - return loaded?.call(diet); + return loaded?.call(diet, isStarted, hasStartedDiet); } @override @@ -572,12 +600,12 @@ class _$DietStateLoadedImpl implements DietStateLoaded { TResult Function()? $default, { TResult Function()? initial, TResult Function()? loading, - TResult Function(Diet diet)? loaded, + TResult Function(Diet diet, bool isStarted, bool hasStartedDiet)? loaded, TResult Function(String message)? error, required TResult orElse(), }) { if (loaded != null) { - return loaded(diet); + return loaded(diet, isStarted, hasStartedDiet); } return orElse(); } @@ -624,9 +652,14 @@ class _$DietStateLoadedImpl implements DietStateLoaded { } abstract class DietStateLoaded implements AboutDietState { - const factory DietStateLoaded(final Diet diet) = _$DietStateLoadedImpl; + const factory DietStateLoaded( + {required final Diet diet, + final bool isStarted, + final bool hasStartedDiet}) = _$DietStateLoadedImpl; Diet get diet; + bool get isStarted; + bool get hasStartedDiet; /// Create a copy of AboutDietState /// with the given fields replaced by the non-null parameter values. @@ -707,7 +740,8 @@ class _$DietStateErrorImpl implements DietStateError { TResult Function() $default, { required TResult Function() initial, required TResult Function() loading, - required TResult Function(Diet diet) loaded, + required TResult Function(Diet diet, bool isStarted, bool hasStartedDiet) + loaded, required TResult Function(String message) error, }) { return error(message); @@ -719,7 +753,7 @@ class _$DietStateErrorImpl implements DietStateError { TResult? Function()? $default, { TResult? Function()? initial, TResult? Function()? loading, - TResult? Function(Diet diet)? loaded, + TResult? Function(Diet diet, bool isStarted, bool hasStartedDiet)? loaded, TResult? Function(String message)? error, }) { return error?.call(message); @@ -731,7 +765,7 @@ class _$DietStateErrorImpl implements DietStateError { TResult Function()? $default, { TResult Function()? initial, TResult Function()? loading, - TResult Function(Diet diet)? loaded, + TResult Function(Diet diet, bool isStarted, bool hasStartedDiet)? loaded, TResult Function(String message)? error, required TResult orElse(), }) { diff --git a/lib/features/diets/presentation/pages/about_diet_screen.dart b/lib/features/diets/presentation/pages/about_diet_screen.dart index 8cd3fd84..1c63c8f9 100644 --- a/lib/features/diets/presentation/pages/about_diet_screen.dart +++ b/lib/features/diets/presentation/pages/about_diet_screen.dart @@ -12,6 +12,7 @@ import 'package:eatvio_app/features/diets/presentation/cubit/about_diet_cubit.da import 'package:eatvio_app/features/diets/presentation/cubit/about_diet_state.dart'; import 'package:eatvio_app/features/diets/presentation/widgets/author_card.dart'; import 'package:eatvio_app/core/widgets/cpfc_container.dart'; +import 'package:eatvio_app/features/diets/presentation/widgets/cancel_diet_bottom_sheet.dart'; import 'package:eatvio_app/features/diets/presentation/widgets/expandable_text.dart'; import 'package:eatvio_app/features/diets/presentation/widgets/info_bouble.dart'; import 'package:eatvio_app/core/widgets/main_background_bouble.dart'; @@ -75,7 +76,7 @@ class _AboutDietScreenState extends State { Widget build(BuildContext context) { final s = S.of(context); return BlocProvider( - create: (context) => dietsCubit..getDietWithLoading(widget.diet.id), + create: (context) => dietsCubit..initDietPage(widget.diet.id), child: BlocProvider( create: (BuildContext context) => commentsCubit ..getComments( @@ -130,18 +131,37 @@ class _AboutDietScreenState extends State { shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), ), - backgroundColor: appTheme.greenA700, + backgroundColor: dietState.isStarted ? appTheme.transparentRed : appTheme.greenA700, onPressed: () { final cubit = context.read(); - showModalBottomSheet( - context: context, - builder: (context) => StartDietBottomSheet( - aboutDietCubit: cubit, - ), - ); + + if (dietState.isStarted) { + showModalBottomSheet( + context: context, + builder: (context) => CancelDietBottomSheet( + aboutDietCubit: cubit, + dietName: dietState.diet.name, + ), + ); + } else if (dietState.hasStartedDiet) { + showModalBottomSheet( + context: context, + builder: (context) => CancelDietBottomSheet( + aboutDietCubit: cubit, + title: 'Вы собираетесь начать диету ${dietState.diet.name}, завершить диету ${cubit.userStartedDietName}?', + ), + ); + } else { + showModalBottomSheet( + context: context, + builder: (context) => StartDietBottomSheet( + aboutDietCubit: cubit, + ), + ); + } }, label: Text( - s.startDiet, + dietState.isStarted ? 'Отменить диету' : s.startDiet, style: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700), ), ), diff --git a/lib/features/diets/presentation/widgets/calendar_widget.dart b/lib/features/diets/presentation/widgets/calendar_widget.dart index bd3fdca2..3ca86847 100644 --- a/lib/features/diets/presentation/widgets/calendar_widget.dart +++ b/lib/features/diets/presentation/widgets/calendar_widget.dart @@ -5,8 +5,8 @@ import 'package:intl/intl.dart'; import 'package:table_calendar/table_calendar.dart'; class CalendarWidget extends StatefulWidget { - const CalendarWidget({super.key}); - + const CalendarWidget({super.key, required this.startDate}); + final List startDate; @override State createState() => _CalendarWidgetState(); } @@ -56,6 +56,9 @@ class _CalendarWidgetState extends State { _selectedDay = selectedDay; _focusedDay = focusedDay; }); + if (widget.startDate.isNotEmpty) { + widget.startDate[0] = selectedDay; + } } }, onFormatChanged: (format) { @@ -69,6 +72,7 @@ class _CalendarWidgetState extends State { _focusedDay = focusedDay; }, calendarStyle: CalendarStyle( + outsideDaysVisible: true, weekendTextStyle: TextStyle( color: appTheme.gray, ), @@ -90,6 +94,17 @@ class _CalendarWidgetState extends State { color: appTheme.blueA400, shape: BoxShape.circle, ), + outsideTextStyle: TextStyle( + color: appTheme.gray, + ), + outsideDecoration: BoxDecoration( + color: appTheme.dividerColor, + shape: BoxShape.circle, + ), + withinRangeDecoration: BoxDecoration( + color: appTheme.blueA400, + shape: BoxShape.circle, + ), todayTextStyle: TextStyle( color: appTheme.gray, ), diff --git a/lib/features/diets/presentation/widgets/cancel_diet_bottom_sheet.dart b/lib/features/diets/presentation/widgets/cancel_diet_bottom_sheet.dart new file mode 100644 index 00000000..08a2dbf2 --- /dev/null +++ b/lib/features/diets/presentation/widgets/cancel_diet_bottom_sheet.dart @@ -0,0 +1,79 @@ +import 'package:eatvio_app/core/utils/toasts.dart'; +import 'package:eatvio_app/core/widgets/bottom_sheet_handler_bar.dart'; +import 'package:eatvio_app/features/diets/presentation/cubit/about_diet_cubit.dart'; +import 'package:eatvio_app/features/diets/presentation/widgets/start_diet_bottom_sheet.dart'; +import 'package:eatvio_app/core/theme/custom_text_style.dart'; +import 'package:eatvio_app/core/theme/theme_helper.dart'; +import 'package:eatvio_app/core/widgets/custom_outlined_button.dart'; +import 'package:flutter/material.dart'; + +class CancelDietBottomSheet extends StatelessWidget { + const CancelDietBottomSheet({super.key, required this.aboutDietCubit, this.title, this.dietName}); + final AboutDietCubit aboutDietCubit; + final String? title; + final String? dietName; + @override + Widget build(BuildContext context) { + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(height: 4), + BottomSheetHandlerBar(), + SizedBox(height: 12), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 25.0), + child: Text( + title ?? 'Вы действительно хотите отменить прохождение диеты ${dietName ?? ''}?', + textAlign: TextAlign.center, + style: TextStyles.headingXLSemibold.copyWith( + color: appTheme.black, + ), + ), + ), + Padding( + padding: EdgeInsets.only(left: 16, right: 16, top: 16, bottom: 28), + child: CustomOutlinedButton( + onPressed: () async { + if (title != null) { + final result = await aboutDietCubit.cancelDiet(); + if (result) { + Navigator.pop(context); + showSuccessToast('предыдущая диета отменена'); + showModalBottomSheet( + context: context, + builder: (context) => StartDietBottomSheet( + aboutDietCubit: aboutDietCubit, + ), + ); + } else { + showErrorToast('Не удалось отменить диету'); + Navigator.pop(context); + } + } else { + final result = await aboutDietCubit.cancelDiet(); + if (result) { + showSuccessToast('Диета отменена'); + Navigator.pop(context); + aboutDietCubit.clearStartedDietsState(); + } else { + showErrorToast('Не удалось отменить диету'); + Navigator.pop(context); + } + } + }, + height: 60, + text: 'Подтвердить', + buttonTextStyle: TextStyles.textLSemibold.copyWith( + color: appTheme.whiteA700, + ), + buttonStyle: ButtonStyle( + backgroundColor: WidgetStatePropertyAll(appTheme.blueA400), + side: WidgetStatePropertyAll(BorderSide.none), + ), + ), + ), + ], + ); + } +} diff --git a/lib/features/diets/presentation/widgets/start_diet_bottom_sheet.dart b/lib/features/diets/presentation/widgets/start_diet_bottom_sheet.dart index b7ac41d5..e54fcf9c 100644 --- a/lib/features/diets/presentation/widgets/start_diet_bottom_sheet.dart +++ b/lib/features/diets/presentation/widgets/start_diet_bottom_sheet.dart @@ -1,3 +1,4 @@ +import 'package:eatvio_app/core/utils/toasts.dart'; import 'package:eatvio_app/core/widgets/bottom_sheet_handler_bar.dart'; import 'package:eatvio_app/core/widgets/custom_divider.dart'; import 'package:eatvio_app/features/diets/presentation/cubit/about_diet_cubit.dart'; @@ -8,9 +9,16 @@ import 'package:eatvio_app/core/theme/theme_helper.dart'; import 'package:eatvio_app/core/widgets/custom_outlined_button.dart'; import 'package:flutter/material.dart'; -class StartDietBottomSheet extends StatelessWidget { +class StartDietBottomSheet extends StatefulWidget { const StartDietBottomSheet({super.key, required this.aboutDietCubit}); final AboutDietCubit aboutDietCubit; + + @override + State createState() => _StartDietBottomSheetState(); +} + +class _StartDietBottomSheetState extends State { + final startDate = [DateTime.now()]; @override Widget build(BuildContext context) { final s = S.of(context); @@ -42,11 +50,23 @@ class StartDietBottomSheet extends StatelessWidget { padding: const EdgeInsets.symmetric(horizontal: 16.0), child: CustomDivider(), ), - CalendarWidget(), + CalendarWidget( + startDate: startDate, + ), Padding( padding: EdgeInsets.only(left: 16, right: 16, top: 16, bottom: 28), child: CustomOutlinedButton( - onPressed: () {}, + onPressed: () async { + final result = await widget.aboutDietCubit.startDiet(startDate[0]); + if (result) { + showSuccessToast('Диета успешно начата'); + Navigator.pop(context); + widget.aboutDietCubit.emitCurrentStartedDietsState(); + } else { + showErrorToast('Не удалось начать диету'); + Navigator.pop(context); + } + }, height: 60, text: s.startDiet, buttonTextStyle: TextStyles.textLSemibold.copyWith( diff --git a/lib/features/diets/repositories/diet_repository.dart b/lib/features/diets/repositories/diet_repository.dart index d0a48e38..070434ed 100644 --- a/lib/features/diets/repositories/diet_repository.dart +++ b/lib/features/diets/repositories/diet_repository.dart @@ -49,6 +49,22 @@ class DietRepository { } } + Future startDiet(int id, String date) async { + try { + return await dietsService.startDiet(id: id, body: {'date': date}); + } catch (e) { + rethrow; + } + } + + Future cancelDiet(int id) async{ + try { + return await dietsService.cancelDiet(id: id); + } catch (e) { + rethrow; + } + } + void emitLikesStateToFeed(LikesState state) => _dietLikeStateStreamController.add(state); void emitFavoriteStateToFeed(FavoriteState state) => _dietFavoriteStateStreamController.add(state); void emitCommentsStateToFeed(CommentsState state) => _dietCommentsStateStreamController.add(state); diff --git a/lib/features/feed/presentation/bloc/plans_cubit/plans_cubit.dart b/lib/features/feed/presentation/bloc/plans_cubit/plans_cubit.dart index 5c2f28fd..69c0f2ee 100644 --- a/lib/features/feed/presentation/bloc/plans_cubit/plans_cubit.dart +++ b/lib/features/feed/presentation/bloc/plans_cubit/plans_cubit.dart @@ -267,7 +267,7 @@ class PlansCubit extends Cubit { Duration(milliseconds: 200), () async { try { - await planRepository.addToFavorite(slug); + await planRepository.removeFromFavorite(slug); } catch (e) { log(e.toString()); showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время'); diff --git a/lib/features/feed/presentation/pages/feed_screen.dart b/lib/features/feed/presentation/pages/feed_screen.dart index 4ff555a1..0dbe0b31 100644 --- a/lib/features/feed/presentation/pages/feed_screen.dart +++ b/lib/features/feed/presentation/pages/feed_screen.dart @@ -10,7 +10,7 @@ import 'package:eatvio_app/features/feed/presentation/bloc/profile_cubit/profile import 'package:eatvio_app/features/feed/presentation/bloc/profile_cubit/profile_state.dart'; import 'package:eatvio_app/features/feed/presentation/bloc/recipes_cubit/recipes_cubit.dart'; import 'package:eatvio_app/features/feed/presentation/widgets/blog_posts.dart'; -import 'package:eatvio_app/features/feed/presentation/widgets/choice_chip_widget.dart'; +import 'package:eatvio_app/features/feed/presentation/widgets/chip_group.dart'; import 'package:eatvio_app/features/feed/presentation/widgets/consultants.dart'; import 'package:eatvio_app/features/feed/presentation/widgets/diets.dart'; import 'package:eatvio_app/features/feed/presentation/widgets/plans.dart'; @@ -18,13 +18,13 @@ import 'package:eatvio_app/features/feed/presentation/widgets/tags_modal_widget. import 'package:eatvio_app/features/feed/presentation/bloc/plans_cubit/plans_cubit.dart'; import 'package:eatvio_app/core/locator_service.dart'; import 'package:eatvio_app/core/theme/theme_helper.dart'; +import 'package:eatvio_app/features/recipes/presentation/pages/recipe_create_screen.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; import '../widgets/recipes.dart'; -final bucket = PageStorageBucket(); class FeedScreen extends StatelessWidget { const FeedScreen({super.key}); @@ -122,6 +122,7 @@ class _FeedScreenContentState extends State { recipesScrollController.dispose(); dietsScrollController.dispose(); plansScrollController.dispose(); + consultantsScrollController.dispose(); super.dispose(); } @@ -179,7 +180,18 @@ class _FeedScreenContentState extends State { floating: true, snap: true, scrolledUnderElevation: 0, - bottom: _chipGroup(), + bottom: PreferredSize( + preferredSize: Size(MediaQuery.of(context).size.width, 32), + child: ChipGroup( + selectedChipIndex: _selectedChipIndex, + chipsLabelList: chipsLabelList, + setSelectedChipIndex: (index) { + setState(() { + _selectedChipIndex = index; + }); + }, + ), + ), title: Row( children: [ CustomSearchBar( @@ -245,7 +257,7 @@ class _FeedScreenContentState extends State { SizedBox(width: 12), switch (_selectedChipIndex) { 1 => AddItemButton( - onTap: () => GoRouter.of(context).pushNamed('recipeCreateScreen'), + onTap: () => GoRouter.of(context).pushNamed(RecipeCreateScreen.name), ), 2 => AddItemButton( onTap: state.profile?.role == 'consultant' ? () => GoRouter.of(context).pushNamed(CreateDietScreen.name) : null, @@ -273,80 +285,4 @@ class _FeedScreenContentState extends State { ), ); } - - _chipGroup() { - return PreferredSize( - preferredSize: Size(MediaQuery.of(context).size.width, 32), - child: Align( - alignment: Alignment.centerLeft, - child: SingleChildScrollView( - padding: EdgeInsets.symmetric(horizontal: 16), - scrollDirection: Axis.horizontal, - child: Row( - children: [ - ChoiceChipWidget( - index: 0, - selectedChipIndex: _selectedChipIndex, - chipsLabelList: chipsLabelList, - onSelected: (bool selected) { - setState(() { - _selectedChipIndex = 0; - FocusScope.of(context).requestFocus(new FocusNode()); - }); - }, - ), - SizedBox(width: 8), - ChoiceChipWidget( - index: 1, - selectedChipIndex: _selectedChipIndex, - chipsLabelList: chipsLabelList, - onSelected: (bool selected) { - setState(() { - _selectedChipIndex = 1; - FocusScope.of(context).requestFocus(new FocusNode()); - }); - }, - ), - SizedBox(width: 8), - ChoiceChipWidget( - index: 2, - selectedChipIndex: _selectedChipIndex, - chipsLabelList: chipsLabelList, - onSelected: (bool selected) { - setState(() { - _selectedChipIndex = 2; - FocusScope.of(context).requestFocus(new FocusNode()); - }); - }, - ), - SizedBox(width: 8), - ChoiceChipWidget( - index: 3, - selectedChipIndex: _selectedChipIndex, - chipsLabelList: chipsLabelList, - onSelected: (bool selected) { - setState(() { - _selectedChipIndex = 3; - FocusScope.of(context).requestFocus(new FocusNode()); - }); - }, - ), - SizedBox(width: 8), - ChoiceChipWidget( - index: 4, - selectedChipIndex: _selectedChipIndex, - chipsLabelList: chipsLabelList, - onSelected: (bool selected) { - setState(() { - _selectedChipIndex = 4; - FocusScope.of(context).requestFocus(new FocusNode()); - }); - }, - ), - ], - ), - ), - ), - ); - } } diff --git a/lib/features/feed/presentation/widgets/chip_group.dart b/lib/features/feed/presentation/widgets/chip_group.dart new file mode 100644 index 00000000..44f8b241 --- /dev/null +++ b/lib/features/feed/presentation/widgets/chip_group.dart @@ -0,0 +1,44 @@ +import 'package:eatvio_app/features/feed/presentation/widgets/choice_chip_widget.dart'; +import 'package:flutter/widgets.dart'; + +class ChipGroup extends StatelessWidget { + const ChipGroup({ + super.key, + required this.selectedChipIndex, + required this.chipsLabelList, + required this.setSelectedChipIndex, + }); + final int selectedChipIndex; + final List chipsLabelList; + final void Function(int) setSelectedChipIndex; + + @override + Widget build(BuildContext context) { + return Align( + alignment: Alignment.centerLeft, + child: SingleChildScrollView( + padding: EdgeInsets.symmetric(horizontal: 16), + scrollDirection: Axis.horizontal, + child: Row( + children: [ + ...List.generate( + 5, + (index) => Padding( + padding: EdgeInsets.only(right: 8.0), + child: ChoiceChipWidget( + index: index, + selectedChipIndex: selectedChipIndex, + chipsLabelList: chipsLabelList, + onSelected: (bool selected) { + setSelectedChipIndex(index); + FocusScope.of(context).requestFocus(new FocusNode()); + }, + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/features/menu/presentation/pages/menu_screen.dart b/lib/features/menu/presentation/pages/menu_screen.dart index 7f784c9d..c935af35 100644 --- a/lib/features/menu/presentation/pages/menu_screen.dart +++ b/lib/features/menu/presentation/pages/menu_screen.dart @@ -67,7 +67,7 @@ class MenuScreen extends StatelessWidget { ), _divider(context), _tile( - leading: ImageConstant.icnSupport, + leading: ImageConstant.icnDevTools, title: 'Для разработчиков', onTap: () { GoRouter.of(context).pushNamed(DevToolsScreen.name); diff --git a/lib/features/profile/presentation/pages/profile_screen.dart b/lib/features/profile/presentation/pages/profile_screen.dart index 19a20315..fb4f11d8 100644 --- a/lib/features/profile/presentation/pages/profile_screen.dart +++ b/lib/features/profile/presentation/pages/profile_screen.dart @@ -337,7 +337,7 @@ class _ProfileScreenState extends State { padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: InkWell( onTap: () { - GoRouter.of(context).pushNamed('blogPostCreateScreen'); + GoRouter.of(context).pushNamed('blogPostCreateScreen'); }, borderRadius: BorderRadius.circular(8), child: Ink( diff --git a/lib/features/profile/repositories/profile_repository.dart b/lib/features/profile/repositories/profile_repository.dart index 21f5e7b3..5a1da306 100644 --- a/lib/features/profile/repositories/profile_repository.dart +++ b/lib/features/profile/repositories/profile_repository.dart @@ -1,6 +1,7 @@ import 'dart:io'; import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart'; +import 'package:eatvio_app/features/profile/models/chosen_diets_pagination_model.dart'; import 'package:eatvio_app/features/profile/models/circumference_model.dart'; import 'package:eatvio_app/features/profile/models/profile_model.dart'; import 'package:eatvio_app/features/profile/models/sub_model.dart'; @@ -171,4 +172,12 @@ class ProfileRepository { rethrow; } } + + Future> getChosenDiets() async { + try { + return (await profileService.getChosenDiets()).diets; + } catch (e) { + rethrow; + } + } } diff --git a/lib/features/recipes/entities/recipe_create_entity.dart b/lib/features/recipes/entities/recipe_create_entity.dart index 43a44a55..75e7c783 100644 --- a/lib/features/recipes/entities/recipe_create_entity.dart +++ b/lib/features/recipes/entities/recipe_create_entity.dart @@ -1,5 +1,8 @@ import 'dart:io'; +import 'package:eatvio_app/features/recipes/models/ingredient.dart'; +import 'package:eatvio_app/features/recipes/models/recipe_model.dart'; +import 'package:eatvio_app/features/recipes/models/step.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; part 'recipe_create_entity.freezed.dart'; @@ -11,11 +14,71 @@ enum TypeOfRecipe { snacks, sweets, drinks, - other, + other; + + String toString() { + switch (this) { + case TypeOfRecipe.first_meals: + return 'Первые блюда'; + case TypeOfRecipe.second_meals: + return 'Вторые блюда'; + case TypeOfRecipe.salads: + return 'Салаты'; + case TypeOfRecipe.snacks: + return 'Закуски'; + case TypeOfRecipe.sweets: + return 'Сладости'; + case TypeOfRecipe.drinks: + return 'Напитки'; + case TypeOfRecipe.other: + return 'Другое'; + } + } + + String toApiType() { + switch (this) { + case TypeOfRecipe.first_meals: + return 'first_meals'; + case TypeOfRecipe.second_meals: + return 'second_meals'; + case TypeOfRecipe.salads: + return 'salads'; + case TypeOfRecipe.snacks: + return 'snacks'; + case TypeOfRecipe.sweets: + return 'sweets'; + case TypeOfRecipe.drinks: + return 'drinks'; + case TypeOfRecipe.other: + return 'other'; + } + } + + static TypeOfRecipe? fromString(String value) { + switch (value) { + case 'Первые блюда': + return TypeOfRecipe.first_meals; + case 'Вторые блюда': + return TypeOfRecipe.second_meals; + case 'Салаты': + return TypeOfRecipe.salads; + case 'Закуски': + return TypeOfRecipe.snacks; + case 'Сладости': + return TypeOfRecipe.sweets; + case 'Напитки': + return TypeOfRecipe.drinks; + case 'Другое': + return TypeOfRecipe.other; + default: + return null; + } + } } @freezed class RecipeCreateEntity with _$RecipeCreateEntity { + const RecipeCreateEntity._(); const factory RecipeCreateEntity({ String? title, TypeOfRecipe? typeOfRecipe, @@ -26,19 +89,25 @@ class RecipeCreateEntity with _$RecipeCreateEntity { String? description, File? image, String? videoUrl, - required List ingredients, + required List ingredients, required List steps, required List tags, // tags for search }) = _RecipeCreateEntity; -} -@freezed -class IngredientEntity with _$IngredientEntity { - const factory IngredientEntity({ - required String name, - required String unit, - required String portion, - }) = _IngredientEntity; + RecipeModel toModel() => RecipeModel( + title: title, + type: typeOfRecipe?.toApiType(), + servings: countOfPortions, + energyPerServing: energyInOnePortion?.toInt(), + totalCookingTime: totalCookTime?.split(':').fold(0, (value, element) => (int.tryParse(element) ?? 0)), + activeCookTime: activeCookTime?.split(':').fold(0, (value, element) => (int.tryParse(element) ?? 0)), + description: description, + image: image?.path, + video_url: videoUrl, + ingredients: ingredients, + steps: steps.map((step) => Step(image: step.image?.path ?? '', description: step.description ?? '')).toList(), + tags: tags, + ); } @freezed diff --git a/lib/features/recipes/entities/recipe_create_entity.freezed.dart b/lib/features/recipes/entities/recipe_create_entity.freezed.dart index e42db8f4..aabda874 100644 --- a/lib/features/recipes/entities/recipe_create_entity.freezed.dart +++ b/lib/features/recipes/entities/recipe_create_entity.freezed.dart @@ -25,7 +25,7 @@ mixin _$RecipeCreateEntity { String? get description => throw _privateConstructorUsedError; File? get image => throw _privateConstructorUsedError; String? get videoUrl => throw _privateConstructorUsedError; - List get ingredients => throw _privateConstructorUsedError; + List get ingredients => throw _privateConstructorUsedError; List get steps => throw _privateConstructorUsedError; List get tags => throw _privateConstructorUsedError; @@ -52,7 +52,7 @@ abstract class $RecipeCreateEntityCopyWith<$Res> { String? description, File? image, String? videoUrl, - List ingredients, + List ingredients, List steps, List tags}); } @@ -125,7 +125,7 @@ class _$RecipeCreateEntityCopyWithImpl<$Res, $Val extends RecipeCreateEntity> ingredients: null == ingredients ? _value.ingredients : ingredients // ignore: cast_nullable_to_non_nullable - as List, + as List, steps: null == steps ? _value.steps : steps // ignore: cast_nullable_to_non_nullable @@ -156,7 +156,7 @@ abstract class _$$RecipeCreateEntityImplCopyWith<$Res> String? description, File? image, String? videoUrl, - List ingredients, + List ingredients, List steps, List tags}); } @@ -227,7 +227,7 @@ class __$$RecipeCreateEntityImplCopyWithImpl<$Res> ingredients: null == ingredients ? _value._ingredients : ingredients // ignore: cast_nullable_to_non_nullable - as List, + as List, steps: null == steps ? _value._steps : steps // ignore: cast_nullable_to_non_nullable @@ -242,7 +242,7 @@ class __$$RecipeCreateEntityImplCopyWithImpl<$Res> /// @nodoc -class _$RecipeCreateEntityImpl implements _RecipeCreateEntity { +class _$RecipeCreateEntityImpl extends _RecipeCreateEntity { const _$RecipeCreateEntityImpl( {this.title, this.typeOfRecipe, @@ -253,12 +253,13 @@ class _$RecipeCreateEntityImpl implements _RecipeCreateEntity { this.description, this.image, this.videoUrl, - required final List ingredients, + required final List ingredients, required final List steps, required final List tags}) : _ingredients = ingredients, _steps = steps, - _tags = tags; + _tags = tags, + super._(); @override final String? title; @@ -280,9 +281,9 @@ class _$RecipeCreateEntityImpl implements _RecipeCreateEntity { final File? image; @override final String? videoUrl; - final List _ingredients; + final List _ingredients; @override - List get ingredients { + List get ingredients { if (_ingredients is EqualUnmodifiableListView) return _ingredients; // ignore: implicit_dynamic_type return EqualUnmodifiableListView(_ingredients); @@ -362,7 +363,7 @@ class _$RecipeCreateEntityImpl implements _RecipeCreateEntity { this, _$identity); } -abstract class _RecipeCreateEntity implements RecipeCreateEntity { +abstract class _RecipeCreateEntity extends RecipeCreateEntity { const factory _RecipeCreateEntity( {final String? title, final TypeOfRecipe? typeOfRecipe, @@ -373,9 +374,10 @@ abstract class _RecipeCreateEntity implements RecipeCreateEntity { final String? description, final File? image, final String? videoUrl, - required final List ingredients, + required final List ingredients, required final List steps, required final List tags}) = _$RecipeCreateEntityImpl; + const _RecipeCreateEntity._() : super._(); @override String? get title; @@ -396,7 +398,7 @@ abstract class _RecipeCreateEntity implements RecipeCreateEntity { @override String? get videoUrl; @override - List get ingredients; + List get ingredients; @override List get steps; @override @@ -410,171 +412,6 @@ abstract class _RecipeCreateEntity implements RecipeCreateEntity { throw _privateConstructorUsedError; } -/// @nodoc -mixin _$IngredientEntity { - String get name => throw _privateConstructorUsedError; - String get unit => throw _privateConstructorUsedError; - String get portion => throw _privateConstructorUsedError; - - /// Create a copy of IngredientEntity - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - $IngredientEntityCopyWith get copyWith => - throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $IngredientEntityCopyWith<$Res> { - factory $IngredientEntityCopyWith( - IngredientEntity value, $Res Function(IngredientEntity) then) = - _$IngredientEntityCopyWithImpl<$Res, IngredientEntity>; - @useResult - $Res call({String name, String unit, String portion}); -} - -/// @nodoc -class _$IngredientEntityCopyWithImpl<$Res, $Val extends IngredientEntity> - implements $IngredientEntityCopyWith<$Res> { - _$IngredientEntityCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - /// Create a copy of IngredientEntity - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? name = null, - Object? unit = null, - Object? portion = null, - }) { - return _then(_value.copyWith( - name: null == name - ? _value.name - : name // ignore: cast_nullable_to_non_nullable - as String, - unit: null == unit - ? _value.unit - : unit // ignore: cast_nullable_to_non_nullable - as String, - portion: null == portion - ? _value.portion - : portion // ignore: cast_nullable_to_non_nullable - as String, - ) as $Val); - } -} - -/// @nodoc -abstract class _$$IngredientEntityImplCopyWith<$Res> - implements $IngredientEntityCopyWith<$Res> { - factory _$$IngredientEntityImplCopyWith(_$IngredientEntityImpl value, - $Res Function(_$IngredientEntityImpl) then) = - __$$IngredientEntityImplCopyWithImpl<$Res>; - @override - @useResult - $Res call({String name, String unit, String portion}); -} - -/// @nodoc -class __$$IngredientEntityImplCopyWithImpl<$Res> - extends _$IngredientEntityCopyWithImpl<$Res, _$IngredientEntityImpl> - implements _$$IngredientEntityImplCopyWith<$Res> { - __$$IngredientEntityImplCopyWithImpl(_$IngredientEntityImpl _value, - $Res Function(_$IngredientEntityImpl) _then) - : super(_value, _then); - - /// Create a copy of IngredientEntity - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? name = null, - Object? unit = null, - Object? portion = null, - }) { - return _then(_$IngredientEntityImpl( - name: null == name - ? _value.name - : name // ignore: cast_nullable_to_non_nullable - as String, - unit: null == unit - ? _value.unit - : unit // ignore: cast_nullable_to_non_nullable - as String, - portion: null == portion - ? _value.portion - : portion // ignore: cast_nullable_to_non_nullable - as String, - )); - } -} - -/// @nodoc - -class _$IngredientEntityImpl implements _IngredientEntity { - const _$IngredientEntityImpl( - {required this.name, required this.unit, required this.portion}); - - @override - final String name; - @override - final String unit; - @override - final String portion; - - @override - String toString() { - return 'IngredientEntity(name: $name, unit: $unit, portion: $portion)'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$IngredientEntityImpl && - (identical(other.name, name) || other.name == name) && - (identical(other.unit, unit) || other.unit == unit) && - (identical(other.portion, portion) || other.portion == portion)); - } - - @override - int get hashCode => Object.hash(runtimeType, name, unit, portion); - - /// Create a copy of IngredientEntity - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - @override - @pragma('vm:prefer-inline') - _$$IngredientEntityImplCopyWith<_$IngredientEntityImpl> get copyWith => - __$$IngredientEntityImplCopyWithImpl<_$IngredientEntityImpl>( - this, _$identity); -} - -abstract class _IngredientEntity implements IngredientEntity { - const factory _IngredientEntity( - {required final String name, - required final String unit, - required final String portion}) = _$IngredientEntityImpl; - - @override - String get name; - @override - String get unit; - @override - String get portion; - - /// Create a copy of IngredientEntity - /// with the given fields replaced by the non-null parameter values. - @override - @JsonKey(includeFromJson: false, includeToJson: false) - _$$IngredientEntityImplCopyWith<_$IngredientEntityImpl> get copyWith => - throw _privateConstructorUsedError; -} - /// @nodoc mixin _$StepEntity { String? get description => throw _privateConstructorUsedError; diff --git a/lib/features/recipes/models/ingredient.dart b/lib/features/recipes/models/ingredient.dart index d7a130e4..480f358a 100644 --- a/lib/features/recipes/models/ingredient.dart +++ b/lib/features/recipes/models/ingredient.dart @@ -13,4 +13,4 @@ class Ingredient with _$Ingredient { }) = _Ingredient; factory Ingredient.fromJson(Map json) => _$IngredientFromJson(json); -} +} \ No newline at end of file diff --git a/lib/features/recipes/models/recipe_model.dart b/lib/features/recipes/models/recipe_model.dart index 04add902..588650dd 100644 --- a/lib/features/recipes/models/recipe_model.dart +++ b/lib/features/recipes/models/recipe_model.dart @@ -11,13 +11,17 @@ class RecipeModel with _$RecipeModel { const factory RecipeModel({ List? ingredients, List? steps, + List? tags, String? type, String? state, String? title, String? description, String? video_url, String? image, - int? cooking_time, + int? totalCookingTime, + int? activeCookTime, + int? servings, + int? energyPerServing, }) = _RecipeModel; factory RecipeModel.fromJson(Map json) => _$RecipeModelFromJson(json); diff --git a/lib/features/recipes/models/recipe_model.freezed.dart b/lib/features/recipes/models/recipe_model.freezed.dart index 691ac1f3..7b549e53 100644 --- a/lib/features/recipes/models/recipe_model.freezed.dart +++ b/lib/features/recipes/models/recipe_model.freezed.dart @@ -22,13 +22,17 @@ RecipeModel _$RecipeModelFromJson(Map json) { mixin _$RecipeModel { List? get ingredients => throw _privateConstructorUsedError; List? get steps => throw _privateConstructorUsedError; + List? get tags => throw _privateConstructorUsedError; String? get type => throw _privateConstructorUsedError; String? get state => throw _privateConstructorUsedError; String? get title => throw _privateConstructorUsedError; String? get description => throw _privateConstructorUsedError; String? get video_url => throw _privateConstructorUsedError; String? get image => throw _privateConstructorUsedError; - int? get cooking_time => throw _privateConstructorUsedError; + int? get totalCookingTime => throw _privateConstructorUsedError; + int? get activeCookTime => throw _privateConstructorUsedError; + int? get servings => throw _privateConstructorUsedError; + int? get energyPerServing => throw _privateConstructorUsedError; /// Serializes this RecipeModel to a JSON map. Map toJson() => throw _privateConstructorUsedError; @@ -49,13 +53,17 @@ abstract class $RecipeModelCopyWith<$Res> { $Res call( {List? ingredients, List? steps, + List? tags, String? type, String? state, String? title, String? description, String? video_url, String? image, - int? cooking_time}); + int? totalCookingTime, + int? activeCookTime, + int? servings, + int? energyPerServing}); } /// @nodoc @@ -75,13 +83,17 @@ class _$RecipeModelCopyWithImpl<$Res, $Val extends RecipeModel> $Res call({ Object? ingredients = freezed, Object? steps = freezed, + Object? tags = freezed, Object? type = freezed, Object? state = freezed, Object? title = freezed, Object? description = freezed, Object? video_url = freezed, Object? image = freezed, - Object? cooking_time = freezed, + Object? totalCookingTime = freezed, + Object? activeCookTime = freezed, + Object? servings = freezed, + Object? energyPerServing = freezed, }) { return _then(_value.copyWith( ingredients: freezed == ingredients @@ -92,6 +104,10 @@ class _$RecipeModelCopyWithImpl<$Res, $Val extends RecipeModel> ? _value.steps : steps // ignore: cast_nullable_to_non_nullable as List?, + tags: freezed == tags + ? _value.tags + : tags // ignore: cast_nullable_to_non_nullable + as List?, type: freezed == type ? _value.type : type // ignore: cast_nullable_to_non_nullable @@ -116,9 +132,21 @@ class _$RecipeModelCopyWithImpl<$Res, $Val extends RecipeModel> ? _value.image : image // ignore: cast_nullable_to_non_nullable as String?, - cooking_time: freezed == cooking_time - ? _value.cooking_time - : cooking_time // ignore: cast_nullable_to_non_nullable + totalCookingTime: freezed == totalCookingTime + ? _value.totalCookingTime + : totalCookingTime // ignore: cast_nullable_to_non_nullable + as int?, + activeCookTime: freezed == activeCookTime + ? _value.activeCookTime + : activeCookTime // ignore: cast_nullable_to_non_nullable + as int?, + servings: freezed == servings + ? _value.servings + : servings // ignore: cast_nullable_to_non_nullable + as int?, + energyPerServing: freezed == energyPerServing + ? _value.energyPerServing + : energyPerServing // ignore: cast_nullable_to_non_nullable as int?, ) as $Val); } @@ -135,13 +163,17 @@ abstract class _$$RecipeModelImplCopyWith<$Res> $Res call( {List? ingredients, List? steps, + List? tags, String? type, String? state, String? title, String? description, String? video_url, String? image, - int? cooking_time}); + int? totalCookingTime, + int? activeCookTime, + int? servings, + int? energyPerServing}); } /// @nodoc @@ -159,13 +191,17 @@ class __$$RecipeModelImplCopyWithImpl<$Res> $Res call({ Object? ingredients = freezed, Object? steps = freezed, + Object? tags = freezed, Object? type = freezed, Object? state = freezed, Object? title = freezed, Object? description = freezed, Object? video_url = freezed, Object? image = freezed, - Object? cooking_time = freezed, + Object? totalCookingTime = freezed, + Object? activeCookTime = freezed, + Object? servings = freezed, + Object? energyPerServing = freezed, }) { return _then(_$RecipeModelImpl( ingredients: freezed == ingredients @@ -176,6 +212,10 @@ class __$$RecipeModelImplCopyWithImpl<$Res> ? _value._steps : steps // ignore: cast_nullable_to_non_nullable as List?, + tags: freezed == tags + ? _value._tags + : tags // ignore: cast_nullable_to_non_nullable + as List?, type: freezed == type ? _value.type : type // ignore: cast_nullable_to_non_nullable @@ -200,9 +240,21 @@ class __$$RecipeModelImplCopyWithImpl<$Res> ? _value.image : image // ignore: cast_nullable_to_non_nullable as String?, - cooking_time: freezed == cooking_time - ? _value.cooking_time - : cooking_time // ignore: cast_nullable_to_non_nullable + totalCookingTime: freezed == totalCookingTime + ? _value.totalCookingTime + : totalCookingTime // ignore: cast_nullable_to_non_nullable + as int?, + activeCookTime: freezed == activeCookTime + ? _value.activeCookTime + : activeCookTime // ignore: cast_nullable_to_non_nullable + as int?, + servings: freezed == servings + ? _value.servings + : servings // ignore: cast_nullable_to_non_nullable + as int?, + energyPerServing: freezed == energyPerServing + ? _value.energyPerServing + : energyPerServing // ignore: cast_nullable_to_non_nullable as int?, )); } @@ -214,15 +266,20 @@ class _$RecipeModelImpl implements _RecipeModel { const _$RecipeModelImpl( {final List? ingredients, final List? steps, + final List? tags, this.type, this.state, this.title, this.description, this.video_url, this.image, - this.cooking_time}) + this.totalCookingTime, + this.activeCookTime, + this.servings, + this.energyPerServing}) : _ingredients = ingredients, - _steps = steps; + _steps = steps, + _tags = tags; factory _$RecipeModelImpl.fromJson(Map json) => _$$RecipeModelImplFromJson(json); @@ -247,6 +304,16 @@ class _$RecipeModelImpl implements _RecipeModel { return EqualUnmodifiableListView(value); } + final List? _tags; + @override + List? get tags { + final value = _tags; + if (value == null) return null; + if (_tags is EqualUnmodifiableListView) return _tags; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(value); + } + @override final String? type; @override @@ -260,11 +327,17 @@ class _$RecipeModelImpl implements _RecipeModel { @override final String? image; @override - final int? cooking_time; + final int? totalCookingTime; + @override + final int? activeCookTime; + @override + final int? servings; + @override + final int? energyPerServing; @override String toString() { - return 'RecipeModel(ingredients: $ingredients, steps: $steps, type: $type, state: $state, title: $title, description: $description, video_url: $video_url, image: $image, cooking_time: $cooking_time)'; + return 'RecipeModel(ingredients: $ingredients, steps: $steps, tags: $tags, type: $type, state: $state, title: $title, description: $description, video_url: $video_url, image: $image, totalCookingTime: $totalCookingTime, activeCookTime: $activeCookTime, servings: $servings, energyPerServing: $energyPerServing)'; } @override @@ -275,6 +348,7 @@ class _$RecipeModelImpl implements _RecipeModel { const DeepCollectionEquality() .equals(other._ingredients, _ingredients) && const DeepCollectionEquality().equals(other._steps, _steps) && + const DeepCollectionEquality().equals(other._tags, _tags) && (identical(other.type, type) || other.type == type) && (identical(other.state, state) || other.state == state) && (identical(other.title, title) || other.title == title) && @@ -283,8 +357,14 @@ class _$RecipeModelImpl implements _RecipeModel { (identical(other.video_url, video_url) || other.video_url == video_url) && (identical(other.image, image) || other.image == image) && - (identical(other.cooking_time, cooking_time) || - other.cooking_time == cooking_time)); + (identical(other.totalCookingTime, totalCookingTime) || + other.totalCookingTime == totalCookingTime) && + (identical(other.activeCookTime, activeCookTime) || + other.activeCookTime == activeCookTime) && + (identical(other.servings, servings) || + other.servings == servings) && + (identical(other.energyPerServing, energyPerServing) || + other.energyPerServing == energyPerServing)); } @JsonKey(includeFromJson: false, includeToJson: false) @@ -293,13 +373,17 @@ class _$RecipeModelImpl implements _RecipeModel { runtimeType, const DeepCollectionEquality().hash(_ingredients), const DeepCollectionEquality().hash(_steps), + const DeepCollectionEquality().hash(_tags), type, state, title, description, video_url, image, - cooking_time); + totalCookingTime, + activeCookTime, + servings, + energyPerServing); /// Create a copy of RecipeModel /// with the given fields replaced by the non-null parameter values. @@ -321,13 +405,17 @@ abstract class _RecipeModel implements RecipeModel { const factory _RecipeModel( {final List? ingredients, final List? steps, + final List? tags, final String? type, final String? state, final String? title, final String? description, final String? video_url, final String? image, - final int? cooking_time}) = _$RecipeModelImpl; + final int? totalCookingTime, + final int? activeCookTime, + final int? servings, + final int? energyPerServing}) = _$RecipeModelImpl; factory _RecipeModel.fromJson(Map json) = _$RecipeModelImpl.fromJson; @@ -337,6 +425,8 @@ abstract class _RecipeModel implements RecipeModel { @override List? get steps; @override + List? get tags; + @override String? get type; @override String? get state; @@ -349,7 +439,13 @@ abstract class _RecipeModel implements RecipeModel { @override String? get image; @override - int? get cooking_time; + int? get totalCookingTime; + @override + int? get activeCookTime; + @override + int? get servings; + @override + int? get energyPerServing; /// Create a copy of RecipeModel /// with the given fields replaced by the non-null parameter values. diff --git a/lib/features/recipes/models/recipe_model.g.dart b/lib/features/recipes/models/recipe_model.g.dart index d5ab9c96..478765ae 100644 --- a/lib/features/recipes/models/recipe_model.g.dart +++ b/lib/features/recipes/models/recipe_model.g.dart @@ -16,24 +16,32 @@ _$RecipeModelImpl _$$RecipeModelImplFromJson(Map json) => ?.map((e) => e == null ? null : Step.fromJson(e as Map)) .toList(), + tags: (json['tags'] as List?)?.map((e) => e as String).toList(), type: json['type'] as String?, state: json['state'] as String?, title: json['title'] as String?, description: json['description'] as String?, video_url: json['video_url'] as String?, image: json['image'] as String?, - cooking_time: (json['cooking_time'] as num?)?.toInt(), + totalCookingTime: (json['totalCookingTime'] as num?)?.toInt(), + activeCookTime: (json['activeCookTime'] as num?)?.toInt(), + servings: (json['servings'] as num?)?.toInt(), + energyPerServing: (json['energyPerServing'] as num?)?.toInt(), ); Map _$$RecipeModelImplToJson(_$RecipeModelImpl instance) => { 'ingredients': instance.ingredients, 'steps': instance.steps, + 'tags': instance.tags, 'type': instance.type, 'state': instance.state, 'title': instance.title, 'description': instance.description, 'video_url': instance.video_url, 'image': instance.image, - 'cooking_time': instance.cooking_time, + 'totalCookingTime': instance.totalCookingTime, + 'activeCookTime': instance.activeCookTime, + 'servings': instance.servings, + 'energyPerServing': instance.energyPerServing, }; diff --git a/lib/features/recipes/presentation/bloc/create_recipe_cubit/create_recipe_cubit.dart b/lib/features/recipes/presentation/bloc/create_recipe_cubit/create_recipe_cubit.dart index 03253f26..78112360 100644 --- a/lib/features/recipes/presentation/bloc/create_recipe_cubit/create_recipe_cubit.dart +++ b/lib/features/recipes/presentation/bloc/create_recipe_cubit/create_recipe_cubit.dart @@ -1,14 +1,18 @@ import 'dart:io'; import 'package:eatvio_app/features/recipes/entities/recipe_create_entity.dart'; +import 'package:eatvio_app/features/recipes/models/ingredient.dart'; import 'package:eatvio_app/features/recipes/presentation/bloc/create_recipe_cubit/create_recipe_state.dart'; +import 'package:eatvio_app/features/recipes/reporitories/recipe_repository.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; class CreateRecipeCubit extends Cubit { - CreateRecipeCubit() + final RecipeRepository recipeRepository; + CreateRecipeCubit({required this.recipeRepository}) : super( CreateRecipeState( RecipeCreateEntity( + typeOfRecipe: TypeOfRecipe.first_meals, ingredients: [], steps: [], tags: [], @@ -38,7 +42,7 @@ class CreateRecipeCubit extends Cubit { ); } - addIngredient(IngredientEntity entity) { + addIngredient(Ingredient entity) { emit( CreateRecipeState( state.entity.copyWith( @@ -52,7 +56,7 @@ class CreateRecipeCubit extends Cubit { } removeIngredient(int index) { - List ingredients = [...state.entity.ingredients]; + List ingredients = [...state.entity.ingredients]; ingredients.removeAt(index); emit( @@ -65,8 +69,8 @@ class CreateRecipeCubit extends Cubit { } replaceIngredientPosition(int oldIndex, int newIndex) { - List ingredients = [...state.entity.ingredients]; - final IngredientEntity item = ingredients.removeAt(oldIndex); + List ingredients = [...state.entity.ingredients]; + final item = ingredients.removeAt(oldIndex); ingredients.insert(newIndex, item); emit( CreateRecipeState( @@ -216,4 +220,12 @@ class CreateRecipeCubit extends Cubit { ), ); } + + Future publishRecipe() async { + try { + await recipeRepository.publishRecipe(state.entity.toModel()); + } catch (e) { + rethrow; + } + } } diff --git a/lib/features/recipes/presentation/pages/recipe_create_screen.dart b/lib/features/recipes/presentation/pages/recipe_create_screen.dart index efeace26..78da5627 100644 --- a/lib/features/recipes/presentation/pages/recipe_create_screen.dart +++ b/lib/features/recipes/presentation/pages/recipe_create_screen.dart @@ -10,6 +10,9 @@ import 'package:flutter_bloc/flutter_bloc.dart'; class RecipeCreateScreen extends StatefulWidget { const RecipeCreateScreen({super.key}); + static const name = 'recipeCreateScreen'; + static const path = '/recipe-create-screen'; + @override State createState() => _RecipeCreateScreenState(); } @@ -39,28 +42,34 @@ class _RecipeCreateScreenState extends State { InfoAboutRecipeWidget(), IngredientsWidget(), StepsWidget(), - InkWell( - onTap: () {}, - borderRadius: BorderRadius.circular(20), - child: Ink( - width: MediaQuery.of(context).size.width - 32, - decoration: BoxDecoration( - color: appTheme.blueA400, + Builder( + builder: (context) { + return InkWell( + onTap: () { + context.read().publishRecipe(); + }, borderRadius: BorderRadius.circular(20), - ), - child: Container( - padding: const EdgeInsets.all(16), - alignment: Alignment.center, - child: Text( - 'Опубликовать', - style: TextStyle( - fontSize: 16, - color: Colors.white, - fontWeight: FontWeight.w600, + child: Ink( + width: MediaQuery.of(context).size.width - 32, + decoration: BoxDecoration( + color: appTheme.blueA400, + borderRadius: BorderRadius.circular(20), + ), + child: Container( + padding: const EdgeInsets.all(16), + alignment: Alignment.center, + child: Text( + 'Опубликовать', + style: TextStyle( + fontSize: 16, + color: Colors.white, + fontWeight: FontWeight.w600, + ), + ), ), ), - ), - ), + ); + }, ), ], ), diff --git a/lib/features/recipes/presentation/widgets/info_about_recipe_widget.dart b/lib/features/recipes/presentation/widgets/info_about_recipe_widget.dart index 473cc04a..44ef152e 100644 --- a/lib/features/recipes/presentation/widgets/info_about_recipe_widget.dart +++ b/lib/features/recipes/presentation/widgets/info_about_recipe_widget.dart @@ -18,14 +18,7 @@ class InfoAboutRecipeWidget extends StatefulWidget { } class _InfoAboutRecipeWidgetState extends State { - final List items = const [ - SelectionPopupModel(title: 'Первые Блюда'), - SelectionPopupModel(title: 'Вторые Блюда'), - SelectionPopupModel(title: 'Салаты'), - SelectionPopupModel(title: 'Сладости'), - SelectionPopupModel(title: 'Напитки'), - SelectionPopupModel(title: 'Прочее'), - ]; + final List items = TypeOfRecipe.values.map((e) => SelectionPopupModel(title: e.toString())).toList(); late SelectionPopupModel selectedType; late final CreateRecipeCubit createRecipeCubit; @@ -64,14 +57,7 @@ class _InfoAboutRecipeWidgetState extends State { selectedType = newSelectedType; }); createRecipeCubit.changeType( - switch (selectedType.title) { - 'Первые Блюда' => TypeOfRecipe.first_meals, - 'Вторые Блюда' => TypeOfRecipe.second_meals, - 'Салаты' => TypeOfRecipe.salads, - 'Сладости' => TypeOfRecipe.sweets, - 'Напитки' => TypeOfRecipe.drinks, - _ => TypeOfRecipe.other, - }, + TypeOfRecipe.fromString(selectedType.title) ?? TypeOfRecipe.other, ); }, ), diff --git a/lib/features/recipes/presentation/widgets/ingredients_widget.dart b/lib/features/recipes/presentation/widgets/ingredients_widget.dart index e1ac4748..5f66ed87 100644 --- a/lib/features/recipes/presentation/widgets/ingredients_widget.dart +++ b/lib/features/recipes/presentation/widgets/ingredients_widget.dart @@ -1,6 +1,6 @@ import 'package:eatvio_app/core/theme/theme_helper.dart'; import 'package:eatvio_app/core/widgets/custom_text_form_field.dart'; -import 'package:eatvio_app/features/recipes/entities/recipe_create_entity.dart'; +import 'package:eatvio_app/features/recipes/models/ingredient.dart'; import 'package:eatvio_app/features/recipes/presentation/bloc/create_recipe_cubit/create_recipe_cubit.dart'; import 'package:eatvio_app/features/recipes/presentation/bloc/create_recipe_cubit/create_recipe_state.dart'; import 'package:flutter/material.dart'; @@ -161,7 +161,7 @@ class _IngredientsWidgetState extends State { onTap: () { if (nameController.text.isNotEmpty && (pcsController.text.isNotEmpty || gramsController.text.isNotEmpty)) { createRecipeCubit.addIngredient( - IngredientEntity( + Ingredient( name: nameController.text, unit: gramsController.text.isEmpty ? pcsController.text : gramsController.text, portion: gramsController.text.isEmpty ? 'pcs' : 'gr', diff --git a/lib/features/recipes/reporitories/recipe_repository.dart b/lib/features/recipes/reporitories/recipe_repository.dart index bea4d06d..b9c49aef 100644 --- a/lib/features/recipes/reporitories/recipe_repository.dart +++ b/lib/features/recipes/reporitories/recipe_repository.dart @@ -19,11 +19,14 @@ class RecipeRepository { } } - Future publishRecipe(RecipeModel recipe) async { + Future publishRecipe(RecipeModel recipe) async { try { - final File file = File(recipe.image ?? ''); + if (recipe.image == null || (recipe.image?.isEmpty ?? true)) { + return null; + } + final file = File(recipe.image ?? ''); - List> ingredientsMap = recipe.ingredients?.map((ingredient) { + final ingredientsMap = recipe.ingredients?.map((ingredient) { return { 'name': ingredient!.name, 'unit': ingredient.unit, @@ -32,25 +35,42 @@ class RecipeRepository { }).toList() ?? []; - List> stepsMap = recipe.steps?.map((step) { + final stepsMap = recipe.steps?.map((step) { return { - 'image': File(step?.image ?? ""), 'description': step?.description ?? "", }; }).toList() ?? []; - return await recipeService.publishRecipe( - recipe.type, - recipe.state, - recipe.title, - recipe.description, - recipe.video_url, - ingredientsMap, - stepsMap, - recipe.cooking_time, - file, - ); + // recipe.steps?.map((step) { + // return File(step?.image ?? ""); + // }).toList() ?? + // []; + + if (recipe.type != null && + recipe.description != null && + recipe.totalCookingTime != null && + recipe.activeCookTime != null && + recipe.servings != null && + recipe.energyPerServing != null && + ingredientsMap.isNotEmpty) { + return await recipeService.publishRecipe( + type: recipe.type!, + state: 'active', + title: recipe.title, + tags: recipe.tags, + description: recipe.description!, + video_url: recipe.video_url, + ingredients: ingredientsMap, + steps: stepsMap, + cooking_time: recipe.totalCookingTime!, + active_time: recipe.activeCookTime!, + image: file, + servings: recipe.servings!, + energy_per_serving: recipe.energyPerServing!, + ); + } + return null; } catch (e) { rethrow; } diff --git a/lib/features/recipes/services/recipe_service.dart b/lib/features/recipes/services/recipe_service.dart index 54703dc5..2e70e730 100644 --- a/lib/features/recipes/services/recipe_service.dart +++ b/lib/features/recipes/services/recipe_service.dart @@ -19,18 +19,25 @@ abstract class RecipeService { @GET('/api/diary/product_nutritional_values/filter') Future>> getInputProduct(@Query('name') String name); + @POST('/api/recipes/create') - Future publishRecipe( - @Part() String? type, - @Part() String? state, + @MultiPart() + Future publishRecipe({ + @Part() required String type, + @Part() required String state, @Part() String? title, - @Part() String? description, + @Part() required String description, @Part() String? video_url, - @Part() List> ingredients, - @Part() List> steps, + @Part() List>? ingredients, + @Part() List>? steps, + @Part() List? tags, @Part() int? cooking_time, - @Part() File image, - ); + @Part() int? active_time, + @Part() required int servings, + @Part() required int energy_per_serving, + @Part() File? image, + + }); @POST('/api/recipes/update/{id}') Future editRecipe(@Part() Map recipe, String id); diff --git a/lib/features/recipes/services/recipe_service.g.dart b/lib/features/recipes/services/recipe_service.g.dart index 20e02b55..396070b1 100644 --- a/lib/features/recipes/services/recipe_service.g.dart +++ b/lib/features/recipes/services/recipe_service.g.dart @@ -92,75 +92,95 @@ class _RecipeService implements RecipeService { } @override - Future publishRecipe( - String? type, - String? state, + Future publishRecipe({ + required String type, + required String state, String? title, - String? description, + required String description, String? video_url, - List> ingredients, - List> steps, + List>? ingredients, + List>? steps, + List? tags, int? cooking_time, - File image, - ) async { + int? active_time, + required int servings, + required int energy_per_serving, + File? image, + }) async { final _extra = {}; final queryParameters = {}; queryParameters.removeWhere((k, v) => v == null); final _headers = {}; final _data = FormData(); - if (type != null) { - _data.fields.add(MapEntry( - 'type', - type, - )); - } - if (state != null) { - _data.fields.add(MapEntry( - 'state', - state, - )); - } + _data.fields.add(MapEntry( + 'type', + type, + )); + _data.fields.add(MapEntry( + 'state', + state, + )); if (title != null) { _data.fields.add(MapEntry( 'title', title, )); } - if (description != null) { - _data.fields.add(MapEntry( - 'description', - description, - )); - } + _data.fields.add(MapEntry( + 'description', + description, + )); if (video_url != null) { _data.fields.add(MapEntry( 'video_url', video_url, )); } - ingredients.forEach((i) { + ingredients?.forEach((i) { _data.fields.add(MapEntry('ingredients', jsonEncode(i))); }); - steps.forEach((i) { + steps?.forEach((i) { _data.fields.add(MapEntry('steps', jsonEncode(i))); }); + tags?.forEach((i) { + _data.fields.add(MapEntry('tags', i)); + }); if (cooking_time != null) { _data.fields.add(MapEntry( 'cooking_time', cooking_time.toString(), )); } - _data.files.add(MapEntry( - 'image', - MultipartFile.fromFileSync( - image.path, - filename: image.path.split(Platform.pathSeparator).last, - ), + if (active_time != null) { + _data.fields.add(MapEntry( + 'active_time', + active_time.toString(), + )); + } + _data.fields.add(MapEntry( + 'servings', + servings.toString(), )); + _data.fields.add(MapEntry( + 'energy_per_serving', + energy_per_serving.toString(), + )); + if (image != null) { + if (image != null) { + _data.files.add(MapEntry( + 'image', + MultipartFile.fromFileSync( + image.path, + filename: image.path.split(Platform.pathSeparator).last, + ), + )); + } + } final _options = _setStreamType(Options( method: 'POST', headers: _headers, extra: _extra, + contentType: 'multipart/form-data', ) .compose( _dio.options, diff --git a/lib/features/registration/presentation/bloc/registration_cubit/registration_cubit.dart b/lib/features/registration/presentation/bloc/registration_cubit/registration_cubit.dart index 6fa7cbd6..c5b5133e 100644 --- a/lib/features/registration/presentation/bloc/registration_cubit/registration_cubit.dart +++ b/lib/features/registration/presentation/bloc/registration_cubit/registration_cubit.dart @@ -1,3 +1,5 @@ +import 'dart:developer'; + import 'package:eatvio_app/core/app_router.dart'; import 'package:eatvio_app/core/token_storage.dart'; @@ -57,7 +59,7 @@ class RegistrationCubit extends Cubit { await TokenStorage.saveTokenInStorage(token); AppRouter.navigatorKey.currentContext?.go('/my-profile-screen'); } catch (e) { - rethrow; + log(e.toString()); } } diff --git a/lib/features/settings/services/settings_service.dart b/lib/features/settings/services/settings_service.dart index 71d5de73..d17b5842 100644 --- a/lib/features/settings/services/settings_service.dart +++ b/lib/features/settings/services/settings_service.dart @@ -12,12 +12,12 @@ abstract class SettingsService { @GET('/api/profile/settings') Future getProfileSettings(); - @POST('/api/profile/settings/update') + @PUT('/api/profile/settings/update') Future updateProfileSettings({ @Body() required Map data, }); - @POST('/api/profile/update') + @PUT('/api/profile/update') Future updateProfileDetails({ @Body() required Map data, }); diff --git a/lib/features/settings/services/settings_service.g.dart b/lib/features/settings/services/settings_service.g.dart index 53d31175..d08c842a 100644 --- a/lib/features/settings/services/settings_service.g.dart +++ b/lib/features/settings/services/settings_service.g.dart @@ -63,7 +63,7 @@ class _SettingsService implements SettingsService { final _data = {}; _data.addAll(data); final _options = _setStreamType(Options( - method: 'POST', + method: 'PUT', headers: _headers, extra: _extra, ) @@ -98,7 +98,7 @@ class _SettingsService implements SettingsService { final _data = {}; _data.addAll(data); final _options = _setStreamType(Options( - method: 'POST', + method: 'PUT', headers: _headers, extra: _extra, ) diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 355d15b2..af6cf693 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -226,5 +226,9 @@ "takes_pills": "Принимает лекарства", "sport_hobbies": "Спортивные увлечения", "liked_groceries": "Любимые продукты", - "not_liked_groceries": "Не любимые продукты" + "not_liked_groceries": "Не любимые продукты", + "diet_started": "Диета уже начата", + "cancel_diet": "Отменить диету?", + "confirm": "Подтвердить", + "could_not_cancel_diet": "Не удалось отменить диету" }