From 589122581bb2ea56a282253add5d49f0be8403e2 Mon Sep 17 00:00:00 2001 From: Username1548 Date: Mon, 3 Feb 2025 15:32:12 +0700 Subject: [PATCH 1/4] internet connection handling --- lib/core/app_router.dart | 2 +- .../presentation/cubit/create_diet_cubit.dart | 4 +- .../presentation/dev_tools_screen.dart | 157 +++++---- lib/features/dev_tools/test_cases.dart | 237 +++++++++++++- lib/features/feed/models/recipes_model.dart | 10 +- .../feed/models/recipes_model.freezed.dart | 136 ++++---- lib/features/feed/models/recipes_model.g.dart | 14 +- .../bloc/recipes_cubit/recipes_cubit.dart | 4 +- .../feed/presentation/widgets/recipes.dart | 2 +- .../presentation/widgets/recipes_widget.dart | 4 +- .../internet_connection_checker_screen.dart | 172 ++++++++++ .../bloc/plans_cubit/plans_cubit.dart | 4 +- .../entities/recipe_create_entity.dart | 9 +- .../recipe_create_entity.freezed.dart | 7 +- lib/features/recipes/models/ingredient.dart | 2 +- .../create_recipe_cubit.dart | 261 +++++++-------- .../create_recipe_state.dart | 16 +- .../create_recipe_state.freezed.dart | 188 ++++++++++- .../pages/about_recipes_screen.dart | 20 +- .../pages/recipe_create_screen.dart | 60 ++-- .../presentation/widgets/image_widget.dart | 22 +- .../widgets/info_about_recipe_widget.dart | 83 ++++- .../widgets/ingredients_widget.dart | 6 +- .../presentation/widgets/portion_widget.dart | 114 ++++--- .../widgets/time_of_cook_widget.dart | 306 +++++++++++------- .../reporitories/recipe_repository.dart | 87 ++--- .../recipes/services/recipe_service.dart | 23 +- .../recipes/services/recipe_service.g.dart | 95 +----- lib/main.dart | 4 +- pubspec.yaml | 1 + 30 files changed, 1384 insertions(+), 666 deletions(-) create mode 100644 lib/features/internet_connection_checker/internet_connection_checker_screen.dart diff --git a/lib/core/app_router.dart b/lib/core/app_router.dart index 39daded4..b7239f07 100644 --- a/lib/core/app_router.dart +++ b/lib/core/app_router.dart @@ -54,7 +54,7 @@ class AppRouter { initialLocation: '/auth-screen', navigatorKey: navigatorKey, routes: [ - GoRoute( + GoRoute( path: '/auth-screen', name: 'authScreen', redirect: (context, state) async { diff --git a/lib/features/create_diet/presentation/cubit/create_diet_cubit.dart b/lib/features/create_diet/presentation/cubit/create_diet_cubit.dart index 666643a8..67107cea 100644 --- a/lib/features/create_diet/presentation/cubit/create_diet_cubit.dart +++ b/lib/features/create_diet/presentation/cubit/create_diet_cubit.dart @@ -304,7 +304,7 @@ class CreateDietCubit extends Cubit { } if (state.dietData.name == null || state.dietData.name == '') { result = false; - emit(state.copyWith(dietNameError: 'Поле не заполненно')); + emit(state.copyWith(dietNameError: 'Поле не заполнено')); } if ((state.dietData.details?.length ?? 0) < 3) { result = false; @@ -312,7 +312,7 @@ class CreateDietCubit extends Cubit { } if (state.dietData.details == null || state.dietData.details == '') { result = false; - emit(state.copyWith(dietDescriptionError: 'Поле не заполненно')); + emit(state.copyWith(dietDescriptionError: 'Поле не заполнено')); } if (state.dietData.duration == null) { result = false; diff --git a/lib/features/dev_tools/presentation/dev_tools_screen.dart b/lib/features/dev_tools/presentation/dev_tools_screen.dart index ea6c3817..e2e5ab48 100644 --- a/lib/features/dev_tools/presentation/dev_tools_screen.dart +++ b/lib/features/dev_tools/presentation/dev_tools_screen.dart @@ -25,9 +25,28 @@ class DevToolsScreenContent extends StatefulWidget { class _DevToolsScreenContentState extends State { String baseUrlForTest = ''; - final List testCases = [Test850task(), GetChatRoomPreviews(), GetChatRoom(), SendMessage(), GetChatMessages(), GetChatRoomIdByUserSlug()]; + final List testCases = [ + Test850task(), + GetChatRoomPreviews(), + GetChatRoom(), + SendMessage(), + GetChatMessages(), + GetChatRoomIdByUserSlug(), + GetBlogPostList(), + GetRecipes(), + GetDiets(), + GetPlans(), + GetBlogPostComments(), + SendBlogPostComment(), + GetDietsComments(), + SendDietsComment(), + GetPlansComments(), + SendPlansComment(), + GetRecipesComments(), + ]; final controller = TextEditingController(); late final List testsStates; + @override void initState() { super.initState(); @@ -40,7 +59,6 @@ class _DevToolsScreenContentState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar(), body: SingleChildScrollView( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 10), @@ -73,76 +91,89 @@ class _DevToolsScreenContentState extends State { ), ], ), - SizedBox( + const SizedBox( height: 10, ), + if (baseUrlForTest != baseUrl) + CustomOutlinedButton( + text: 'Set dev2 base url', + onPressed: () { + setState(() { + this.baseUrlForTest = baseUrl; + }); + }, + buttonStyle: ButtonStyle(), + ), + SizedBox( + height: baseUrlForTest != baseUrl ? 40 : 10, + ), if (baseUrlForTest != 'https://eatvio.ru') ListView.builder( shrinkWrap: true, itemCount: testCases.length, - itemBuilder: (context, index) => Row( - children: [ - Text('${index + 1}. ${testCases[index].name}'), - SizedBox( - width: 50, - ), - Expanded( - child: CustomOutlinedButton( - text: 'run', - height: 20, - width: 60, - onPressed: () async { - if (testsStates[index] == TestState.loading) return; - - setState(() { - testsStates[index] = TestState.loading; - }); - final result = await testCases[index].run(baseUrl: baseUrlForTest); - if (result) { - setState(() { - testsStates[index] = TestState.success; - }); - showSuccessToast('${testCases[index].name} passed'); - } else { - setState(() { - testsStates[index] = TestState.fail; - }); - showErrorToast('${testCases[index].name} failed'); - } - }, - buttonStyle: ButtonStyle(), + itemBuilder: (context, index) => Padding( + padding: const EdgeInsets.only(bottom: 10.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Text('${index + 1}. ${testCases[index].name}'), ), - ), - SizedBox( - width: 30, - ), - switch (testsStates[index]) { - TestState.success => Expanded( - child: Container( + Row( + mainAxisSize: MainAxisSize.min, + children: [ + CustomOutlinedButton( + text: 'run', height: 20, - color: Colors.green, + width: 60, + onPressed: () async { + if (testsStates[index] == TestState.loading) return; + + setState(() { + testsStates[index] = TestState.loading; + }); + final result = await testCases[index].run(baseUrl: baseUrlForTest); + if (result) { + setState(() { + testsStates[index] = TestState.success; + }); + showSuccessToast('${testCases[index].name} passed'); + } else { + setState(() { + testsStates[index] = TestState.fail; + }); + showErrorToast('${testCases[index].name} failed'); + } + }, + buttonStyle: ButtonStyle(), ), - ), - TestState.fail => Expanded( - child: Container( - height: 20, - color: Colors.red, - ), - ), - TestState.notRun => Expanded( - child: Container( - height: 20, - color: Colors.grey, - ), - ), - TestState.loading => Expanded( - child: Container( - height: 20, - child: SizedBox(height: 20, width: 20, child: Center(child: CircularProgressIndicator())), - ), - ), - }, - ], + SizedBox(width: 5), + switch (testsStates[index]) { + TestState.success => Container( + width: 20, + height: 20, + color: Colors.green, + ), + TestState.fail => Container( + width: 20, + height: 20, + color: Colors.red, + ), + TestState.notRun => Container( + width: 20, + height: 20, + color: Colors.grey, + ), + TestState.loading => Container( + width: 20, + height: 20, + child: SizedBox(height: 20, width: 20, child: Center(child: CircularProgressIndicator())), + ), + }, + ], + ), + ], + ), ), ), ], diff --git a/lib/features/dev_tools/test_cases.dart b/lib/features/dev_tools/test_cases.dart index cb812174..30eb9c6a 100644 --- a/lib/features/dev_tools/test_cases.dart +++ b/lib/features/dev_tools/test_cases.dart @@ -8,6 +8,7 @@ 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'; +import 'package:eatvio_app/features/recipes/services/recipe_service.dart'; sealed class TestCases { String get name => throw UnimplementedError(); @@ -115,6 +116,7 @@ class Test850task implements TestCases { } } +// ChatService class GetChatRoomPreviews implements TestCases { @override String get name => '/api/chat/index'; @@ -140,7 +142,7 @@ class GetChatRoom implements TestCases { @override Future run({required String baseUrl}) async { - try { + try { final dio = sl(); final chatService = ChatService(dio, baseUrl: baseUrl); @@ -159,7 +161,7 @@ class SendMessage implements TestCases { @override Future run({required String baseUrl}) async { - try { + try { final dio = sl(); final chatService = ChatService(dio, baseUrl: baseUrl); @@ -178,7 +180,7 @@ class GetChatMessages implements TestCases { @override Future run({required String baseUrl}) async { - try { + try { final dio = sl(); final chatService = ChatService(dio, baseUrl: baseUrl); @@ -197,7 +199,7 @@ class GetChatRoomIdByUserSlug implements TestCases { @override Future run({required String baseUrl}) async { - try { + try { final dio = sl(); final chatService = ChatService(dio, baseUrl: baseUrl); final consultantService = FeedService(dio, baseUrl: baseUrl); @@ -217,3 +219,230 @@ class GetChatRoomIdByUserSlug implements TestCases { } } } + +// FeedService +class GetBlogPostList implements TestCases { + @override + String get name => '/api/blogPosts/index'; + + @override + Future run({required String baseUrl}) async { + try { + final dio = sl(); + final feedService = FeedService(dio, baseUrl: baseUrl); + await feedService.getBlogPostList(page: 1); + return true; + } catch (e) { + return false; + } + } +} + +class GetRecipes implements TestCases { + @override + String get name => '/api/recipes/index'; + + @override + Future run({required String baseUrl}) async { + try { + final dio = sl(); + final feedService = FeedService(dio, baseUrl: baseUrl); + final recipeService = RecipeService(dio, baseUrl: baseUrl); + final tagsModel = await recipeService.getTags(); + final tags = tagsModel.data; + final tagsIds = tags.map((e) => e.id).toList(); + await feedService.getRecipes(page: 1, tags: tagsIds, query: null); + return true; + } catch (e) { + return false; + } + } +} + +class GetDiets implements TestCases { + @override + String get name => '/api/diets/index'; + + @override + Future run({required String baseUrl}) async { + try { + final dio = sl(); + final feedService = FeedService(dio, baseUrl: baseUrl); + await feedService.getDiets(page: 1, query: null); + return true; + } catch (e) { + return false; + } + } +} + +class GetPlans implements TestCases { + @override + String get name => '/api/plans/index'; + + @override + Future run({required String baseUrl}) async { + try { + final dio = sl(); + final feedService = FeedService(dio, baseUrl: baseUrl); + await feedService.getPlans(page: 1, query: null); + return true; + } catch (e) { + return false; + } + } +} + +class GetBlogPostComments implements TestCases { + @override + String get name => '/api/blogPosts/{id}/comments/show'; + + @override + Future run({required String baseUrl}) async { + try { + final dio = sl(); + final feedService = FeedService(dio, baseUrl: baseUrl); + final blogPostsModel = await feedService.getBlogPostList(page: 1, query: null); + final blogPosts = blogPostsModel.feedBlogPostList; + await feedService.getBlogPostComments( + id: blogPosts.first.id.toString(), + ); + return true; + } catch (e) { + return false; + } + } +} + +class SendBlogPostComment implements TestCases { + @override + String get name => '/api/blogPosts/{id}/comment/create'; + + @override + Future run({required String baseUrl}) async { + try { + final dio = sl(); + final feedService = FeedService(dio, baseUrl: baseUrl); + final blogPostsModel = await feedService.getBlogPostList(page: 1, query: null); + final blogPosts = blogPostsModel.feedBlogPostList; + await feedService.sendBlogPostComment( + id: blogPosts.first.id.toString(), + body: 'auto test message', + ); + return true; + } catch (e) { + return false; + } + } +} + +class GetDietsComments implements TestCases { + @override + String get name => '/api/diets/{id}/comments/show'; + + @override + Future run({required String baseUrl}) async { + try { + final dio = sl(); + final feedService = FeedService(dio, baseUrl: baseUrl); + final dietsModel = await feedService.getDiets(page: 1, query: null); + final diets = dietsModel.data; + await feedService.getDietsComments( + id: diets.first.id.toString(), + ); + return true; + } catch (e) { + return false; + } + } +} + +class SendDietsComment implements TestCases { + @override + String get name => '/api/diets/{id}/comment/create'; + + @override + Future run({required String baseUrl}) async { + try { + final dio = sl(); + final feedService = FeedService(dio, baseUrl: baseUrl); + final dietsModel = await feedService.getDiets(page: 1, query: null); + final diets = dietsModel.data; + await feedService.sendDietsComment( + id: diets.first.id.toString(), + body: 'auto test message', + ); + return true; + } catch (e) { + return false; + } + } +} + +class GetPlansComments implements TestCases { + @override + String get name => '/api/plans/{slug}/comments/show'; + + @override + Future run({required String baseUrl}) async { + try { + final dio = sl(); + final feedService = FeedService(dio, baseUrl: baseUrl); + final plansModel = await feedService.getPlans(page: 1, query: null); + final plans = plansModel.data; + await feedService.getPlansComments( + slug: plans.first.slug, + ); + return true; + } catch (e) { + return false; + } + } +} + +class SendPlansComment implements TestCases { + @override + String get name => '/api/plans/{slug}/comment/create'; + + @override + Future run({required String baseUrl}) async { + try { + final dio = sl(); + final feedService = FeedService(dio, baseUrl: baseUrl); + final plansModel = await feedService.getPlans(page: 1, query: null); + final plans = plansModel.data; + await feedService.sendPlansComment( + slug: plans.first.slug, + body: 'auto test message', + ); + return true; + } catch (e) { + return false; + } + } +} + +class GetRecipesComments implements TestCases { + @override + String get name => '/api/recipes/{id}/comments/show'; + + @override + Future run({required String baseUrl}) async { + try { + final dio = sl(); + final feedService = FeedService(dio, baseUrl: baseUrl); + final recipeService = RecipeService(dio, baseUrl: baseUrl); + final tagsModel = await recipeService.getTags(); + final tags = tagsModel.data; + final tagsIds = tags.map((e) => e.id).toList(); + final recipesModel = await feedService.getRecipes(page: 1, tags: tagsIds, query: null); + final recipes = recipesModel.recipesList; + await feedService.getRecipesComments( + id: recipes.first.id.toString(), + ); + return true; + } catch (e) { + return false; + } + } +} diff --git a/lib/features/feed/models/recipes_model.dart b/lib/features/feed/models/recipes_model.dart index 8f0db8ad..ff287211 100644 --- a/lib/features/feed/models/recipes_model.dart +++ b/lib/features/feed/models/recipes_model.dart @@ -11,14 +11,14 @@ class Recipes with _$Recipes { required String image, required String state, @JsonKey(name: 'cooking_time') required int cookingTime, - required List ingredients, + required List? ingredients, required String description, - required List steps, - @JsonKey(name: 'video_url') required String videoUrl, + required List? steps, + @JsonKey(name: 'video_url') required String? videoUrl, @JsonKey(name: 'created_at') required DateTime createdAt, @JsonKey(name: 'updated_at') required DateTime updatedAt, - required String title, - required String type, + required String? title, + required String? type, required String slug, @JsonKey(name: 'total_comments') required int commentsCount, @JsonKey(name: 'was_liked_by_me') required bool wasLikedByMe, diff --git a/lib/features/feed/models/recipes_model.freezed.dart b/lib/features/feed/models/recipes_model.freezed.dart index 34d1c39a..75d6ea7b 100644 --- a/lib/features/feed/models/recipes_model.freezed.dart +++ b/lib/features/feed/models/recipes_model.freezed.dart @@ -25,17 +25,17 @@ mixin _$Recipes { String get state => throw _privateConstructorUsedError; @JsonKey(name: 'cooking_time') int get cookingTime => throw _privateConstructorUsedError; - List get ingredients => throw _privateConstructorUsedError; + List? get ingredients => throw _privateConstructorUsedError; String get description => throw _privateConstructorUsedError; - List get steps => throw _privateConstructorUsedError; + List? get steps => throw _privateConstructorUsedError; @JsonKey(name: 'video_url') - String get videoUrl => throw _privateConstructorUsedError; + String? get videoUrl => throw _privateConstructorUsedError; @JsonKey(name: 'created_at') DateTime get createdAt => throw _privateConstructorUsedError; @JsonKey(name: 'updated_at') DateTime get updatedAt => throw _privateConstructorUsedError; - String get title => throw _privateConstructorUsedError; - String get type => throw _privateConstructorUsedError; + String? get title => throw _privateConstructorUsedError; + String? get type => throw _privateConstructorUsedError; String get slug => throw _privateConstructorUsedError; @JsonKey(name: 'total_comments') int get commentsCount => throw _privateConstructorUsedError; @@ -68,14 +68,14 @@ abstract class $RecipesCopyWith<$Res> { String image, String state, @JsonKey(name: 'cooking_time') int cookingTime, - List ingredients, + List? ingredients, String description, - List steps, - @JsonKey(name: 'video_url') String videoUrl, + List? steps, + @JsonKey(name: 'video_url') String? videoUrl, @JsonKey(name: 'created_at') DateTime createdAt, @JsonKey(name: 'updated_at') DateTime updatedAt, - String title, - String type, + String? title, + String? type, String slug, @JsonKey(name: 'total_comments') int commentsCount, @JsonKey(name: 'was_liked_by_me') bool wasLikedByMe, @@ -106,14 +106,14 @@ class _$RecipesCopyWithImpl<$Res, $Val extends Recipes> Object? image = null, Object? state = null, Object? cookingTime = null, - Object? ingredients = null, + Object? ingredients = freezed, Object? description = null, - Object? steps = null, - Object? videoUrl = null, + Object? steps = freezed, + Object? videoUrl = freezed, Object? createdAt = null, Object? updatedAt = null, - Object? title = null, - Object? type = null, + Object? title = freezed, + Object? type = freezed, Object? slug = null, Object? commentsCount = null, Object? wasLikedByMe = null, @@ -139,22 +139,22 @@ class _$RecipesCopyWithImpl<$Res, $Val extends Recipes> ? _value.cookingTime : cookingTime // ignore: cast_nullable_to_non_nullable as int, - ingredients: null == ingredients + ingredients: freezed == ingredients ? _value.ingredients : ingredients // ignore: cast_nullable_to_non_nullable - as List, + as List?, description: null == description ? _value.description : description // ignore: cast_nullable_to_non_nullable as String, - steps: null == steps + steps: freezed == steps ? _value.steps : steps // ignore: cast_nullable_to_non_nullable - as List, - videoUrl: null == videoUrl + as List?, + videoUrl: freezed == videoUrl ? _value.videoUrl : videoUrl // ignore: cast_nullable_to_non_nullable - as String, + as String?, createdAt: null == createdAt ? _value.createdAt : createdAt // ignore: cast_nullable_to_non_nullable @@ -163,14 +163,14 @@ class _$RecipesCopyWithImpl<$Res, $Val extends Recipes> ? _value.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable as DateTime, - title: null == title + title: freezed == title ? _value.title : title // ignore: cast_nullable_to_non_nullable - as String, - type: null == type + as String?, + type: freezed == type ? _value.type : type // ignore: cast_nullable_to_non_nullable - as String, + as String?, slug: null == slug ? _value.slug : slug // ignore: cast_nullable_to_non_nullable @@ -225,14 +225,14 @@ abstract class _$$RecipesImplCopyWith<$Res> implements $RecipesCopyWith<$Res> { String image, String state, @JsonKey(name: 'cooking_time') int cookingTime, - List ingredients, + List? ingredients, String description, - List steps, - @JsonKey(name: 'video_url') String videoUrl, + List? steps, + @JsonKey(name: 'video_url') String? videoUrl, @JsonKey(name: 'created_at') DateTime createdAt, @JsonKey(name: 'updated_at') DateTime updatedAt, - String title, - String type, + String? title, + String? type, String slug, @JsonKey(name: 'total_comments') int commentsCount, @JsonKey(name: 'was_liked_by_me') bool wasLikedByMe, @@ -262,14 +262,14 @@ class __$$RecipesImplCopyWithImpl<$Res> Object? image = null, Object? state = null, Object? cookingTime = null, - Object? ingredients = null, + Object? ingredients = freezed, Object? description = null, - Object? steps = null, - Object? videoUrl = null, + Object? steps = freezed, + Object? videoUrl = freezed, Object? createdAt = null, Object? updatedAt = null, - Object? title = null, - Object? type = null, + Object? title = freezed, + Object? type = freezed, Object? slug = null, Object? commentsCount = null, Object? wasLikedByMe = null, @@ -295,22 +295,22 @@ class __$$RecipesImplCopyWithImpl<$Res> ? _value.cookingTime : cookingTime // ignore: cast_nullable_to_non_nullable as int, - ingredients: null == ingredients + ingredients: freezed == ingredients ? _value._ingredients : ingredients // ignore: cast_nullable_to_non_nullable - as List, + as List?, description: null == description ? _value.description : description // ignore: cast_nullable_to_non_nullable as String, - steps: null == steps + steps: freezed == steps ? _value._steps : steps // ignore: cast_nullable_to_non_nullable - as List, - videoUrl: null == videoUrl + as List?, + videoUrl: freezed == videoUrl ? _value.videoUrl : videoUrl // ignore: cast_nullable_to_non_nullable - as String, + as String?, createdAt: null == createdAt ? _value.createdAt : createdAt // ignore: cast_nullable_to_non_nullable @@ -319,14 +319,14 @@ class __$$RecipesImplCopyWithImpl<$Res> ? _value.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable as DateTime, - title: null == title + title: freezed == title ? _value.title : title // ignore: cast_nullable_to_non_nullable - as String, - type: null == type + as String?, + type: freezed == type ? _value.type : type // ignore: cast_nullable_to_non_nullable - as String, + as String?, slug: null == slug ? _value.slug : slug // ignore: cast_nullable_to_non_nullable @@ -367,9 +367,9 @@ class _$RecipesImpl implements _Recipes { required this.image, required this.state, @JsonKey(name: 'cooking_time') required this.cookingTime, - required final List ingredients, + required final List? ingredients, required this.description, - required final List steps, + required final List? steps, @JsonKey(name: 'video_url') required this.videoUrl, @JsonKey(name: 'created_at') required this.createdAt, @JsonKey(name: 'updated_at') required this.updatedAt, @@ -397,27 +397,31 @@ class _$RecipesImpl implements _Recipes { @override @JsonKey(name: 'cooking_time') final int cookingTime; - final List _ingredients; + final List? _ingredients; @override - List get ingredients { + List? get ingredients { + final value = _ingredients; + if (value == null) return null; if (_ingredients is EqualUnmodifiableListView) return _ingredients; // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_ingredients); + return EqualUnmodifiableListView(value); } @override final String description; - final List _steps; + final List? _steps; @override - List get steps { + List? get steps { + final value = _steps; + if (value == null) return null; if (_steps is EqualUnmodifiableListView) return _steps; // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_steps); + return EqualUnmodifiableListView(value); } @override @JsonKey(name: 'video_url') - final String videoUrl; + final String? videoUrl; @override @JsonKey(name: 'created_at') final DateTime createdAt; @@ -425,9 +429,9 @@ class _$RecipesImpl implements _Recipes { @JsonKey(name: 'updated_at') final DateTime updatedAt; @override - final String title; + final String? title; @override - final String type; + final String? type; @override final String slug; @override @@ -537,14 +541,14 @@ abstract class _Recipes implements Recipes { required final String image, required final String state, @JsonKey(name: 'cooking_time') required final int cookingTime, - required final List ingredients, + required final List? ingredients, required final String description, - required final List steps, - @JsonKey(name: 'video_url') required final String videoUrl, + required final List? steps, + @JsonKey(name: 'video_url') required final String? videoUrl, @JsonKey(name: 'created_at') required final DateTime createdAt, @JsonKey(name: 'updated_at') required final DateTime updatedAt, - required final String title, - required final String type, + required final String? title, + required final String? type, required final String slug, @JsonKey(name: 'total_comments') required final int commentsCount, @JsonKey(name: 'was_liked_by_me') required final bool wasLikedByMe, @@ -565,14 +569,14 @@ abstract class _Recipes implements Recipes { @JsonKey(name: 'cooking_time') int get cookingTime; @override - List get ingredients; + List? get ingredients; @override String get description; @override - List get steps; + List? get steps; @override @JsonKey(name: 'video_url') - String get videoUrl; + String? get videoUrl; @override @JsonKey(name: 'created_at') DateTime get createdAt; @@ -580,9 +584,9 @@ abstract class _Recipes implements Recipes { @JsonKey(name: 'updated_at') DateTime get updatedAt; @override - String get title; + String? get title; @override - String get type; + String? get type; @override String get slug; @override diff --git a/lib/features/feed/models/recipes_model.g.dart b/lib/features/feed/models/recipes_model.g.dart index cc96df42..00da0654 100644 --- a/lib/features/feed/models/recipes_model.g.dart +++ b/lib/features/feed/models/recipes_model.g.dart @@ -12,18 +12,18 @@ _$RecipesImpl _$$RecipesImplFromJson(Map json) => image: json['image'] as String, state: json['state'] as String, cookingTime: (json['cooking_time'] as num).toInt(), - ingredients: (json['ingredients'] as List) - .map((e) => Ingredient.fromJson(e as Map)) + ingredients: (json['ingredients'] as List?) + ?.map((e) => Ingredient.fromJson(e as Map)) .toList(), description: json['description'] as String, - steps: (json['steps'] as List) - .map((e) => Step.fromJson(e as Map)) + steps: (json['steps'] as List?) + ?.map((e) => Step.fromJson(e as Map)) .toList(), - videoUrl: json['video_url'] as String, + videoUrl: json['video_url'] as String?, createdAt: DateTime.parse(json['created_at'] as String), updatedAt: DateTime.parse(json['updated_at'] as String), - title: json['title'] as String, - type: json['type'] as String, + title: json['title'] as String?, + type: json['type'] as String?, slug: json['slug'] as String, commentsCount: (json['total_comments'] as num).toInt(), wasLikedByMe: json['was_liked_by_me'] as bool, diff --git a/lib/features/feed/presentation/bloc/recipes_cubit/recipes_cubit.dart b/lib/features/feed/presentation/bloc/recipes_cubit/recipes_cubit.dart index d3a6a47e..60fdb41a 100644 --- a/lib/features/feed/presentation/bloc/recipes_cubit/recipes_cubit.dart +++ b/lib/features/feed/presentation/bloc/recipes_cubit/recipes_cubit.dart @@ -3,6 +3,7 @@ import 'package:eatvio_app/features/feed/repositories/feed_repository.dart'; import 'package:eatvio_app/features/recipes/models/recipe_tag_model.dart'; import 'package:eatvio_app/features/recipes/reporitories/recipe_repository.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'dart:developer'; class RecipesCubit extends Cubit { final FeedRepository feedRepository; @@ -38,7 +39,8 @@ class RecipesCubit extends Cubit { ), ); lastPage = currentPage; - } catch (e) { + } catch (e, st) { + log(e.toString(), stackTrace: st); emit(RecipesState.error(errorMessage: e.toString())); } } diff --git a/lib/features/feed/presentation/widgets/recipes.dart b/lib/features/feed/presentation/widgets/recipes.dart index 9679e81c..0f188c3b 100644 --- a/lib/features/feed/presentation/widgets/recipes.dart +++ b/lib/features/feed/presentation/widgets/recipes.dart @@ -47,4 +47,4 @@ class Recipes extends StatelessWidget { ), ); } -} \ No newline at end of file +} diff --git a/lib/features/feed/presentation/widgets/recipes_widget.dart b/lib/features/feed/presentation/widgets/recipes_widget.dart index 61936531..f85d7fcc 100644 --- a/lib/features/feed/presentation/widgets/recipes_widget.dart +++ b/lib/features/feed/presentation/widgets/recipes_widget.dart @@ -61,7 +61,7 @@ class _RecipesWidgetState extends State { children: [ _iconButton('assets/icons/clock.svg', widget.recipes.cookingTime.toString() + ' минут'), SizedBox(width: 8), - _iconButton('assets/icons/steps.svg', widget.recipes.steps.length.toString() + ' этапов'), + _iconButton('assets/icons/steps.svg', '${widget.recipes.steps?.length ?? 0}' + ' этапов'), SizedBox(width: 8), _iconButton('assets/icons/calories.svg', '139.9 ккал'), ], @@ -73,7 +73,7 @@ class _RecipesWidgetState extends State { Padding( padding: const EdgeInsets.symmetric(horizontal: 12), child: Text( - widget.recipes.title, + widget.recipes.title.toString(), style: TextStyle( fontWeight: FontWeight.w600, color: Colors.black87, diff --git a/lib/features/internet_connection_checker/internet_connection_checker_screen.dart b/lib/features/internet_connection_checker/internet_connection_checker_screen.dart new file mode 100644 index 00000000..f8d258b1 --- /dev/null +++ b/lib/features/internet_connection_checker/internet_connection_checker_screen.dart @@ -0,0 +1,172 @@ +import 'dart:async'; +import 'dart:developer'; +import 'dart:io'; + +import 'package:connectivity_plus/connectivity_plus.dart'; +import 'package:easy_debounce/easy_debounce.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'; +import 'package:flutter/services.dart'; +import 'package:http/http.dart' as http; + +class InternetConnectionCheckerScreen extends StatefulWidget { + const InternetConnectionCheckerScreen({super.key, required this.child}); + final Widget child; + + @override + State createState() => _InternetConnectionCheckerScreenState(); +} + +class _InternetConnectionCheckerScreenState extends State { + bool _hasInternet = true; + final Connectivity _connectivity = Connectivity(); + late StreamSubscription> _connectivitySubscription; + late final AppLifecycleListener _listener; + + @override + void initState() { + super.initState(); + _checkConnection(); + _connectivitySubscription = _connectivity.onConnectivityChanged.listen(_updateAccessStatus); + _listener = AppLifecycleListener( + onResume: () async { + _connectivitySubscription.resume(); + await _checkConnection(); + }, + onHide: _connectivitySubscription.pause, + onPause: _connectivitySubscription.pause, + ); + } + + Future _checkConnection() async { + late List result; + + try { + result = await _connectivity.checkConnectivity(); + } on PlatformException catch (e) { + log('Couldn\'t check connectivity status', error: e); + return; + } + + return _updateAccessStatus(result); + } + + Future _updateAccessStatus(List result) async { + final hasInternet = await _checkInternetAccess(result); + + setState(() { + _hasInternet = hasInternet; + }); + } + + Future _checkInternetAccess(List result) async { + if (result.contains(ConnectivityResult.none)) { + return false; + } + try { + final response = await Future.any([ + http.get(Uri.parse('https://google.com')), + http.get(Uri.parse('https://yandex.ru')), + http.get(Uri.parse('https://dev2.eatvio.com')), + http.get(Uri.parse('https://eatvio.ru')), + ]).timeout(Duration(seconds: 5)); + + return response.statusCode >= 100 && response.statusCode < 600; + } on SocketException catch (_) { + return false; + } on TimeoutException catch (_) { + return false; + } on HandshakeException catch (_) { + return false; + } on CertificateException catch (_) { + return false; + } on http.ClientException catch (_) { + return false; + } catch (e, st) { + log(e.toString(), stackTrace: st); + return false; + } + } + + @override + void dispose() { + _connectivitySubscription.cancel(); + _listener.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + widget.child, + if (!_hasInternet) + NoInternetScreen( + onTryAgain: () async { + EasyDebounce.debounce( + 'internet connection check', + Duration(seconds: 2), + () async { + await _checkConnection(); + }, + ); + }, + ), + ], + ); + } +} + +class NoInternetScreen extends StatelessWidget { + const NoInternetScreen({super.key, required this.onTryAgain}); + final Future Function() onTryAgain; + @override + Widget build(BuildContext context) { + return Scaffold( + body: Column( + children: [ + Spacer(), + SizedBox(height: 28), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 52), + child: Text( + 'Отсутствует подключение к интернету', + textAlign: TextAlign.center, + style: TextStyles.headingXLSemibold.copyWith( + color: appTheme.black, + ), + ), + ), + SizedBox(height: 16), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 40), + child: Text( + 'Проверьте соединение с сетью и попробуйте ещё раз', + textAlign: TextAlign.center, + style: TextStyles.textMMedium.copyWith( + color: appTheme.gray, + ), + ), + ), + SizedBox(height: 20), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: CustomOutlinedButton( + onPressed: onTryAgain, + height: 52, + text: 'Попробовать ещё раз', + buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700), + buttonStyle: ButtonStyle( + backgroundColor: WidgetStatePropertyAll(appTheme.blueA400), + side: WidgetStatePropertyAll(BorderSide.none), + ), + ), + ), + Spacer(), + ], + ), + ); + } +} diff --git a/lib/features/profile/presentation/bloc/plans_cubit/plans_cubit.dart b/lib/features/profile/presentation/bloc/plans_cubit/plans_cubit.dart index 79eb02a8..4b42c19b 100644 --- a/lib/features/profile/presentation/bloc/plans_cubit/plans_cubit.dart +++ b/lib/features/profile/presentation/bloc/plans_cubit/plans_cubit.dart @@ -1,3 +1,5 @@ +import 'dart:developer'; + import 'package:eatvio_app/features/plans/repositories/plans_repository.dart'; import 'package:eatvio_app/features/profile/presentation/bloc/plans_cubit/plans_state.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -14,7 +16,7 @@ class PlansCubit extends Cubit { try { emit(PlansState.loaded(plansList: await plansRepository.getPlansBySlug(slug))); } catch (e) { - print(e); + log(e.toString()); emit(PlansState.error(errorMessage: e.toString())); } } diff --git a/lib/features/recipes/entities/recipe_create_entity.dart b/lib/features/recipes/entities/recipe_create_entity.dart index 75e7c783..92f7d642 100644 --- a/lib/features/recipes/entities/recipe_create_entity.dart +++ b/lib/features/recipes/entities/recipe_create_entity.dart @@ -105,15 +105,22 @@ class RecipeCreateEntity with _$RecipeCreateEntity { image: image?.path, video_url: videoUrl, ingredients: ingredients, - steps: steps.map((step) => Step(image: step.image?.path ?? '', description: step.description ?? '')).toList(), + steps: steps.map((step) { + if (step.hasProps()) return Step(image: step.image?.path ?? '', description: step.description ?? ''); + }).toList(), tags: tags, ); } @freezed class StepEntity with _$StepEntity { + const StepEntity._(); const factory StepEntity({ String? description, File? image, }) = _StepEntity; + + bool hasProps() { + return description != null; + } } diff --git a/lib/features/recipes/entities/recipe_create_entity.freezed.dart b/lib/features/recipes/entities/recipe_create_entity.freezed.dart index aabda874..57535cc7 100644 --- a/lib/features/recipes/entities/recipe_create_entity.freezed.dart +++ b/lib/features/recipes/entities/recipe_create_entity.freezed.dart @@ -506,8 +506,8 @@ class __$$StepEntityImplCopyWithImpl<$Res> /// @nodoc -class _$StepEntityImpl implements _StepEntity { - const _$StepEntityImpl({this.description, this.image}); +class _$StepEntityImpl extends _StepEntity { + const _$StepEntityImpl({this.description, this.image}) : super._(); @override final String? description; @@ -541,9 +541,10 @@ class _$StepEntityImpl implements _StepEntity { __$$StepEntityImplCopyWithImpl<_$StepEntityImpl>(this, _$identity); } -abstract class _StepEntity implements StepEntity { +abstract class _StepEntity extends StepEntity { const factory _StepEntity({final String? description, final File? image}) = _$StepEntityImpl; + const _StepEntity._() : super._(); @override String? get description; diff --git a/lib/features/recipes/models/ingredient.dart b/lib/features/recipes/models/ingredient.dart index 480f358a..26c979e6 100644 --- a/lib/features/recipes/models/ingredient.dart +++ b/lib/features/recipes/models/ingredient.dart @@ -12,5 +12,5 @@ class Ingredient with _$Ingredient { required String portion, }) = _Ingredient; - factory Ingredient.fromJson(Map json) => _$IngredientFromJson(json); + factory Ingredient.fromJson(Map json) => _$IngredientFromJson(json); } \ No newline at end of file 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 78112360..00d4961b 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,3 +1,4 @@ +import 'dart:developer'; import 'dart:io'; import 'package:eatvio_app/features/recipes/entities/recipe_create_entity.dart'; @@ -11,10 +12,10 @@ class CreateRecipeCubit extends Cubit { CreateRecipeCubit({required this.recipeRepository}) : super( CreateRecipeState( - RecipeCreateEntity( + entity: RecipeCreateEntity( typeOfRecipe: TypeOfRecipe.first_meals, ingredients: [], - steps: [], + steps: [StepEntity()], tags: [], ), ), @@ -22,13 +23,11 @@ class CreateRecipeCubit extends Cubit { addStep() { emit( - CreateRecipeState( - state.entity.copyWith( - steps: [ - ...state.entity.steps, - StepEntity(), - ], - ), + state.copyWith.entity( + steps: [ + ...state.entity.steps, + StepEntity(), + ], ), ); } @@ -36,21 +35,16 @@ class CreateRecipeCubit extends Cubit { removeStep(StepEntity step) { List steps = [...state.entity.steps]; steps.remove(step); - - emit( - CreateRecipeState(state.entity.copyWith(steps: steps)), - ); + emit(state.copyWith.entity(steps: steps)); } addIngredient(Ingredient entity) { emit( - CreateRecipeState( - state.entity.copyWith( - ingredients: [ - ...state.entity.ingredients, - entity, - ], - ), + state.copyWith.entity( + ingredients: [ + ...state.entity.ingredients, + entity, + ], ), ); } @@ -59,67 +53,35 @@ class CreateRecipeCubit extends Cubit { List ingredients = [...state.entity.ingredients]; ingredients.removeAt(index); - emit( - CreateRecipeState( - state.entity.copyWith( - ingredients: ingredients, - ), - ), - ); + emit(state.copyWith.entity(ingredients: ingredients)); } replaceIngredientPosition(int oldIndex, int newIndex) { List ingredients = [...state.entity.ingredients]; final item = ingredients.removeAt(oldIndex); ingredients.insert(newIndex, item); - emit( - CreateRecipeState( - state.entity.copyWith( - ingredients: ingredients, - ), - ), - ); + emit(state.copyWith.entity(ingredients: ingredients)); } replaceTitle(String title) { - emit( - CreateRecipeState( - state.entity.copyWith( - title: title, - ), - ), - ); + emit(state.copyWith.entity(title: title == '' ? null : title)); } replaceDescription(String description) { - emit( - CreateRecipeState( - state.entity.copyWith( - description: description, - ), - ), - ); + emit(state.copyWith.entity(description: description == '' ? null : description)); } changeType(TypeOfRecipe typeOfRecipe) { - emit( - CreateRecipeState( - state.entity.copyWith( - typeOfRecipe: typeOfRecipe, - ), - ), - ); + emit(state.copyWith.entity(typeOfRecipe: typeOfRecipe)); } addTag(String tag) { emit( - CreateRecipeState( - state.entity.copyWith( - tags: [ - ...state.entity.tags, - tag, - ], - ), + state.copyWith.entity( + tags: [ + ...state.entity.tags, + tag, + ], ), ); } @@ -128,104 +90,145 @@ class CreateRecipeCubit extends Cubit { List tags = [...state.entity.tags]; tags.remove(tag); - emit( - CreateRecipeState( - state.entity.copyWith( - tags: tags, - ), - ), - ); + emit(state.copyWith.entity(tags: tags)); } replacePortionsCount(int count) { - emit( - CreateRecipeState( - state.entity.copyWith( - countOfPortions: count, - ), - ), - ); + emit(state.copyWith.entity(countOfPortions: count)); } replaceEnegryInPortion(double energy) { - emit( - CreateRecipeState( - state.entity.copyWith( - energyInOnePortion: energy, - ), - ), - ); + emit(state.copyWith.entity(energyInOnePortion: energy)); } replaceTotalTime(String time) { - emit( - CreateRecipeState( - state.entity.copyWith( - totalCookTime: time, - ), - ), - ); + emit(state.copyWith.entity(totalCookTime: time)); } replaceActiveTime(String time) { - emit( - CreateRecipeState( - state.entity.copyWith( - activeCookTime: time, - ), - ), - ); + emit(state.copyWith.entity(activeCookTime: time)); } replaceVideoUrl(String videoUrl) { - emit( - CreateRecipeState( - state.entity.copyWith( - videoUrl: videoUrl, - ), - ), - ); + emit(state.copyWith.entity(videoUrl: videoUrl)); } replaceImage(File? image) { - emit( - CreateRecipeState( - state.entity.copyWith( - image: image, - ), - ), - ); + emit(state.copyWith.entity(image: image)); } replaceDescriptionInStep(String description, int index) { List steps = [...state.entity.steps]; + steps[index] = steps[index].copyWith(description: description); - emit( - CreateRecipeState( - state.entity.copyWith( - steps: steps, - ), - ), - ); + + emit(state.copyWith.entity(steps: steps)); } replaceImageInStep(File? image, int index) { List steps = [...state.entity.steps]; + steps[index] = steps[index].copyWith(image: image); - emit( - CreateRecipeState( - state.entity.copyWith( - steps: steps, - ), - ), - ); + + emit(state.copyWith.entity(steps: steps)); } - Future publishRecipe() async { + Future publishRecipe() async { try { - await recipeRepository.publishRecipe(state.entity.toModel()); - } catch (e) { - rethrow; + final model = state.entity.toModel(); + await recipeRepository.publishRecipe(model); + return true; + } catch (e, st) { + log(e.toString(), stackTrace: st); + return false; + } + } + + bool validateAll() { + final isTitleValid = validateTitle(); + final isDescValid = validateDescription(); + final isImageValid = validateImage(); + final areServingsValid = validateServings(); + final isCookTimeValid = validateTotalCookTime(); + final isActiveCookTimeValid = validateActiveCookTime(); + final isEnergyPerServingsValid = validateEnergyPerServing(); + + return isTitleValid && isDescValid && isImageValid && areServingsValid && isCookTimeValid && isActiveCookTimeValid && isEnergyPerServingsValid; + } + + bool validateTitle() { + if (state.entity.title == null || (state.entity.title?.isEmpty ?? true)) { + emit(state.copyWith(titleValidationError: 'Поле не заполнено')); + return false; + } else if (state.entity.title!.length < 3) { + emit(state.copyWith(titleValidationError: 'Минимальная длина 3 символа ')); + return false; + } else { + emit(state.copyWith(titleValidationError: null)); + return true; + } + } + + bool validateServings() { + if (state.entity.countOfPortions == null) { + emit(state.copyWith(servingsValidationError: 'Поле не заполнено')); + return false; + } else { + emit(state.copyWith(servingsValidationError: null)); + return true; + } + } + + bool validateEnergyPerServing() { + if (state.entity.energyInOnePortion == null) { + emit(state.copyWith(energyPerServingValidationError: 'Поле не заполнено')); + return false; + } else { + emit(state.copyWith(energyPerServingValidationError: null)); + return true; + } + } + + bool validateActiveCookTime() { + if (state.entity.activeCookTime == null || state.entity.activeCookTime == '00:00') { + emit(state.copyWith(activeTimeValidationError: 'Поле не заполнено')); + return false; + } else { + emit(state.copyWith(activeTimeValidationError: null)); + return true; + } + } + + bool validateTotalCookTime() { + if (state.entity.totalCookTime == null || state.entity.totalCookTime == '00:00') { + emit(state.copyWith(cookingTimeValidationError: 'Поле не заполнено')); + return false; + } else { + emit(state.copyWith(cookingTimeValidationError: null)); + return true; + } + } + + bool validateDescription() { + if (state.entity.description == null || (state.entity.description?.isEmpty ?? true)) { + emit(state.copyWith(descriptionValidationError: 'Поле не заполнено')); + return false; + } else if (state.entity.description!.length < 3) { + emit(state.copyWith(descriptionValidationError: 'Минимальная длина 3 символа ')); + return false; + } else { + emit(state.copyWith(descriptionValidationError: null)); + return true; + } + } + + bool validateImage() { + if (state.entity.image == null) { + emit(state.copyWith(imageValidationError: 'Поле не заполнено')); + return false; + } else { + emit(state.copyWith(imageValidationError: null)); + return true; } } } diff --git a/lib/features/recipes/presentation/bloc/create_recipe_cubit/create_recipe_state.dart b/lib/features/recipes/presentation/bloc/create_recipe_cubit/create_recipe_state.dart index 19adabf8..fd95a839 100644 --- a/lib/features/recipes/presentation/bloc/create_recipe_cubit/create_recipe_state.dart +++ b/lib/features/recipes/presentation/bloc/create_recipe_cubit/create_recipe_state.dart @@ -4,8 +4,16 @@ import 'package:freezed_annotation/freezed_annotation.dart'; part 'create_recipe_state.freezed.dart'; @freezed -class CreateRecipeState with _$CreateRecipeState { - const factory CreateRecipeState( - RecipeCreateEntity entity, - ) = _CreateRecipeState; +class CreateRecipeState with _$CreateRecipeState { + const factory CreateRecipeState({ + required RecipeCreateEntity entity, + String? titleValidationError, + String? descriptionValidationError, + String? cookingTimeValidationError, + String? activeTimeValidationError, + String? servingsValidationError, + String? energyPerServingValidationError, + String? imageValidationError, + }) = _CreateRecipeState; + } diff --git a/lib/features/recipes/presentation/bloc/create_recipe_cubit/create_recipe_state.freezed.dart b/lib/features/recipes/presentation/bloc/create_recipe_cubit/create_recipe_state.freezed.dart index b1661b76..302f048c 100644 --- a/lib/features/recipes/presentation/bloc/create_recipe_cubit/create_recipe_state.freezed.dart +++ b/lib/features/recipes/presentation/bloc/create_recipe_cubit/create_recipe_state.freezed.dart @@ -17,6 +17,14 @@ final _privateConstructorUsedError = UnsupportedError( /// @nodoc mixin _$CreateRecipeState { RecipeCreateEntity get entity => throw _privateConstructorUsedError; + String? get titleValidationError => throw _privateConstructorUsedError; + String? get descriptionValidationError => throw _privateConstructorUsedError; + String? get cookingTimeValidationError => throw _privateConstructorUsedError; + String? get activeTimeValidationError => throw _privateConstructorUsedError; + String? get servingsValidationError => throw _privateConstructorUsedError; + String? get energyPerServingValidationError => + throw _privateConstructorUsedError; + String? get imageValidationError => throw _privateConstructorUsedError; /// Create a copy of CreateRecipeState /// with the given fields replaced by the non-null parameter values. @@ -31,7 +39,15 @@ abstract class $CreateRecipeStateCopyWith<$Res> { CreateRecipeState value, $Res Function(CreateRecipeState) then) = _$CreateRecipeStateCopyWithImpl<$Res, CreateRecipeState>; @useResult - $Res call({RecipeCreateEntity entity}); + $Res call( + {RecipeCreateEntity entity, + String? titleValidationError, + String? descriptionValidationError, + String? cookingTimeValidationError, + String? activeTimeValidationError, + String? servingsValidationError, + String? energyPerServingValidationError, + String? imageValidationError}); $RecipeCreateEntityCopyWith<$Res> get entity; } @@ -52,12 +68,48 @@ class _$CreateRecipeStateCopyWithImpl<$Res, $Val extends CreateRecipeState> @override $Res call({ Object? entity = null, + Object? titleValidationError = freezed, + Object? descriptionValidationError = freezed, + Object? cookingTimeValidationError = freezed, + Object? activeTimeValidationError = freezed, + Object? servingsValidationError = freezed, + Object? energyPerServingValidationError = freezed, + Object? imageValidationError = freezed, }) { return _then(_value.copyWith( entity: null == entity ? _value.entity : entity // ignore: cast_nullable_to_non_nullable as RecipeCreateEntity, + titleValidationError: freezed == titleValidationError + ? _value.titleValidationError + : titleValidationError // ignore: cast_nullable_to_non_nullable + as String?, + descriptionValidationError: freezed == descriptionValidationError + ? _value.descriptionValidationError + : descriptionValidationError // ignore: cast_nullable_to_non_nullable + as String?, + cookingTimeValidationError: freezed == cookingTimeValidationError + ? _value.cookingTimeValidationError + : cookingTimeValidationError // ignore: cast_nullable_to_non_nullable + as String?, + activeTimeValidationError: freezed == activeTimeValidationError + ? _value.activeTimeValidationError + : activeTimeValidationError // ignore: cast_nullable_to_non_nullable + as String?, + servingsValidationError: freezed == servingsValidationError + ? _value.servingsValidationError + : servingsValidationError // ignore: cast_nullable_to_non_nullable + as String?, + energyPerServingValidationError: freezed == + energyPerServingValidationError + ? _value.energyPerServingValidationError + : energyPerServingValidationError // ignore: cast_nullable_to_non_nullable + as String?, + imageValidationError: freezed == imageValidationError + ? _value.imageValidationError + : imageValidationError // ignore: cast_nullable_to_non_nullable + as String?, ) as $Val); } @@ -80,7 +132,15 @@ abstract class _$$CreateRecipeStateImplCopyWith<$Res> __$$CreateRecipeStateImplCopyWithImpl<$Res>; @override @useResult - $Res call({RecipeCreateEntity entity}); + $Res call( + {RecipeCreateEntity entity, + String? titleValidationError, + String? descriptionValidationError, + String? cookingTimeValidationError, + String? activeTimeValidationError, + String? servingsValidationError, + String? energyPerServingValidationError, + String? imageValidationError}); @override $RecipeCreateEntityCopyWith<$Res> get entity; @@ -100,12 +160,48 @@ class __$$CreateRecipeStateImplCopyWithImpl<$Res> @override $Res call({ Object? entity = null, + Object? titleValidationError = freezed, + Object? descriptionValidationError = freezed, + Object? cookingTimeValidationError = freezed, + Object? activeTimeValidationError = freezed, + Object? servingsValidationError = freezed, + Object? energyPerServingValidationError = freezed, + Object? imageValidationError = freezed, }) { return _then(_$CreateRecipeStateImpl( - null == entity + entity: null == entity ? _value.entity : entity // ignore: cast_nullable_to_non_nullable as RecipeCreateEntity, + titleValidationError: freezed == titleValidationError + ? _value.titleValidationError + : titleValidationError // ignore: cast_nullable_to_non_nullable + as String?, + descriptionValidationError: freezed == descriptionValidationError + ? _value.descriptionValidationError + : descriptionValidationError // ignore: cast_nullable_to_non_nullable + as String?, + cookingTimeValidationError: freezed == cookingTimeValidationError + ? _value.cookingTimeValidationError + : cookingTimeValidationError // ignore: cast_nullable_to_non_nullable + as String?, + activeTimeValidationError: freezed == activeTimeValidationError + ? _value.activeTimeValidationError + : activeTimeValidationError // ignore: cast_nullable_to_non_nullable + as String?, + servingsValidationError: freezed == servingsValidationError + ? _value.servingsValidationError + : servingsValidationError // ignore: cast_nullable_to_non_nullable + as String?, + energyPerServingValidationError: freezed == + energyPerServingValidationError + ? _value.energyPerServingValidationError + : energyPerServingValidationError // ignore: cast_nullable_to_non_nullable + as String?, + imageValidationError: freezed == imageValidationError + ? _value.imageValidationError + : imageValidationError // ignore: cast_nullable_to_non_nullable + as String?, )); } } @@ -113,14 +209,36 @@ class __$$CreateRecipeStateImplCopyWithImpl<$Res> /// @nodoc class _$CreateRecipeStateImpl implements _CreateRecipeState { - const _$CreateRecipeStateImpl(this.entity); + const _$CreateRecipeStateImpl( + {required this.entity, + this.titleValidationError, + this.descriptionValidationError, + this.cookingTimeValidationError, + this.activeTimeValidationError, + this.servingsValidationError, + this.energyPerServingValidationError, + this.imageValidationError}); @override final RecipeCreateEntity entity; + @override + final String? titleValidationError; + @override + final String? descriptionValidationError; + @override + final String? cookingTimeValidationError; + @override + final String? activeTimeValidationError; + @override + final String? servingsValidationError; + @override + final String? energyPerServingValidationError; + @override + final String? imageValidationError; @override String toString() { - return 'CreateRecipeState(entity: $entity)'; + return 'CreateRecipeState(entity: $entity, titleValidationError: $titleValidationError, descriptionValidationError: $descriptionValidationError, cookingTimeValidationError: $cookingTimeValidationError, activeTimeValidationError: $activeTimeValidationError, servingsValidationError: $servingsValidationError, energyPerServingValidationError: $energyPerServingValidationError, imageValidationError: $imageValidationError)'; } @override @@ -128,11 +246,42 @@ class _$CreateRecipeStateImpl implements _CreateRecipeState { return identical(this, other) || (other.runtimeType == runtimeType && other is _$CreateRecipeStateImpl && - (identical(other.entity, entity) || other.entity == entity)); + (identical(other.entity, entity) || other.entity == entity) && + (identical(other.titleValidationError, titleValidationError) || + other.titleValidationError == titleValidationError) && + (identical(other.descriptionValidationError, + descriptionValidationError) || + other.descriptionValidationError == + descriptionValidationError) && + (identical(other.cookingTimeValidationError, + cookingTimeValidationError) || + other.cookingTimeValidationError == + cookingTimeValidationError) && + (identical(other.activeTimeValidationError, + activeTimeValidationError) || + other.activeTimeValidationError == activeTimeValidationError) && + (identical( + other.servingsValidationError, servingsValidationError) || + other.servingsValidationError == servingsValidationError) && + (identical(other.energyPerServingValidationError, + energyPerServingValidationError) || + other.energyPerServingValidationError == + energyPerServingValidationError) && + (identical(other.imageValidationError, imageValidationError) || + other.imageValidationError == imageValidationError)); } @override - int get hashCode => Object.hash(runtimeType, entity); + int get hashCode => Object.hash( + runtimeType, + entity, + titleValidationError, + descriptionValidationError, + cookingTimeValidationError, + activeTimeValidationError, + servingsValidationError, + energyPerServingValidationError, + imageValidationError); /// Create a copy of CreateRecipeState /// with the given fields replaced by the non-null parameter values. @@ -145,11 +294,32 @@ class _$CreateRecipeStateImpl implements _CreateRecipeState { } abstract class _CreateRecipeState implements CreateRecipeState { - const factory _CreateRecipeState(final RecipeCreateEntity entity) = - _$CreateRecipeStateImpl; + const factory _CreateRecipeState( + {required final RecipeCreateEntity entity, + final String? titleValidationError, + final String? descriptionValidationError, + final String? cookingTimeValidationError, + final String? activeTimeValidationError, + final String? servingsValidationError, + final String? energyPerServingValidationError, + final String? imageValidationError}) = _$CreateRecipeStateImpl; @override RecipeCreateEntity get entity; + @override + String? get titleValidationError; + @override + String? get descriptionValidationError; + @override + String? get cookingTimeValidationError; + @override + String? get activeTimeValidationError; + @override + String? get servingsValidationError; + @override + String? get energyPerServingValidationError; + @override + String? get imageValidationError; /// Create a copy of CreateRecipeState /// with the given fields replaced by the non-null parameter values. diff --git a/lib/features/recipes/presentation/pages/about_recipes_screen.dart b/lib/features/recipes/presentation/pages/about_recipes_screen.dart index e5749b7d..3bb3d35f 100644 --- a/lib/features/recipes/presentation/pages/about_recipes_screen.dart +++ b/lib/features/recipes/presentation/pages/about_recipes_screen.dart @@ -212,7 +212,7 @@ class _AboutRecipesScreenState extends State { Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Text( - widget.recipes.title, + widget.recipes.title ?? '', style: TextStyle( color: Colors.black, fontSize: 24, @@ -241,7 +241,7 @@ class _AboutRecipesScreenState extends State { Icon(Icons.fast_forward_outlined), SizedBox(width: 4), Text( - widget.recipes.steps.length.toString() + ' этапов', + '${widget.recipes.steps?.length ?? 0}' + ' этапов', style: TextStyle( color: Colors.black, fontSize: 12, @@ -286,9 +286,14 @@ class _AboutRecipesScreenState extends State { padding: const EdgeInsets.symmetric(horizontal: 16), shrinkWrap: true, physics: NeverScrollableScrollPhysics(), - itemBuilder: (context, index) => _ingredient(widget.recipes.ingredients[index]), + itemBuilder: (context, index) { + if (widget.recipes.ingredients != null) { + return _ingredient(widget.recipes.ingredients![index]); + } + return SizedBox.shrink(); + }, separatorBuilder: (context, index) => SizedBox(height: 4), - itemCount: widget.recipes.ingredients.length, + itemCount: widget.recipes.ingredients?.length ?? 0, ), SizedBox(height: 12), Padding( @@ -307,7 +312,10 @@ class _AboutRecipesScreenState extends State { ListView.separated( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), - itemBuilder: (context, index) => _step(widget.recipes.steps[index], index + 1), + itemBuilder: (context, index) { + if (widget.recipes.steps == null) return SizedBox.shrink(); + return _step(widget.recipes.steps![index], index + 1); + }, separatorBuilder: (context, index) => Padding( padding: const EdgeInsets.all(16), child: Container( @@ -318,7 +326,7 @@ class _AboutRecipesScreenState extends State { ), ), ), - itemCount: widget.recipes.steps.length, + itemCount: widget.recipes.steps?.length ?? 0, ), SizedBox(height: 8), Padding( diff --git a/lib/features/recipes/presentation/pages/recipe_create_screen.dart b/lib/features/recipes/presentation/pages/recipe_create_screen.dart index 78da5627..f383da68 100644 --- a/lib/features/recipes/presentation/pages/recipe_create_screen.dart +++ b/lib/features/recipes/presentation/pages/recipe_create_screen.dart @@ -1,6 +1,8 @@ import 'package:eatvio_app/core/locator_service.dart'; import 'package:eatvio_app/core/theme/theme_helper.dart'; +import 'package:eatvio_app/core/utils/toasts.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:eatvio_app/features/recipes/presentation/widgets/info_about_recipe_widget.dart'; import 'package:eatvio_app/features/recipes/presentation/widgets/ingredients_widget.dart'; import 'package:eatvio_app/features/recipes/presentation/widgets/steps_widget.dart'; @@ -42,32 +44,44 @@ class _RecipeCreateScreenState extends State { InfoAboutRecipeWidget(), IngredientsWidget(), StepsWidget(), - Builder( - builder: (context) { - return InkWell( - onTap: () { - context.read().publishRecipe(); - }, - borderRadius: BorderRadius.circular(20), - child: Ink( - width: MediaQuery.of(context).size.width - 32, - decoration: BoxDecoration( - color: appTheme.blueA400, + BlocBuilder( + builder: (context, state) { + return Builder( + builder: (context) { + return InkWell( + onTap: () async { + if (context.read().validateAll()) { + final isSuccess = await context.read().publishRecipe(); + if (isSuccess) { + showSuccessToast('Рецепт успешно опубликован'); + Navigator.pop(context); + } else { + showErrorToast('Не удалось опубликовать рецепт'); + } + } + }, 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/image_widget.dart b/lib/features/recipes/presentation/widgets/image_widget.dart index 2521ff69..251e3345 100644 --- a/lib/features/recipes/presentation/widgets/image_widget.dart +++ b/lib/features/recipes/presentation/widgets/image_widget.dart @@ -8,14 +8,13 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:image_picker/image_picker.dart'; class ImageWidget extends StatefulWidget { - const ImageWidget({super.key}); - + const ImageWidget({super.key, required this.image}); + final File? image; @override State createState() => _ImageWidgetState(); } class _ImageWidgetState extends State { - XFile? image; late final CreateRecipeCubit createRecipeCubit; @override @@ -30,7 +29,7 @@ class _ImageWidgetState extends State { onTap: () { _showImagePicker(); }, - child: image == null + child: widget.image == null ? SvgPicture.asset( width: MediaQuery.of(context).size.width - 32, 'assets/icons/upload_photo_of_portion.svg', @@ -40,7 +39,7 @@ class _ImageWidgetState extends State { ClipRRect( borderRadius: BorderRadius.circular(20), child: Image.file( - File(image!.path), + File(widget.image!.path), ), ), Positioned( @@ -48,9 +47,6 @@ class _ImageWidgetState extends State { top: 16, child: GestureDetector( onTap: () { - setState(() { - image = null; - }); createRecipeCubit.replaceImage(null); }, child: Container( @@ -76,10 +72,11 @@ class _ImageWidgetState extends State { CupertinoActionSheetAction( onPressed: () async { final picker = ImagePicker(); - image = await picker.pickImage(source: ImageSource.camera); + final image = await picker.pickImage(source: ImageSource.camera); setState(() {}); if (image != null) { - createRecipeCubit.replaceImage(File(image!.path)); + createRecipeCubit.replaceImage(File(image.path)); + createRecipeCubit.validateImage(); } Navigator.of(context).pop(); }, @@ -94,10 +91,11 @@ class _ImageWidgetState extends State { CupertinoActionSheetAction( onPressed: () async { final picker = ImagePicker(); - image = await picker.pickImage(source: ImageSource.gallery); + final image = await picker.pickImage(source: ImageSource.gallery); setState(() {}); if (image != null) { - createRecipeCubit.replaceImage(File(image!.path)); + createRecipeCubit.replaceImage(File(image.path)); + createRecipeCubit.validateImage(); } Navigator.of(context).pop(); }, 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 44ef152e..0a7cb9fe 100644 --- a/lib/features/recipes/presentation/widgets/info_about_recipe_widget.dart +++ b/lib/features/recipes/presentation/widgets/info_about_recipe_widget.dart @@ -1,8 +1,11 @@ import 'package:eatvio_app/core/data/selectionPopupModel/selection_popup_model.dart'; +import 'package:eatvio_app/core/theme/theme_helper.dart'; import 'package:eatvio_app/core/widgets/custom_drop_down.dart'; import 'package:eatvio_app/core/widgets/custom_text_form_field.dart'; +import 'package:eatvio_app/features/create_diet/presentation/widgets/error_widget.dart'; import 'package:eatvio_app/features/recipes/entities/recipe_create_entity.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:eatvio_app/features/recipes/presentation/widgets/image_widget.dart'; import 'package:eatvio_app/features/recipes/presentation/widgets/portion_widget.dart'; import 'package:eatvio_app/features/recipes/presentation/widgets/tags_widget.dart'; @@ -40,10 +43,29 @@ class _InfoAboutRecipeWidgetState extends State { children: [ Text('Название'), SizedBox(height: 4), - CustomTextFormField( - hintText: 'Например: тар-тар из говядины', - onChanged: (value) { - createRecipeCubit.replaceTitle(value); + BlocBuilder( + buildWhen: (previous, current) => previous.titleValidationError != current.titleValidationError, + builder: (context, state) { + return Column( + children: [ + CustomTextFormField( + hintText: 'Например: тар-тар из говядины', + hasError: state.titleValidationError != null, + onChanged: (value) { + createRecipeCubit.replaceTitle(value); + if (state.titleValidationError != null) { + createRecipeCubit.validateTitle(); + } + }, + ), + if (state.titleValidationError != null) ...[ + SizedBox(height: 4), + CustomErrorWidget( + error: state.titleValidationError!, + ), + ], + ], + ); }, ), const SizedBox(height: 12), @@ -70,18 +92,57 @@ class _InfoAboutRecipeWidgetState extends State { const SizedBox(height: 12), Text('Описание'), SizedBox(height: 4), - CustomTextFormField( - hintText: 'Расскажите, почему вы выбрали именно этот рецепт? Опишите, каким будет готовое блюдо и в чём его достоинства.', - minLines: 5, - maxLines: 5, - onChanged: (value) { - createRecipeCubit.replaceDescription(value); + BlocBuilder( + buildWhen: (previous, current) => previous.descriptionValidationError != current.descriptionValidationError, + builder: (context, state) { + return Column( + children: [ + CustomTextFormField( + hintText: 'Расскажите, почему вы выбрали именно этот рецепт? Опишите, каким будет готовое блюдо и в чём его достоинства.', + minLines: 5, + maxLines: 5, + hasError: state.descriptionValidationError != null, + onChanged: (value) { + createRecipeCubit.replaceDescription(value); + if (state.descriptionValidationError != null) { + createRecipeCubit.validateDescription(); + } + }, + ), + if (state.descriptionValidationError != null) ...[ + SizedBox(height: 4), + CustomErrorWidget( + error: state.descriptionValidationError!, + ), + ], + ], + ); }, ), const SizedBox(height: 12), Text('Фото обложка'), SizedBox(height: 4), - ImageWidget(), + BlocBuilder( + buildWhen: (previous, current) => previous.imageValidationError != current.imageValidationError || previous.entity.image != current.entity.image, + builder: (context, state) { + return Column( + children: [ + Container( + child: ImageWidget(image: state.entity.image), + decoration: state.imageValidationError != null + ? BoxDecoration(border: Border.all(width: 1, color: appTheme.red), borderRadius: BorderRadius.circular(8)) + : null, + ), + if (state.imageValidationError != null) ...[ + SizedBox(height: 4), + CustomErrorWidget( + error: state.imageValidationError!, + ), + ], + ], + ); + }, + ), const SizedBox(height: 12), Text('Ссылка на видео (если есть)'), SizedBox(height: 4), diff --git a/lib/features/recipes/presentation/widgets/ingredients_widget.dart b/lib/features/recipes/presentation/widgets/ingredients_widget.dart index 5f66ed87..3983bccf 100644 --- a/lib/features/recipes/presentation/widgets/ingredients_widget.dart +++ b/lib/features/recipes/presentation/widgets/ingredients_widget.dart @@ -69,7 +69,7 @@ class _IngredientsWidgetState extends State { color: Colors.grey.shade600, ), title: Text(state.entity.ingredients[index].name), - subtitle: Text('${state.entity.ingredients[index].unit} ${state.entity.ingredients[index].portion == 'gr' ? 'гр' : 'шт'}'), + subtitle: Text('${state.entity.ingredients[index].portion} ${state.entity.ingredients[index].unit == 'gr' ? 'гр' : 'шт'}'), trailing: IconButton( onPressed: () { createRecipeCubit.removeIngredient(index); @@ -163,8 +163,8 @@ class _IngredientsWidgetState extends State { createRecipeCubit.addIngredient( Ingredient( name: nameController.text, - unit: gramsController.text.isEmpty ? pcsController.text : gramsController.text, - portion: gramsController.text.isEmpty ? 'pcs' : 'gr', + unit: gramsController.text.isEmpty ? 'pcs' : 'gr', + portion: gramsController.text.isEmpty ? pcsController.text : gramsController.text, ), ); Navigator.of(context).pop(); diff --git a/lib/features/recipes/presentation/widgets/portion_widget.dart b/lib/features/recipes/presentation/widgets/portion_widget.dart index 875183ff..569f2c7c 100644 --- a/lib/features/recipes/presentation/widgets/portion_widget.dart +++ b/lib/features/recipes/presentation/widgets/portion_widget.dart @@ -1,5 +1,7 @@ import 'package:eatvio_app/core/widgets/custom_text_form_field.dart'; +import 'package:eatvio_app/features/create_diet/presentation/widgets/error_widget.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'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -12,7 +14,6 @@ class PortionWidget extends StatefulWidget { class _PortionWidgetState extends State { late final CreateRecipeCubit createRecipeCubit; - @override void initState() { super.initState(); @@ -21,49 +22,86 @@ class _PortionWidgetState extends State { @override Widget build(BuildContext context) { - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - Row( + return BlocBuilder( + buildWhen: (previous, current) => + previous.servingsValidationError != current.servingsValidationError || + previous.energyPerServingValidationError != current.energyPerServingValidationError, + builder: (context, state) { + return Column( mainAxisSize: MainAxisSize.min, children: [ - Expanded( - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text('Кол-во порций'), - SizedBox(height: 4), - CustomTextFormField( - hintText: '1', - onChanged: (value) { - createRecipeCubit.replacePortionsCount(int.parse(value)); - }, + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Expanded( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Кол-во порций'), + SizedBox(height: 4), + Column( + children: [ + CustomTextFormField( + hintText: '1', + hasError: state.servingsValidationError != null, + textInputType: TextInputType.number, + onChanged: (value) { + createRecipeCubit.replacePortionsCount(int.parse(value)); + if (state.servingsValidationError != null) { + createRecipeCubit.validateServings(); + } + }, + ), + if (state.servingsValidationError != null) ...[ + SizedBox(height: 4), + CustomErrorWidget( + error: state.servingsValidationError!, + ), + ], + ], + ), + ], ), - ], - ), - ), - const SizedBox(width: 8), - Expanded( - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text('Ккал в одной порции'), - SizedBox(height: 4), - CustomTextFormField( - hintText: '100', - suffixText: 'Ккал', - onChanged: (value) { - createRecipeCubit.replaceEnegryInPortion(double.parse(value)); - }, + ), + const SizedBox(width: 8), + Expanded( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Ккал в одной порции'), + SizedBox(height: 4), + Column( + children: [ + CustomTextFormField( + hintText: '100', + hasError: state.energyPerServingValidationError != null, + suffixText: 'Ккал', + textInputType: TextInputType.numberWithOptions(decimal: true), + onChanged: (value) { + createRecipeCubit.replaceEnegryInPortion(double.parse(value)); + if (state.energyPerServingValidationError != null) { + createRecipeCubit.validateEnergyPerServing(); + } + }, + ), + if (state.energyPerServingValidationError != null) ...[ + SizedBox(height: 4), + CustomErrorWidget( + error: state.energyPerServingValidationError!, + ), + ], + ], + ), + ], ), - ], - ), + ), + ], ), ], - ), - ], + ); + }, ); } } diff --git a/lib/features/recipes/presentation/widgets/time_of_cook_widget.dart b/lib/features/recipes/presentation/widgets/time_of_cook_widget.dart index f329f791..1c4e0306 100644 --- a/lib/features/recipes/presentation/widgets/time_of_cook_widget.dart +++ b/lib/features/recipes/presentation/widgets/time_of_cook_widget.dart @@ -1,6 +1,8 @@ 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/create_diet/presentation/widgets/error_widget.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'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -22,130 +24,198 @@ class _TimeOfCookWidgetState extends State { @override Widget build(BuildContext context) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Row( - children: [ - Text('Общее время приготовления'), - const SizedBox(width: 8), - GestureDetector( - onTap: () { - showDialog( - context: context, - builder: (context) => AlertDialog( - content: Text( - 'Общее время приготовления - это период времени от начала приготовления блюда до подачи его на стол.', - style: TextStyle( - fontWeight: FontWeight.w500, - color: Color(0xFF323F4B), - height: 1.2, - ), - ), - ), - ); - }, - child: Icon( - Icons.help_outline_rounded, - color: appTheme.blueA400, - size: 20, - ), - ), - ], - ), - SizedBox(height: 4), - Row( + return BlocBuilder( + buildWhen: (previous, current) => + previous.activeTimeValidationError != current.activeTimeValidationError || previous.cookingTimeValidationError != current.cookingTimeValidationError, + builder: (context, state) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ - Expanded( - child: CustomTextFormField( - hintText: 'Часов', - onChanged: (value) { - if (createRecipeCubit.state.entity.totalCookTime == null) { - createRecipeCubit.replaceTotalTime('${value.padLeft(2, '0')}:00'); - } else { - createRecipeCubit.replaceTotalTime('${value.padLeft(2, '0')}:${createRecipeCubit.state.entity.totalCookTime!.substring(3, 5)}'); - } - }, - ), - ), - const SizedBox(width: 8), - Expanded( - child: CustomTextFormField( - hintText: 'Минут', - onChanged: (value) { - if (createRecipeCubit.state.entity.totalCookTime == null) { - createRecipeCubit.replaceTotalTime('00:${value.padLeft(2, '0')}'); - } else { - createRecipeCubit.replaceTotalTime('${createRecipeCubit.state.entity.totalCookTime!.substring(0, 2)}:${value.padLeft(2, '0')}'); - } - }, - ), - ), - ], - ), - const SizedBox(height: 12), - Row( - children: [ - Text('Активное время приготовления'), - const SizedBox(width: 8), - GestureDetector( - onTap: () { - showDialog( - context: context, - builder: (context) => AlertDialog( - content: Text( - 'Активное время приготовления - это период времени, требующий активного участия в приготовлении блюда. Оно включает мытье и очистку продуктов, нарезку, шинковку, взбивание, перемешивание, размешивание, перекладывание и т.п.', - style: TextStyle( - fontWeight: FontWeight.w500, - color: Color(0xFF323F4B), - height: 1.2, + Row( + children: [ + Text('Общее время приготовления'), + const SizedBox(width: 8), + GestureDetector( + onTap: () { + showDialog( + context: context, + builder: (context) => AlertDialog( + content: Text( + 'Общее время приготовления - это период времени от начала приготовления блюда до подачи его на стол.', + style: TextStyle( + fontWeight: FontWeight.w500, + color: Color(0xFF323F4B), + height: 1.2, + ), + ), ), - ), + ); + }, + child: Icon( + Icons.help_outline_rounded, + color: appTheme.blueA400, + size: 20, ), - ); - }, - child: Icon( - Icons.help_outline_rounded, - color: appTheme.blueA400, - size: 20, - ), + ), + ], + ), + SizedBox(height: 4), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Expanded( + child: Column( + children: [ + CustomTextFormField( + hintText: 'Часов', + hasError: state.cookingTimeValidationError != null, + textInputType: TextInputType.number, + onChanged: (value) { + if (createRecipeCubit.state.entity.totalCookTime == null) { + createRecipeCubit.replaceTotalTime('${value.padLeft(2, '0')}:00'); + } else { + createRecipeCubit.replaceTotalTime('${value.padLeft(2, '0')}:${createRecipeCubit.state.entity.totalCookTime!.substring(3, 5)}'); + } + if (state.cookingTimeValidationError != null) { + createRecipeCubit.validateTotalCookTime(); + } + }, + ), + if (state.cookingTimeValidationError != null) ...[ + SizedBox(height: 4), + CustomErrorWidget( + error: state.cookingTimeValidationError!, + ), + ], + ], + ), + ), + const SizedBox(width: 8), + Expanded( + child: Column( + children: [ + CustomTextFormField( + hintText: 'Минут', + hasError: state.cookingTimeValidationError != null, + textInputType: TextInputType.number, + onChanged: (value) { + if (createRecipeCubit.state.entity.totalCookTime == null) { + createRecipeCubit.replaceTotalTime('00:${value.padLeft(2, '0')}'); + } else { + createRecipeCubit.replaceTotalTime('${createRecipeCubit.state.entity.totalCookTime!.substring(0, 2)}:${value.padLeft(2, '0')}'); + } + if (state.cookingTimeValidationError != null) { + createRecipeCubit.validateTotalCookTime(); + } + }, + ), + if (state.cookingTimeValidationError != null) ...[ + SizedBox(height: 4), + CustomErrorWidget( + error: state.cookingTimeValidationError!, + ), + ], + ], + ), + ), + ], + ), + const SizedBox(height: 12), + Row( + children: [ + Text('Активное время приготовления'), + const SizedBox(width: 8), + GestureDetector( + onTap: () { + showDialog( + context: context, + builder: (context) => AlertDialog( + content: Text( + 'Активное время приготовления - это период времени, требующий активного участия в приготовлении блюда. Оно включает мытье и очистку продуктов, нарезку, шинковку, взбивание, перемешивание, размешивание, перекладывание и т.п.', + style: TextStyle( + fontWeight: FontWeight.w500, + color: Color(0xFF323F4B), + height: 1.2, + ), + ), + ), + ); + }, + child: Icon( + Icons.help_outline_rounded, + color: appTheme.blueA400, + size: 20, + ), + ), + ], + ), + SizedBox(height: 4), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Expanded( + child: Column( + children: [ + CustomTextFormField( + hintText: 'Часов', + hasError: state.activeTimeValidationError != null, + textInputType: TextInputType.number, + onChanged: (value) { + if (createRecipeCubit.state.entity.activeCookTime == null) { + createRecipeCubit.replaceActiveTime('${value.padLeft(2, '0')}:00'); + } else { + createRecipeCubit.replaceActiveTime('${value.padLeft(2, '0')}:${createRecipeCubit.state.entity.activeCookTime!.substring(3, 5)}'); + } + + if (state.activeTimeValidationError != null) { + createRecipeCubit.validateActiveCookTime(); + } + }, + ), + if (state.activeTimeValidationError != null) ...[ + SizedBox(height: 4), + CustomErrorWidget( + error: state.activeTimeValidationError!, + ), + ], + ], + ), + ), + const SizedBox(width: 8), + Expanded( + child: Column( + children: [ + CustomTextFormField( + hintText: 'Минут', + hasError: state.activeTimeValidationError != null, + textInputType: TextInputType.number, + onChanged: (value) { + if (createRecipeCubit.state.entity.activeCookTime == null) { + createRecipeCubit.replaceActiveTime('00:${value.padLeft(2, '0')}'); + } else { + createRecipeCubit.replaceActiveTime('${createRecipeCubit.state.entity.activeCookTime!.substring(0, 2)}:${value.padLeft(2, '0')}'); + } + + if (state.activeTimeValidationError != null) { + createRecipeCubit.validateActiveCookTime(); + } + }, + ), + if (state.activeTimeValidationError != null) ...[ + SizedBox(height: 4), + CustomErrorWidget( + error: state.activeTimeValidationError!, + ), + ], + ], + ), + ), + ], ), ], - ), - SizedBox(height: 4), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - Expanded( - child: CustomTextFormField( - hintText: 'Часов', - onChanged: (value) { - if (createRecipeCubit.state.entity.activeCookTime == null) { - createRecipeCubit.replaceActiveTime('${value.padLeft(2, '0')}:00'); - } else { - createRecipeCubit.replaceActiveTime('${value.padLeft(2, '0')}:${createRecipeCubit.state.entity.activeCookTime!.substring(3, 5)}'); - } - }, - ), - ), - const SizedBox(width: 8), - Expanded( - child: CustomTextFormField( - hintText: 'Минут', - onChanged: (value) { - if (createRecipeCubit.state.entity.activeCookTime == null) { - createRecipeCubit.replaceActiveTime('00:${value.padLeft(2, '0')}'); - } else { - createRecipeCubit.replaceActiveTime('${createRecipeCubit.state.entity.activeCookTime!.substring(0, 2)}:${value.padLeft(2, '0')}'); - } - }, - ), - ), - ], - ), - ], + ); + }, ); } } diff --git a/lib/features/recipes/reporitories/recipe_repository.dart b/lib/features/recipes/reporitories/recipe_repository.dart index b9c49aef..4dec15c0 100644 --- a/lib/features/recipes/reporitories/recipe_repository.dart +++ b/lib/features/recipes/reporitories/recipe_repository.dart @@ -1,8 +1,11 @@ import 'dart:convert'; import 'dart:io'; +import 'package:dio/dio.dart'; +import 'package:eatvio_app/core/image_compressing/image_compress_utility.dart'; import 'package:eatvio_app/features/recipes/models/recipe_model.dart'; import 'package:eatvio_app/features/recipes/models/recipe_tag_model.dart'; +import 'package:eatvio_app/features/recipes/models/step.dart'; import 'package:eatvio_app/features/recipes/services/recipe_service.dart'; import 'package:eatvio_app/features/recipes/models/product.dart'; @@ -19,58 +22,56 @@ class RecipeRepository { } } - Future publishRecipe(RecipeModel recipe) async { + Future publishRecipe(RecipeModel recipe) async { try { if (recipe.image == null || (recipe.image?.isEmpty ?? true)) { - return null; + return; } - final file = File(recipe.image ?? ''); + final image = await ImageCompressUtility.compressFile(File(recipe.image ?? '')); + final multiPartFile = await MultipartFile.fromFile(image.path); final ingredientsMap = recipe.ingredients?.map((ingredient) { - return { - 'name': ingredient!.name, - 'unit': ingredient.unit, - 'portion': ingredient.portion, - }; + if (ingredient != null) { + return ingredient.toJson(); + } }).toList() ?? []; - final stepsMap = recipe.steps?.map((step) { - return { - 'description': step?.description ?? "", - }; - }).toList() ?? - []; - - // 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!, - ); + final stepsMap = []; + for (final Step? step in recipe.steps ?? []) { + if (step != null) { + if ((step.image == null || (step.image?.isEmpty ?? true)) && step.description != null) { + stepsMap.add({ + 'description': step.description ?? "", + }); + } else if (step.image != null && (step.image?.isNotEmpty ?? false)) { + final image = await ImageCompressUtility.compressFile(File(step.image ?? '')); + final multipartFile = await MultipartFile.fromFile(image.path); + stepsMap.add({ + 'description': step.description ?? "", + 'image': multipartFile, + }); + } + } } - return null; + final formMap = { + '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': multiPartFile, + 'servings': recipe.servings, + 'energy_per_serving': recipe.energyPerServing, + }; + formMap.removeWhere((k, v) => v == null || (v is String && v.isEmpty)); + final formData = FormData.fromMap(formMap); + await recipeService.publishRecipe(formData: formData); } catch (e) { rethrow; } diff --git a/lib/features/recipes/services/recipe_service.dart b/lib/features/recipes/services/recipe_service.dart index 2e70e730..12e9c78b 100644 --- a/lib/features/recipes/services/recipe_service.dart +++ b/lib/features/recipes/services/recipe_service.dart @@ -1,6 +1,3 @@ -import 'dart:convert'; -import 'dart:io'; - import 'package:dio/dio.dart'; import 'package:eatvio_app/features/recipes/models/recipe_model.dart'; import 'package:eatvio_app/features/recipes/models/recipe_tag_model.dart'; @@ -19,24 +16,10 @@ abstract class RecipeService { @GET('/api/diary/product_nutritional_values/filter') Future>> getInputProduct(@Query('name') String name); - @POST('/api/recipes/create') @MultiPart() - Future publishRecipe({ - @Part() required String type, - @Part() required String state, - @Part() String? title, - @Part() required String description, - @Part() String? video_url, - @Part() List>? ingredients, - @Part() List>? steps, - @Part() List? tags, - @Part() int? cooking_time, - @Part() int? active_time, - @Part() required int servings, - @Part() required int energy_per_serving, - @Part() File? image, - + Future publishRecipe({ + @Body() required FormData formData, }); @POST('/api/recipes/update/{id}') @@ -44,4 +27,4 @@ abstract class RecipeService { @GET('/api/recipes/tags') Future getTags(); -} +} \ No newline at end of file diff --git a/lib/features/recipes/services/recipe_service.g.dart b/lib/features/recipes/services/recipe_service.g.dart index 396070b1..f9882b29 100644 --- a/lib/features/recipes/services/recipe_service.g.dart +++ b/lib/features/recipes/services/recipe_service.g.dart @@ -92,91 +92,12 @@ class _RecipeService implements RecipeService { } @override - Future publishRecipe({ - required String type, - required String state, - String? title, - required String description, - String? video_url, - List>? ingredients, - List>? steps, - List? tags, - int? cooking_time, - int? active_time, - required int servings, - required int energy_per_serving, - File? image, - }) async { + Future publishRecipe({required FormData formData}) async { final _extra = {}; final queryParameters = {}; - queryParameters.removeWhere((k, v) => v == null); final _headers = {}; - final _data = FormData(); - _data.fields.add(MapEntry( - 'type', - type, - )); - _data.fields.add(MapEntry( - 'state', - state, - )); - if (title != null) { - _data.fields.add(MapEntry( - 'title', - title, - )); - } - _data.fields.add(MapEntry( - 'description', - description, - )); - if (video_url != null) { - _data.fields.add(MapEntry( - 'video_url', - video_url, - )); - } - ingredients?.forEach((i) { - _data.fields.add(MapEntry('ingredients', jsonEncode(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(), - )); - } - 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( + final _data = formData; + final _options = _setStreamType(Options( method: 'POST', headers: _headers, extra: _extra, @@ -193,15 +114,7 @@ class _RecipeService implements RecipeService { _dio.options.baseUrl, baseUrl, ))); - final _result = await _dio.fetch>(_options); - late RecipeModel _value; - try { - _value = RecipeModel.fromJson(_result.data!); - } on Object catch (e, s) { - errorLogger?.logError(e, s, _options); - rethrow; - } - return _value; + await _dio.fetch(_options); } @override diff --git a/lib/main.dart b/lib/main.dart index 62b91d0e..ea6fb543 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -6,6 +6,7 @@ import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/new_ import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/notifications_cubit/notifications_cubit.dart'; import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart'; import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart'; +import 'package:eatvio_app/features/internet_connection_checker/internet_connection_checker_screen.dart'; import 'package:eatvio_app/generated/l10n.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -26,7 +27,7 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MultiBlocProvider( - providers: [ + providers: [ BlocProvider(create: (context) => sl()), BlocProvider(create: (context) => sl()), BlocProvider(create: (context) => sl()..loadFeedBlogPostList()), @@ -37,6 +38,7 @@ class MyApp extends StatelessWidget { child: MaterialApp.router( theme: ThemeHelper().themeData(), routerConfig: AppRouter.appRouter, + builder: (context, child) => InternetConnectionCheckerScreen(child: child!), debugShowCheckedModeBanner: false, localizationsDelegates: const [ S.delegate, diff --git a/pubspec.yaml b/pubspec.yaml index 89ad2dd9..5ea11531 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -58,6 +58,7 @@ dependencies: dotted_border: ^2.1.0 http: ^1.2.2 flutter_native_splash: ^2.4.4 + connectivity_plus: ^6.1.2 dev_dependencies: build_runner: ^2.4.14 From 77e7b6c31f30a7a79be7cce2d6aaed2c4bc6298d Mon Sep 17 00:00:00 2001 From: Username1548 Date: Mon, 3 Feb 2025 15:36:01 +0700 Subject: [PATCH 2/4] fixed errors --- lib/features/feed/presentation/widgets/recipes_widget.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/features/feed/presentation/widgets/recipes_widget.dart b/lib/features/feed/presentation/widgets/recipes_widget.dart index d410793a..16681a77 100644 --- a/lib/features/feed/presentation/widgets/recipes_widget.dart +++ b/lib/features/feed/presentation/widgets/recipes_widget.dart @@ -75,7 +75,7 @@ class _RecipesWidgetState extends State { children: [ _iconButton('assets/icons/clock.svg', widget.recipes.cookingTime.toString() + ' минут'), SizedBox(width: 8), - _iconButton('assets/icons/steps.svg', widget.recipes.steps.length.toString() + ' этапов'), + _iconButton('assets/icons/steps.svg', '${widget.recipes.steps?.length.toString()}' + ' этапов'), SizedBox(width: 8), _iconButton('assets/icons/calories.svg', widget.recipes.calories.toStringAsFixed(0) + ' ккал'), ], @@ -87,7 +87,7 @@ class _RecipesWidgetState extends State { Padding( padding: const EdgeInsets.symmetric(horizontal: 12), child: Text( - widget.recipes.title, + widget.recipes.title ?? '', style: TextStyle( fontWeight: FontWeight.w600, color: Colors.black87, From 5ea3264474dd61b1d1c0dbf146e526744a6e190c Mon Sep 17 00:00:00 2001 From: Username1548 Date: Thu, 6 Feb 2025 15:20:51 +0700 Subject: [PATCH 3/4] fixed auth screen --- .../auth/auth_screen/auth_screen.dart | 53 ++++++++++--------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/lib/features/auth/auth_screen/auth_screen.dart b/lib/features/auth/auth_screen/auth_screen.dart index 31169305..914398f1 100644 --- a/lib/features/auth/auth_screen/auth_screen.dart +++ b/lib/features/auth/auth_screen/auth_screen.dart @@ -9,6 +9,7 @@ import 'package:eatvio_app/core/widgets/custom_text_form_field.dart'; import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart'; import 'package:eatvio_app/generated/l10n.dart'; import 'package:eatvio_app/core/theme/theme_helper.dart'; +import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_native_splash/flutter_native_splash.dart'; @@ -102,12 +103,12 @@ class _AuthScreenState extends State { FlutterNativeSplash.remove(); final s = S.of(context); - return Scaffold( - appBar: AppBar( - toolbarHeight: 0, - ), - body: WebViewWidget(controller: _controller), - ); + // return Scaffold( + // appBar: AppBar( + // toolbarHeight: 0, + // ), + // body: WebViewWidget(controller: _controller), + // ); return Scaffold( appBar: AppBar( @@ -157,7 +158,7 @@ class _AuthScreenState extends State { }, child: SvgPicture.asset( ImageConstant.imgLogoEatvio, - height: 48, + height: 70, fit: BoxFit.contain, ), ), @@ -225,30 +226,30 @@ class _AuthScreenState extends State { ], ), Spacer(), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - 'Нажимая "Войти", Вы соглашаетесь с ', + Center( + child: RichText( + textAlign: TextAlign.center, + text: TextSpan( + text: 'Нажимая "Войти", Вы соглашаетесь \n', style: TextStyle( fontSize: 12, + color: Colors.black, ), - ), - GestureDetector( - onTap: () async { - await launchUrl(Uri.parse(baseUrl + '/oferta_bs.pdf')); - }, - child: Text( - 'Публичной офертой', - style: TextStyle( - fontSize: 12, - color: appTheme.blueA400, - decorationColor: appTheme.blueA400, - decoration: TextDecoration.underline, + children: [ + TextSpan(text: 'c '), + TextSpan( + text: ' Публичной офертой', + style: TextStyle( + fontSize: 12, + color: appTheme.blueA400, + decorationColor: appTheme.blueA400, + decoration: TextDecoration.underline, + ), + recognizer: TapGestureRecognizer()..onTap = () async => await launchUrl(Uri.parse(baseUrl + '/oferta_bs.pdf')), ), - ), + ], ), - ], + ), ), const SizedBox(height: 16), BlocBuilder( From 070d7e1047eb48ae471fde4c20b79c79c3289735 Mon Sep 17 00:00:00 2001 From: Username1548 Date: Mon, 10 Feb 2025 21:50:13 +0700 Subject: [PATCH 4/4] fixed profile, recipeis and diets --- lib/core/app_router.dart | 8 +- lib/core/widgets/bottom_sheet_with_blur.dart | 70 +-- .../services/blog_post_service.g.dart | 101 ++-- .../calendar/services/calendar_service.g.dart | 41 +- .../services/consultant_service.g.dart | 74 ++- .../create_diet/models/create_diet_model.dart | 5 +- .../models/create_diet_model.freezed.dart | 48 +- .../presentation/create_diet_screen.dart | 143 +++--- .../presentation/cubit/create_diet_cubit.dart | 120 +++-- .../presentation/cubit/create_diet_state.dart | 7 +- .../cubit/create_diet_state.freezed.dart | 80 ++- .../presentation/diet_preview_screen.dart | 11 +- .../presentation/widgets/add_meal_widget.dart | 4 +- .../presentation/widgets/day_widget.dart | 7 +- .../widgets/diet_image_picker_widget.dart | 19 +- .../widgets/diet_type_choice_widget.dart | 3 +- .../widgets/status_drop_down_widget.dart | 21 +- .../repositories/create_diet_repository.dart | 80 +-- .../services/create_diet_service.dart | 30 +- .../services/create_diet_service.g.dart | 198 +++----- .../services/create_plan_service.g.dart | 95 ++-- .../diary/services/diary_service.g.dart | 460 +++++++----------- .../presentation/pages/about_diet_screen.dart | 11 +- .../feed/models/diets_pagination_model.dart | 43 +- .../diets_pagination_model.freezed.dart | 83 ++-- .../feed/models/diets_pagination_model.g.dart | 39 +- .../consultants_cubit/consultants_cubit.dart | 5 +- .../bloc/diets_cubit/diets_cubit.dart | 10 +- .../feed/presentation/pages/feed_screen.dart | 20 +- .../feed/presentation/widgets/diets.dart | 2 +- .../success_published_diet_bottom_sheet.dart | 54 ++ .../glossary/services/glossary_service.g.dart | 42 +- .../presentation/cubit/my_diets_cubit.dart | 58 ++- .../presentation/my_diets_screen.dart | 6 +- .../presentation/widgets/my_diet_widget.dart | 98 ++-- .../repositories/my_diets_repository.dart | 2 + .../my_diets/services/my_diets_service.dart | 5 +- .../my_diets/services/my_diets_service.g.dart | 4 +- .../profile/models/profile_model.dart | 2 +- .../profile/models/profile_model.freezed.dart | 55 ++- .../profile/models/profile_model.g.dart | 4 +- .../presentation/pages/profile_screen.dart | 28 +- .../entities/recipe_create_entity.dart | 10 +- .../recipe_create_entity.freezed.dart | 128 +++-- .../create_recipe_cubit.dart | 20 +- .../widgets/time_of_cook_widget.dart | 32 +- .../recipes/services/recipe_service.g.dart | 159 +++--- .../services/registration_service.g.dart | 94 ++-- .../presentation/pages/settings_screen.dart | 2 +- .../settings/services/settings_service.g.dart | 99 ++-- .../services/profile_service_test.dart | 2 +- 51 files changed, 1382 insertions(+), 1360 deletions(-) create mode 100644 lib/features/feed/presentation/widgets/success_published_diet_bottom_sheet.dart diff --git a/lib/core/app_router.dart b/lib/core/app_router.dart index b7239f07..646293d0 100644 --- a/lib/core/app_router.dart +++ b/lib/core/app_router.dart @@ -54,11 +54,10 @@ class AppRouter { initialLocation: '/auth-screen', navigatorKey: navigatorKey, routes: [ - GoRoute( + GoRoute( path: '/auth-screen', name: 'authScreen', redirect: (context, state) async { - // TokenStorage.removeTokenInStorage(); if (await TokenStorage.haveTokenInStorage()) { return '/my-profile-screen'; } @@ -276,7 +275,10 @@ class AppRouter { GoRoute( path: CreateDietScreen.path, name: CreateDietScreen.name, - builder: (context, state) => CreateDietScreen(), + builder: (context, state) { + final diet = state.extra as Diet?; + return CreateDietScreen(diet: diet); + }, ), GoRoute( path: DietPreviewScreen.path, diff --git a/lib/core/widgets/bottom_sheet_with_blur.dart b/lib/core/widgets/bottom_sheet_with_blur.dart index 303170c4..d6c1b61f 100644 --- a/lib/core/widgets/bottom_sheet_with_blur.dart +++ b/lib/core/widgets/bottom_sheet_with_blur.dart @@ -4,42 +4,44 @@ import 'package:eatvio_app/core/theme/theme_helper.dart'; import 'package:flutter/material.dart'; Future showBottomSheetWithBlur({ - required BuildContext context, - required Widget child, - double height = 200, - bool hasBlur = true, - bool enableDrag = true, - }) { - return showModalBottomSheet( - context: context, - isScrollControlled: true, - barrierColor: appTheme.transparentBlack, - backgroundColor: Colors.transparent, - elevation: 0, - useSafeArea: true, - enableDrag: enableDrag, - builder: (context) => GestureDetector( - behavior: HitTestBehavior.opaque, - child: BackdropFilter( - filter: ImageFilter.blur( - sigmaX: hasBlur ? 24 : 0, - sigmaY: hasBlur ? 24 : 0, - tileMode: TileMode.mirror, - ), - child: SizedBox( - child: Container( - height: height, - decoration: BoxDecoration( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(20), - topRight: Radius.circular(20), - ), - color: appTheme.whiteA700, + required BuildContext context, + required Widget child, + double height = 200, + bool hasBlur = true, + bool enableDrag = true, + bool userRootNavigator = false, +}) { + return showModalBottomSheet( + context: context, + isScrollControlled: true, + barrierColor: appTheme.transparentBlack, + backgroundColor: Colors.transparent, + useRootNavigator: userRootNavigator, + elevation: 0, + useSafeArea: true, + enableDrag: enableDrag, + builder: (context) => GestureDetector( + behavior: HitTestBehavior.opaque, + child: BackdropFilter( + filter: ImageFilter.blur( + sigmaX: hasBlur ? 24 : 0, + sigmaY: hasBlur ? 24 : 0, + tileMode: TileMode.mirror, + ), + child: SizedBox( + child: Container( + height: height, + decoration: BoxDecoration( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(20), + topRight: Radius.circular(20), ), - child: child, + color: appTheme.whiteA700, ), + child: child, ), ), ), - ); - } \ No newline at end of file + ), + ); +} diff --git a/lib/features/blog_post_create/services/blog_post_service.g.dart b/lib/features/blog_post_create/services/blog_post_service.g.dart index 33f4251a..1f338526 100644 --- a/lib/features/blog_post_create/services/blog_post_service.g.dart +++ b/lib/features/blog_post_create/services/blog_post_service.g.dart @@ -9,11 +9,7 @@ part of 'blog_post_service.dart'; // ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers,unused_element,unnecessary_string_interpolations class _BlogPostService implements BlogPostService { - _BlogPostService( - this._dio, { - this.baseUrl, - this.errorLogger, - }); + _BlogPostService(this._dio, {this.baseUrl, this.errorLogger}); final Dio _dio; @@ -27,23 +23,21 @@ class _BlogPostService implements BlogPostService { final queryParameters = {}; final _headers = {}; final _data = formData; - final _options = _setStreamType(Options( - method: 'POST', - headers: _headers, - extra: _extra, - contentType: 'multipart/form-data', - ) - .compose( - _dio.options, - '/api/blogPosts/create', - queryParameters: queryParameters, - data: _data, - ) - .copyWith( - baseUrl: _combineBaseUrls( - _dio.options.baseUrl, - baseUrl, - ))); + final _options = _setStreamType( + Options( + method: 'POST', + headers: _headers, + extra: _extra, + contentType: 'multipart/form-data', + ) + .compose( + _dio.options, + '/api/blogPosts/create', + queryParameters: queryParameters, + data: _data, + ) + .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)), + ); await _dio.fetch(_options); } @@ -56,23 +50,21 @@ class _BlogPostService implements BlogPostService { final queryParameters = {}; final _headers = {}; final _data = formData; - final _options = _setStreamType(Options( - method: 'POST', - headers: _headers, - extra: _extra, - contentType: 'multipart/form-data', - ) - .compose( - _dio.options, - '/api/blogPosts/update/${id}', - queryParameters: queryParameters, - data: _data, - ) - .copyWith( - baseUrl: _combineBaseUrls( - _dio.options.baseUrl, - baseUrl, - ))); + final _options = _setStreamType( + Options( + method: 'POST', + headers: _headers, + extra: _extra, + contentType: 'multipart/form-data', + ) + .compose( + _dio.options, + '/api/blogPosts/update/${id}', + queryParameters: queryParameters, + data: _data, + ) + .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)), + ); await _dio.fetch(_options); } @@ -82,22 +74,16 @@ class _BlogPostService implements BlogPostService { final queryParameters = {}; final _headers = {}; const Map? _data = null; - final _options = _setStreamType(Options( - method: 'POST', - headers: _headers, - extra: _extra, - ) - .compose( - _dio.options, - '/api/blogPosts/delete/${id}', - queryParameters: queryParameters, - data: _data, - ) - .copyWith( - baseUrl: _combineBaseUrls( - _dio.options.baseUrl, - baseUrl, - ))); + final _options = _setStreamType( + Options(method: 'POST', headers: _headers, extra: _extra) + .compose( + _dio.options, + '/api/blogPosts/delete/${id}', + queryParameters: queryParameters, + data: _data, + ) + .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)), + ); await _dio.fetch(_options); } @@ -114,10 +100,7 @@ class _BlogPostService implements BlogPostService { return requestOptions; } - String _combineBaseUrls( - String dioBaseUrl, - String? baseUrl, - ) { + String _combineBaseUrls(String dioBaseUrl, String? baseUrl) { if (baseUrl == null || baseUrl.trim().isEmpty) { return dioBaseUrl; } diff --git a/lib/features/calendar/services/calendar_service.g.dart b/lib/features/calendar/services/calendar_service.g.dart index 787983c8..5055d002 100644 --- a/lib/features/calendar/services/calendar_service.g.dart +++ b/lib/features/calendar/services/calendar_service.g.dart @@ -9,11 +9,7 @@ part of 'calendar_service.dart'; // ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers,unused_element,unnecessary_string_interpolations class _CalendarService implements CalendarService { - _CalendarService( - this._dio, { - this.baseUrl, - this.errorLogger, - }); + _CalendarService(this._dio, {this.baseUrl, this.errorLogger}); final Dio _dio; @@ -27,27 +23,23 @@ class _CalendarService implements CalendarService { final queryParameters = {}; final _headers = {}; const Map? _data = null; - final _options = _setStreamType>(Options( - method: 'GET', - headers: _headers, - extra: _extra, - ) - .compose( - _dio.options, - '/api/calendar/index', - queryParameters: queryParameters, - data: _data, - ) - .copyWith( - baseUrl: _combineBaseUrls( - _dio.options.baseUrl, - baseUrl, - ))); + final _options = _setStreamType>( + Options(method: 'GET', headers: _headers, extra: _extra) + .compose( + _dio.options, + '/api/calendar/index', + queryParameters: queryParameters, + data: _data, + ) + .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)), + ); final _result = await _dio.fetch>(_options); late List _value; try { _value = _result.data! - .map((dynamic i) => CalendarModel.fromJson(i as Map)) + .map( + (dynamic i) => CalendarModel.fromJson(i as Map), + ) .toList(); } on Object catch (e, s) { errorLogger?.logError(e, s, _options); @@ -69,10 +61,7 @@ class _CalendarService implements CalendarService { return requestOptions; } - String _combineBaseUrls( - String dioBaseUrl, - String? baseUrl, - ) { + String _combineBaseUrls(String dioBaseUrl, String? baseUrl) { if (baseUrl == null || baseUrl.trim().isEmpty) { return dioBaseUrl; } diff --git a/lib/features/consultant/become_consultant/services/consultant_service.g.dart b/lib/features/consultant/become_consultant/services/consultant_service.g.dart index d895b861..79dd924e 100644 --- a/lib/features/consultant/become_consultant/services/consultant_service.g.dart +++ b/lib/features/consultant/become_consultant/services/consultant_service.g.dart @@ -9,11 +9,7 @@ part of 'consultant_service.dart'; // ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers,unused_element,unnecessary_string_interpolations class _ConsultantService implements ConsultantService { - _ConsultantService( - this._dio, { - this.baseUrl, - this.errorLogger, - }); + _ConsultantService(this._dio, {this.baseUrl, this.errorLogger}); final Dio _dio; @@ -22,28 +18,23 @@ class _ConsultantService implements ConsultantService { final ParseErrorLogger? errorLogger; @override - Future consultantRegistration( - {required ConsultantProfileModel consultantModel}) async { + Future consultantRegistration({ + required ConsultantProfileModel consultantModel, + }) async { final _extra = {}; final queryParameters = {}; final _headers = {}; final _data = consultantModel; - final _options = _setStreamType(Options( - method: 'POST', - headers: _headers, - extra: _extra, - ) - .compose( - _dio.options, - '/api/consultant_request/registration', - queryParameters: queryParameters, - data: _data, - ) - .copyWith( - baseUrl: _combineBaseUrls( - _dio.options.baseUrl, - baseUrl, - ))); + final _options = _setStreamType( + Options(method: 'POST', headers: _headers, extra: _extra) + .compose( + _dio.options, + '/api/consultant_request/registration', + queryParameters: queryParameters, + data: _data, + ) + .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)), + ); await _dio.fetch(_options); } @@ -57,23 +48,21 @@ class _ConsultantService implements ConsultantService { if (photos != null) { _data.files.addAll(photos.map((i) => MapEntry('photos[]', i))); } - final _options = _setStreamType>(Options( - method: 'POST', - headers: _headers, - extra: _extra, - contentType: 'multipart/form-data', - ) - .compose( - _dio.options, - '/api/consultant_request/photos/upload', - queryParameters: queryParameters, - data: _data, - ) - .copyWith( - baseUrl: _combineBaseUrls( - _dio.options.baseUrl, - baseUrl, - ))); + final _options = _setStreamType>( + Options( + method: 'POST', + headers: _headers, + extra: _extra, + contentType: 'multipart/form-data', + ) + .compose( + _dio.options, + '/api/consultant_request/photos/upload', + queryParameters: queryParameters, + data: _data, + ) + .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)), + ); final _result = await _dio.fetch>(_options); late List _value; try { @@ -98,10 +87,7 @@ class _ConsultantService implements ConsultantService { return requestOptions; } - String _combineBaseUrls( - String dioBaseUrl, - String? baseUrl, - ) { + String _combineBaseUrls(String dioBaseUrl, String? baseUrl) { if (baseUrl == null || baseUrl.trim().isEmpty) { return dioBaseUrl; } diff --git a/lib/features/create_diet/models/create_diet_model.dart b/lib/features/create_diet/models/create_diet_model.dart index f089ff92..84639c77 100644 --- a/lib/features/create_diet/models/create_diet_model.dart +++ b/lib/features/create_diet/models/create_diet_model.dart @@ -9,12 +9,15 @@ class CreateDietModel with _$CreateDietModel { const CreateDietModel._(); const factory CreateDietModel({ + String? imageUrl, String? name, String? details, - String? state, + DietState? state, int? duration, required Map type, XFile? image, List>>? dailySchedule, }) = _CreateDietModel; } + + diff --git a/lib/features/create_diet/models/create_diet_model.freezed.dart b/lib/features/create_diet/models/create_diet_model.freezed.dart index 199f8e49..1effb5c0 100644 --- a/lib/features/create_diet/models/create_diet_model.freezed.dart +++ b/lib/features/create_diet/models/create_diet_model.freezed.dart @@ -16,9 +16,10 @@ final _privateConstructorUsedError = UnsupportedError( /// @nodoc mixin _$CreateDietModel { + String? get imageUrl => throw _privateConstructorUsedError; String? get name => throw _privateConstructorUsedError; String? get details => throw _privateConstructorUsedError; - String? get state => throw _privateConstructorUsedError; + DietState? get state => throw _privateConstructorUsedError; int? get duration => throw _privateConstructorUsedError; Map get type => throw _privateConstructorUsedError; XFile? get image => throw _privateConstructorUsedError; @@ -39,9 +40,10 @@ abstract class $CreateDietModelCopyWith<$Res> { _$CreateDietModelCopyWithImpl<$Res, CreateDietModel>; @useResult $Res call( - {String? name, + {String? imageUrl, + String? name, String? details, - String? state, + DietState? state, int? duration, Map type, XFile? image, @@ -63,6 +65,7 @@ class _$CreateDietModelCopyWithImpl<$Res, $Val extends CreateDietModel> @pragma('vm:prefer-inline') @override $Res call({ + Object? imageUrl = freezed, Object? name = freezed, Object? details = freezed, Object? state = freezed, @@ -72,6 +75,10 @@ class _$CreateDietModelCopyWithImpl<$Res, $Val extends CreateDietModel> Object? dailySchedule = freezed, }) { return _then(_value.copyWith( + imageUrl: freezed == imageUrl + ? _value.imageUrl + : imageUrl // ignore: cast_nullable_to_non_nullable + as String?, name: freezed == name ? _value.name : name // ignore: cast_nullable_to_non_nullable @@ -83,7 +90,7 @@ class _$CreateDietModelCopyWithImpl<$Res, $Val extends CreateDietModel> state: freezed == state ? _value.state : state // ignore: cast_nullable_to_non_nullable - as String?, + as DietState?, duration: freezed == duration ? _value.duration : duration // ignore: cast_nullable_to_non_nullable @@ -113,9 +120,10 @@ abstract class _$$CreateDietModelImplCopyWith<$Res> @override @useResult $Res call( - {String? name, + {String? imageUrl, + String? name, String? details, - String? state, + DietState? state, int? duration, Map type, XFile? image, @@ -135,6 +143,7 @@ class __$$CreateDietModelImplCopyWithImpl<$Res> @pragma('vm:prefer-inline') @override $Res call({ + Object? imageUrl = freezed, Object? name = freezed, Object? details = freezed, Object? state = freezed, @@ -144,6 +153,10 @@ class __$$CreateDietModelImplCopyWithImpl<$Res> Object? dailySchedule = freezed, }) { return _then(_$CreateDietModelImpl( + imageUrl: freezed == imageUrl + ? _value.imageUrl + : imageUrl // ignore: cast_nullable_to_non_nullable + as String?, name: freezed == name ? _value.name : name // ignore: cast_nullable_to_non_nullable @@ -155,7 +168,7 @@ class __$$CreateDietModelImplCopyWithImpl<$Res> state: freezed == state ? _value.state : state // ignore: cast_nullable_to_non_nullable - as String?, + as DietState?, duration: freezed == duration ? _value.duration : duration // ignore: cast_nullable_to_non_nullable @@ -180,7 +193,8 @@ class __$$CreateDietModelImplCopyWithImpl<$Res> class _$CreateDietModelImpl extends _CreateDietModel { const _$CreateDietModelImpl( - {this.name, + {this.imageUrl, + this.name, this.details, this.state, this.duration, @@ -191,12 +205,14 @@ class _$CreateDietModelImpl extends _CreateDietModel { _dailySchedule = dailySchedule, super._(); + @override + final String? imageUrl; @override final String? name; @override final String? details; @override - final String? state; + final DietState? state; @override final int? duration; final Map _type; @@ -221,7 +237,7 @@ class _$CreateDietModelImpl extends _CreateDietModel { @override String toString() { - return 'CreateDietModel(name: $name, details: $details, state: $state, duration: $duration, type: $type, image: $image, dailySchedule: $dailySchedule)'; + return 'CreateDietModel(imageUrl: $imageUrl, name: $name, details: $details, state: $state, duration: $duration, type: $type, image: $image, dailySchedule: $dailySchedule)'; } @override @@ -229,6 +245,8 @@ class _$CreateDietModelImpl extends _CreateDietModel { return identical(this, other) || (other.runtimeType == runtimeType && other is _$CreateDietModelImpl && + (identical(other.imageUrl, imageUrl) || + other.imageUrl == imageUrl) && (identical(other.name, name) || other.name == name) && (identical(other.details, details) || other.details == details) && (identical(other.state, state) || other.state == state) && @@ -243,6 +261,7 @@ class _$CreateDietModelImpl extends _CreateDietModel { @override int get hashCode => Object.hash( runtimeType, + imageUrl, name, details, state, @@ -263,9 +282,10 @@ class _$CreateDietModelImpl extends _CreateDietModel { abstract class _CreateDietModel extends CreateDietModel { const factory _CreateDietModel( - {final String? name, + {final String? imageUrl, + final String? name, final String? details, - final String? state, + final DietState? state, final int? duration, required final Map type, final XFile? image, @@ -273,12 +293,14 @@ abstract class _CreateDietModel extends CreateDietModel { _$CreateDietModelImpl; const _CreateDietModel._() : super._(); + @override + String? get imageUrl; @override String? get name; @override String? get details; @override - String? get state; + DietState? get state; @override int? get duration; @override diff --git a/lib/features/create_diet/presentation/create_diet_screen.dart b/lib/features/create_diet/presentation/create_diet_screen.dart index 49b23577..cb1cd953 100644 --- a/lib/features/create_diet/presentation/create_diet_screen.dart +++ b/lib/features/create_diet/presentation/create_diet_screen.dart @@ -6,6 +6,7 @@ import 'package:eatvio_app/core/widgets/bottom_sheet_handler_bar.dart'; import 'package:eatvio_app/core/widgets/bottom_sheet_with_blur.dart'; import 'package:eatvio_app/core/widgets/custom_outlined_button.dart'; import 'package:eatvio_app/core/widgets/custom_text_form_field.dart'; +import 'package:eatvio_app/core/widgets/loader.dart'; import 'package:eatvio_app/core/widgets/main_background_bouble.dart'; import 'package:eatvio_app/features/create_diet/presentation/cubit/create_diet_cubit.dart'; import 'package:eatvio_app/features/create_diet/presentation/cubit/create_diet_state.dart'; @@ -16,6 +17,7 @@ import 'package:eatvio_app/features/create_diet/presentation/widgets/diet_image_ import 'package:eatvio_app/features/create_diet/presentation/widgets/error_widget.dart'; import 'package:eatvio_app/features/create_diet/presentation/widgets/status_drop_down_widget.dart'; import 'package:eatvio_app/features/create_diet/presentation/widgets/diet_type_choice_widget.dart'; +import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart'; import 'package:eatvio_app/generated/l10n.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -24,19 +26,37 @@ import 'package:go_router/go_router.dart'; class CreateDietScreen extends StatelessWidget { static const name = 'createDietScreen'; static const path = '/create-diet-screen'; - const CreateDietScreen({super.key}); - + const CreateDietScreen({super.key, this.diet}); + final Diet? diet; @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => sl(), - child: const CreateDietScreenContent(), + create: (context) => sl()..setInitialData(diet), + child: CreateDietScreenContent(diet: diet), ); } } -class CreateDietScreenContent extends StatelessWidget { - const CreateDietScreenContent({super.key}); +class CreateDietScreenContent extends StatefulWidget { + CreateDietScreenContent({super.key, this.diet}); + final Diet? diet; + + @override + State createState() => _CreateDietScreenContentState(); +} + +class _CreateDietScreenContentState extends State { + final nameController = TextEditingController(); + final descriptionController = TextEditingController(); + + @override + void initState() { + super.initState(); + if (widget.diet != null) { + nameController.text = widget.diet?.name ?? ''; + descriptionController.text = widget.diet?.details ?? ''; + } + } @override Widget build(BuildContext context) { @@ -44,55 +64,12 @@ class CreateDietScreenContent extends StatelessWidget { return BlocBuilder( builder: (context, state) { if (state.published) { - WidgetsBinding.instance.addPostFrameCallback((_) { - context.pop(); - showBottomSheetWithBlur( - context: context, - child: Column( - children: [ - SizedBox( - height: 20, - ), - BottomSheetHandlerBar(), - SizedBox( - height: 20, - ), - Text( - '${state.dietData.name ?? 'Диета'}', - style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black), - ), - Text( - 'опубликованна', - style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black), - ), - SizedBox( - height: 20, - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: CustomOutlinedButton( - onPressed: () { - context.pop(); - }, - text: 'Хорошо', - height: 52, - buttonTextStyle: TextStyles.textSSemibold.copyWith(color: appTheme.whiteA700), - buttonStyle: ButtonStyle( - side: WidgetStatePropertyAll(BorderSide.none), - backgroundColor: WidgetStatePropertyAll(appTheme.blueA400), - ), - ), - ), - SizedBox( - height: 20, - ), - ], - ), - ); + Future.delayed(Duration(milliseconds: 500), () { + context.pop('${state.dietData.name ?? 'Диета'}'); }); } else if (state.drafted) { - WidgetsBinding.instance.addPostFrameCallback((_) { - context.pop(); + Future.delayed(Duration(milliseconds: 500), () { + context.pop('${state.dietData.name ?? 'Диета'}'); showBottomSheetWithBlur( context: context, child: Column( @@ -116,7 +93,7 @@ class CreateDietScreenContent extends StatelessWidget { padding: const EdgeInsets.symmetric(horizontal: 20), child: CustomOutlinedButton( onPressed: () { - context.pop(); + Navigator.of(context, rootNavigator: true).pop(); }, text: 'Хорошо', height: 52, @@ -141,7 +118,7 @@ class CreateDietScreenContent extends StatelessWidget { appBar: AppBar( centerTitle: true, title: Text( - s.create_diet, + widget.diet != null ? 'Редактирование диеты' : s.create_diet, textAlign: TextAlign.center, overflow: TextOverflow.ellipsis, softWrap: true, @@ -160,6 +137,7 @@ class CreateDietScreenContent extends StatelessWidget { SizedBox(height: 16), CustomTextFormField( hintText: s.enter_diet_name, + controller: nameController, hasError: state.dietNameError != null, onChanged: (value) { context.read().setName(value); @@ -176,6 +154,7 @@ class CreateDietScreenContent extends StatelessWidget { height: 132, child: CustomTextFormField( hasError: state.dietDescriptionError != null, + controller: descriptionController, textAlignVertical: TextAlignVertical.top, maxLines: null, minLines: null, @@ -227,30 +206,19 @@ class CreateDietScreenContent extends StatelessWidget { physics: NeverScrollableScrollPhysics(), separatorBuilder: (context, index) => SizedBox(height: 8), ), - SizedBox(height: 20), + SizedBox(height: 12), Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0), child: Column( children: [ CustomOutlinedButton( - onPressed: () async { - await context.read().publishDiet(toDraft: true); - }, - text: s.to_draft, - height: 52, - buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.gray), - buttonStyle: ButtonStyle( - side: WidgetStatePropertyAll(BorderSide.none), - backgroundColor: WidgetStatePropertyAll(appTheme.dottedBorder), - ), - ), - SizedBox(height: 12), - CustomOutlinedButton( - onPressed: () async { - final diet = await context.read().getDietForPreview(); - if (diet == null) return; - GoRouter.of(context).pushNamed(DietPreviewScreen.name, extra: diet); - }, + onPressed: state.isDietPublishing + ? () {} + : () async { + final diet = await context.read().getDietForPreview(); + if (diet == null) return; + GoRouter.of(context).pushNamed(DietPreviewScreen.name, extra: diet); + }, text: s.preview, height: 52, buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.gray), @@ -260,18 +228,21 @@ class CreateDietScreenContent extends StatelessWidget { ), ), SizedBox(height: 12), - CustomOutlinedButton( - onPressed: () { - context.read().publishDiet(); - }, - text: s.publish, - height: 52, - buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700), - buttonStyle: ButtonStyle( - side: WidgetStatePropertyAll(BorderSide.none), - backgroundColor: WidgetStatePropertyAll(appTheme.blueA400), + if (state.isDietPublishing) + Loader() + else + CustomOutlinedButton( + onPressed: () { + context.read().publishDiet(); + }, + text: widget.diet != null ? s.save : s.publish, + height: 52, + buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700), + buttonStyle: ButtonStyle( + side: WidgetStatePropertyAll(BorderSide.none), + backgroundColor: WidgetStatePropertyAll(appTheme.blueA400), + ), ), - ), ], ), ), @@ -282,5 +253,5 @@ class CreateDietScreenContent extends StatelessWidget { ); }, ); - } + } } diff --git a/lib/features/create_diet/presentation/cubit/create_diet_cubit.dart b/lib/features/create_diet/presentation/cubit/create_diet_cubit.dart index 67107cea..771e2531 100644 --- a/lib/features/create_diet/presentation/cubit/create_diet_cubit.dart +++ b/lib/features/create_diet/presentation/cubit/create_diet_cubit.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'dart:io'; import 'dart:developer'; +import 'package:dio/dio.dart'; import 'package:eatvio_app/core/const.dart'; import 'package:eatvio_app/core/image_compressing/image_compress_utility.dart'; import 'package:eatvio_app/features/create_diet/presentation/cubit/create_diet_state.dart'; @@ -16,10 +17,46 @@ class CreateDietCubit extends Cubit { final CreateDietRepository createDietRepository; final ProfileRepository profileRepository; final GlossaryRepository glossaryRepository; - static const _dietStateValues = ['active', 'personal', 'draft']; + String? tempImagePath; + static const _dietStateValues = DietState.values; CreateDietCubit({required this.profileRepository, required this.createDietRepository, required this.glossaryRepository}) : super(CreateDietState.initial()); + Future setInitialData(Diet? diet) async { + if (diet == null) return; + emit(state.copyWith(isLoading: true, isDietBeingEdited: true)); + final countOfDays = diet.dailySchedule.length; + final List>> dailySchedule = List.generate(countOfDays, (index) => [[], [], [], [], []]); + + for (final dayEntry in diet.dailySchedule.entries) { + for (final mealEntry in dayEntry.value.entries) { + for (final product in mealEntry.value.values) { + dailySchedule[dayEntry.key - 1][mealEntry.key - 1].add(product); + } + } + } + + final dietStateIndex = _dietStateValues.indexOf(diet.state); + final type = Map.fromIterable(dietTypeValues, value: (element) => false); + type['meat'] = diet.type.meat ?? false; + type['veg'] = diet.type.veg ?? false; + type['carb'] = diet.type.carb ?? false; + type['light'] = diet.type.light ?? false; + emit( + state.copyWith.dietData( + dailySchedule: dailySchedule, + details: diet.details, + state: diet.state, + name: diet.name, + duration: diet.duration, + type: type, + imageUrl: baseUrl + '/storage/' + diet.image, + ), + ); + emit(state.copyWith(dietStateIndex: dietStateIndex, durationIndex: durationValues.indexOf(diet.duration))); + emit(state.copyWith(isLoading: true)); + } + void setDetails(String text) { emit(state.copyWith(dietData: state.dietData.copyWith(details: text), dietDescriptionError: null)); } @@ -82,16 +119,16 @@ class CreateDietCubit extends Cubit { return await glossaryRepository.getGlossaryListByQuery(query); } - void setImage(XFile? image) { - emit(state.copyWith(dietData: state.dietData.copyWith(image: image), dietImageError: null)); + void setImage(XFile? image, {String? imageUrl}) { + emit(state.copyWith(dietData: state.dietData.copyWith(image: image, imageUrl: imageUrl), dietImageError: null)); } void setDailySchedule(List>> dailySchedule) { emit(state.copyWith(dietData: state.dietData.copyWith(dailySchedule: dailySchedule), dietDurationError: null)); } - void setDietState(int index) { - emit(state.copyWith(dietStateIndex: index, dietData: state.dietData.copyWith(state: _dietStateValues[index]), dietStateError: null)); + void setDietState(DietState dietState, int index) { + emit(state.copyWith(dietData: state.dietData.copyWith(state: dietState), dietStateIndex: index, dietStateError: null)); } void setDietType({required String key, required bool value}) { @@ -236,12 +273,26 @@ class CreateDietCubit extends Cubit { ); } } + final dailySchedule = >>{}; + + for (final (dayIndex, day) in state.dietData.dailySchedule!.indexed) { + final mappedDay = >{}; + for (final (mealIndex, meal) in day.indexed) { + final mappedMeal = {}; + for (final (productIndex, product) in meal.indexed) { + mappedMeal[productIndex + 1] = product; + } + mappedDay[mealIndex + 1] = mappedMeal; + } + dailySchedule[dayIndex + 1] = mappedDay; + } + final diet = Diet( id: 0, name: state.dietData.name ?? '', image: imageInBase64, duration: state.dietData.duration ?? 0, - state: state.dietData.state ?? '', + state: state.dietData.state ?? DietState.active, type: DietType( meat: state.dietData.type['meat'], veg: state.dietData.type['veg'], @@ -249,7 +300,7 @@ class CreateDietCubit extends Cubit { light: state.dietData.type['light'], ), details: state.dietData.details ?? '', - dailySchedule: state.dietData.dailySchedule ?? [], + dailySchedule: dailySchedule, author: Author( authorSlug: authorProfile.slug, authorName: authorProfile.name, @@ -267,32 +318,32 @@ class CreateDietCubit extends Cubit { return diet; } - Future publishDiet({bool toDraft = false}) async { + Future publishDiet() async { + emit(state.copyWith(isDietPublishing: true)); if (!_isFromValid()) return null; try { final image = await ImageCompressUtility.compressFile(File(state.dietData.image!.path)); - final dietState = toDraft ? _dietStateValues.last : state.dietData.state; - final diet = await createDietRepository.createDiet( - name: state.dietData.name, - duration: state.dietData.duration, + final dietState = state.dietData.state!; + final dailySchedule = state.dietData.dailySchedule; + await createDietRepository.createDiet( + name: state.dietData.name ?? '', + duration: state.dietData.duration ?? 7, state: dietState, image: image, meatType: state.dietData.type['meat'] == true ? '1' : '0', carbType: state.dietData.type['carb'] == true ? '1' : '0', vegType: state.dietData.type['veg'] == true ? '1' : '0', lightType: state.dietData.type['light'] == true ? '1' : '0', - details: state.dietData.details, + details: state.dietData.details ?? '', + dailySchedule: dailySchedule ?? [], ); - final dailySchedule = state.dietData.dailySchedule; - createDietRepository.createDietDailySchedule( - id: diet.id, - model: {'daily_schedule': dailySchedule}, - ); - emit(state.copyWith(published: !toDraft, drafted: toDraft)); + + emit(state.copyWith(published: dietState != DietState.draft, drafted: dietState == DietState.draft)); return state.dietData.name; - } catch (e, stackTrace) { + } on DioException catch (e, stackTrace) { log(e.toString(), stackTrace: stackTrace); } + emit(state.copyWith(isDietPublishing: false)); return null; } @@ -336,15 +387,11 @@ class CreateDietCubit extends Cubit { if (dailySchedule == null) { result = false; } else { - final dailyScheduleErrors = state.dailyScheduleErrors.map((element) { - return element.map((element) => element).toList(); - }).toList(); + final dailyScheduleErrors = [...state.dailyScheduleErrors]; for (final (dayIndex, day) in dailySchedule.indexed) { - for (final (mealIndex, meal) in day.indexed) { - if (meal.isEmpty) { - result = false; - dailyScheduleErrors[dayIndex][mealIndex] = 'Добавьте продукты'; - } + if (day.every((meal) => meal.isEmpty)) { + result = false; + dailyScheduleErrors[dayIndex] = 'Добавьте продукты'; } } emit(state.copyWith(dailyScheduleErrors: dailyScheduleErrors)); @@ -358,16 +405,13 @@ class CreateDietCubit extends Cubit { if (dailySchedule == null) { result = false; } else { - final dailyScheduleErrors = state.dailyScheduleErrors.map((element) { - return element.map((element) => element).toList(); - }).toList(); - for (final (mealIndex, meal) in dailySchedule[dayIndex].indexed) { - if (meal.isEmpty) { - result = false; - dailyScheduleErrors[dayIndex][mealIndex] = 'Добавьте продукты'; - } else { - dailyScheduleErrors[dayIndex][mealIndex] = ''; - } + final dailyScheduleErrors = [...state.dailyScheduleErrors]; + final day = dailySchedule[dayIndex]; + if (day.every((meal) => meal.isEmpty)) { + result = false; + dailyScheduleErrors[dayIndex] = 'Добавьте продукты'; + } else { + dailyScheduleErrors[dayIndex] = ''; } emit(state.copyWith(dailyScheduleErrors: dailyScheduleErrors)); } diff --git a/lib/features/create_diet/presentation/cubit/create_diet_state.dart b/lib/features/create_diet/presentation/cubit/create_diet_state.dart index b8b85b79..f0a6c2c6 100644 --- a/lib/features/create_diet/presentation/cubit/create_diet_state.dart +++ b/lib/features/create_diet/presentation/cubit/create_diet_state.dart @@ -9,6 +9,7 @@ part 'create_diet_state.freezed.dart'; class CreateDietState with _$CreateDietState { const factory CreateDietState({ required bool isLoading, + required bool isDietPublishing, required String? errorMessage, required CreateDietModel dietData, List>? copiedDay, @@ -17,15 +18,17 @@ class CreateDietState with _$CreateDietState { String? dietDurationError, String? dietStateError, String? dietImageError, - required List> dailyScheduleErrors, + required List dailyScheduleErrors, int? durationIndex, int? dietStateIndex, @Default(false) bool published, @Default(false) bool drafted, + @Default(false) bool isDietBeingEdited, }) = _CreateDietState; factory CreateDietState.initial() => CreateDietState( - dailyScheduleErrors: List.filled(21, ['', '', '', '', '']), + dailyScheduleErrors: List.filled(21, ''), + isDietPublishing: false, isLoading: false, errorMessage: null, dietData: CreateDietModel( diff --git a/lib/features/create_diet/presentation/cubit/create_diet_state.freezed.dart b/lib/features/create_diet/presentation/cubit/create_diet_state.freezed.dart index a49cbb70..cc266342 100644 --- a/lib/features/create_diet/presentation/cubit/create_diet_state.freezed.dart +++ b/lib/features/create_diet/presentation/cubit/create_diet_state.freezed.dart @@ -17,6 +17,7 @@ final _privateConstructorUsedError = UnsupportedError( /// @nodoc mixin _$CreateDietState { bool get isLoading => throw _privateConstructorUsedError; + bool get isDietPublishing => throw _privateConstructorUsedError; String? get errorMessage => throw _privateConstructorUsedError; CreateDietModel get dietData => throw _privateConstructorUsedError; List>? get copiedDay => @@ -26,12 +27,12 @@ mixin _$CreateDietState { String? get dietDurationError => throw _privateConstructorUsedError; String? get dietStateError => throw _privateConstructorUsedError; String? get dietImageError => throw _privateConstructorUsedError; - List> get dailyScheduleErrors => - throw _privateConstructorUsedError; + List get dailyScheduleErrors => throw _privateConstructorUsedError; int? get durationIndex => throw _privateConstructorUsedError; int? get dietStateIndex => throw _privateConstructorUsedError; bool get published => throw _privateConstructorUsedError; bool get drafted => throw _privateConstructorUsedError; + bool get isDietBeingEdited => throw _privateConstructorUsedError; /// Create a copy of CreateDietState /// with the given fields replaced by the non-null parameter values. @@ -48,6 +49,7 @@ abstract class $CreateDietStateCopyWith<$Res> { @useResult $Res call( {bool isLoading, + bool isDietPublishing, String? errorMessage, CreateDietModel dietData, List>? copiedDay, @@ -56,11 +58,12 @@ abstract class $CreateDietStateCopyWith<$Res> { String? dietDurationError, String? dietStateError, String? dietImageError, - List> dailyScheduleErrors, + List dailyScheduleErrors, int? durationIndex, int? dietStateIndex, bool published, - bool drafted}); + bool drafted, + bool isDietBeingEdited}); $CreateDietModelCopyWith<$Res> get dietData; } @@ -81,6 +84,7 @@ class _$CreateDietStateCopyWithImpl<$Res, $Val extends CreateDietState> @override $Res call({ Object? isLoading = null, + Object? isDietPublishing = null, Object? errorMessage = freezed, Object? dietData = null, Object? copiedDay = freezed, @@ -94,12 +98,17 @@ class _$CreateDietStateCopyWithImpl<$Res, $Val extends CreateDietState> Object? dietStateIndex = freezed, Object? published = null, Object? drafted = null, + Object? isDietBeingEdited = null, }) { return _then(_value.copyWith( isLoading: null == isLoading ? _value.isLoading : isLoading // ignore: cast_nullable_to_non_nullable as bool, + isDietPublishing: null == isDietPublishing + ? _value.isDietPublishing + : isDietPublishing // ignore: cast_nullable_to_non_nullable + as bool, errorMessage: freezed == errorMessage ? _value.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable @@ -135,7 +144,7 @@ class _$CreateDietStateCopyWithImpl<$Res, $Val extends CreateDietState> dailyScheduleErrors: null == dailyScheduleErrors ? _value.dailyScheduleErrors : dailyScheduleErrors // ignore: cast_nullable_to_non_nullable - as List>, + as List, durationIndex: freezed == durationIndex ? _value.durationIndex : durationIndex // ignore: cast_nullable_to_non_nullable @@ -152,6 +161,10 @@ class _$CreateDietStateCopyWithImpl<$Res, $Val extends CreateDietState> ? _value.drafted : drafted // ignore: cast_nullable_to_non_nullable as bool, + isDietBeingEdited: null == isDietBeingEdited + ? _value.isDietBeingEdited + : isDietBeingEdited // ignore: cast_nullable_to_non_nullable + as bool, ) as $Val); } @@ -176,6 +189,7 @@ abstract class _$$CreateDietStateImplCopyWith<$Res> @useResult $Res call( {bool isLoading, + bool isDietPublishing, String? errorMessage, CreateDietModel dietData, List>? copiedDay, @@ -184,11 +198,12 @@ abstract class _$$CreateDietStateImplCopyWith<$Res> String? dietDurationError, String? dietStateError, String? dietImageError, - List> dailyScheduleErrors, + List dailyScheduleErrors, int? durationIndex, int? dietStateIndex, bool published, - bool drafted}); + bool drafted, + bool isDietBeingEdited}); @override $CreateDietModelCopyWith<$Res> get dietData; @@ -208,6 +223,7 @@ class __$$CreateDietStateImplCopyWithImpl<$Res> @override $Res call({ Object? isLoading = null, + Object? isDietPublishing = null, Object? errorMessage = freezed, Object? dietData = null, Object? copiedDay = freezed, @@ -221,12 +237,17 @@ class __$$CreateDietStateImplCopyWithImpl<$Res> Object? dietStateIndex = freezed, Object? published = null, Object? drafted = null, + Object? isDietBeingEdited = null, }) { return _then(_$CreateDietStateImpl( isLoading: null == isLoading ? _value.isLoading : isLoading // ignore: cast_nullable_to_non_nullable as bool, + isDietPublishing: null == isDietPublishing + ? _value.isDietPublishing + : isDietPublishing // ignore: cast_nullable_to_non_nullable + as bool, errorMessage: freezed == errorMessage ? _value.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable @@ -262,7 +283,7 @@ class __$$CreateDietStateImplCopyWithImpl<$Res> dailyScheduleErrors: null == dailyScheduleErrors ? _value._dailyScheduleErrors : dailyScheduleErrors // ignore: cast_nullable_to_non_nullable - as List>, + as List, durationIndex: freezed == durationIndex ? _value.durationIndex : durationIndex // ignore: cast_nullable_to_non_nullable @@ -279,6 +300,10 @@ class __$$CreateDietStateImplCopyWithImpl<$Res> ? _value.drafted : drafted // ignore: cast_nullable_to_non_nullable as bool, + isDietBeingEdited: null == isDietBeingEdited + ? _value.isDietBeingEdited + : isDietBeingEdited // ignore: cast_nullable_to_non_nullable + as bool, )); } } @@ -288,6 +313,7 @@ class __$$CreateDietStateImplCopyWithImpl<$Res> class _$CreateDietStateImpl implements _CreateDietState { const _$CreateDietStateImpl( {required this.isLoading, + required this.isDietPublishing, required this.errorMessage, required this.dietData, final List>? copiedDay, @@ -296,17 +322,20 @@ class _$CreateDietStateImpl implements _CreateDietState { this.dietDurationError, this.dietStateError, this.dietImageError, - required final List> dailyScheduleErrors, + required final List dailyScheduleErrors, this.durationIndex, this.dietStateIndex, this.published = false, - this.drafted = false}) + this.drafted = false, + this.isDietBeingEdited = false}) : _copiedDay = copiedDay, _dailyScheduleErrors = dailyScheduleErrors; @override final bool isLoading; @override + final bool isDietPublishing; + @override final String? errorMessage; @override final CreateDietModel dietData; @@ -330,9 +359,9 @@ class _$CreateDietStateImpl implements _CreateDietState { final String? dietStateError; @override final String? dietImageError; - final List> _dailyScheduleErrors; + final List _dailyScheduleErrors; @override - List> get dailyScheduleErrors { + List get dailyScheduleErrors { if (_dailyScheduleErrors is EqualUnmodifiableListView) return _dailyScheduleErrors; // ignore: implicit_dynamic_type @@ -349,10 +378,13 @@ class _$CreateDietStateImpl implements _CreateDietState { @override @JsonKey() final bool drafted; + @override + @JsonKey() + final bool isDietBeingEdited; @override String toString() { - return 'CreateDietState(isLoading: $isLoading, errorMessage: $errorMessage, dietData: $dietData, copiedDay: $copiedDay, dietNameError: $dietNameError, dietDescriptionError: $dietDescriptionError, dietDurationError: $dietDurationError, dietStateError: $dietStateError, dietImageError: $dietImageError, dailyScheduleErrors: $dailyScheduleErrors, durationIndex: $durationIndex, dietStateIndex: $dietStateIndex, published: $published, drafted: $drafted)'; + return 'CreateDietState(isLoading: $isLoading, isDietPublishing: $isDietPublishing, errorMessage: $errorMessage, dietData: $dietData, copiedDay: $copiedDay, dietNameError: $dietNameError, dietDescriptionError: $dietDescriptionError, dietDurationError: $dietDurationError, dietStateError: $dietStateError, dietImageError: $dietImageError, dailyScheduleErrors: $dailyScheduleErrors, durationIndex: $durationIndex, dietStateIndex: $dietStateIndex, published: $published, drafted: $drafted, isDietBeingEdited: $isDietBeingEdited)'; } @override @@ -362,6 +394,8 @@ class _$CreateDietStateImpl implements _CreateDietState { other is _$CreateDietStateImpl && (identical(other.isLoading, isLoading) || other.isLoading == isLoading) && + (identical(other.isDietPublishing, isDietPublishing) || + other.isDietPublishing == isDietPublishing) && (identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage) && (identical(other.dietData, dietData) || @@ -386,13 +420,16 @@ class _$CreateDietStateImpl implements _CreateDietState { other.dietStateIndex == dietStateIndex) && (identical(other.published, published) || other.published == published) && - (identical(other.drafted, drafted) || other.drafted == drafted)); + (identical(other.drafted, drafted) || other.drafted == drafted) && + (identical(other.isDietBeingEdited, isDietBeingEdited) || + other.isDietBeingEdited == isDietBeingEdited)); } @override int get hashCode => Object.hash( runtimeType, isLoading, + isDietPublishing, errorMessage, dietData, const DeepCollectionEquality().hash(_copiedDay), @@ -405,7 +442,8 @@ class _$CreateDietStateImpl implements _CreateDietState { durationIndex, dietStateIndex, published, - drafted); + drafted, + isDietBeingEdited); /// Create a copy of CreateDietState /// with the given fields replaced by the non-null parameter values. @@ -420,6 +458,7 @@ class _$CreateDietStateImpl implements _CreateDietState { abstract class _CreateDietState implements CreateDietState { const factory _CreateDietState( {required final bool isLoading, + required final bool isDietPublishing, required final String? errorMessage, required final CreateDietModel dietData, final List>? copiedDay, @@ -428,15 +467,18 @@ abstract class _CreateDietState implements CreateDietState { final String? dietDurationError, final String? dietStateError, final String? dietImageError, - required final List> dailyScheduleErrors, + required final List dailyScheduleErrors, final int? durationIndex, final int? dietStateIndex, final bool published, - final bool drafted}) = _$CreateDietStateImpl; + final bool drafted, + final bool isDietBeingEdited}) = _$CreateDietStateImpl; @override bool get isLoading; @override + bool get isDietPublishing; + @override String? get errorMessage; @override CreateDietModel get dietData; @@ -453,7 +495,7 @@ abstract class _CreateDietState implements CreateDietState { @override String? get dietImageError; @override - List> get dailyScheduleErrors; + List get dailyScheduleErrors; @override int? get durationIndex; @override @@ -462,6 +504,8 @@ abstract class _CreateDietState implements CreateDietState { bool get published; @override bool get drafted; + @override + bool get isDietBeingEdited; /// Create a copy of CreateDietState /// with the given fields replaced by the non-null parameter values. diff --git a/lib/features/create_diet/presentation/diet_preview_screen.dart b/lib/features/create_diet/presentation/diet_preview_screen.dart index 7459be0d..879e5446 100644 --- a/lib/features/create_diet/presentation/diet_preview_screen.dart +++ b/lib/features/create_diet/presentation/diet_preview_screen.dart @@ -335,7 +335,7 @@ class _DietPreviewScreenState extends State { physics: NeverScrollableScrollPhysics(), shrinkWrap: true, itemBuilder: (context, index) { - return _dayOfPlan(widget.diet.dailySchedule[index], index, s); + return _dayOfPlan(widget.diet.dailySchedule[index] ?? {}, index, s); }, itemCount: widget.diet.dailySchedule.length, ), @@ -363,7 +363,7 @@ class _DietPreviewScreenState extends State { ); } - _dayOfPlan(List> day, int dayNumber, S s) { + _dayOfPlan(Map> day, int dayNumber, S s) { _choiceChipWidget(int index) { return ChoiceChip( selectedColor: appTheme.blueA400, @@ -431,8 +431,8 @@ class _DietPreviewScreenState extends State { ListView.builder( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), - itemBuilder: (context, index) => _daily(day[widget.selectedIndexes[dayNumber]][index], s), - itemCount: day[widget.selectedIndexes[dayNumber]].length, + itemBuilder: (context, index) => _daily(day[widget.selectedIndexes[dayNumber] + 1]?[index + 1], s), + itemCount: day[widget.selectedIndexes[dayNumber] + 1]?.length ?? 0, ), SizedBox(height: 8), ], @@ -450,7 +450,8 @@ class _DietPreviewScreenState extends State { ); } - _daily(DailySchedule daily, S s) { + _daily(DailySchedule? daily, S s) { + if (daily == null) return SizedBox.shrink(); return Padding( padding: const EdgeInsets.only(bottom: 12), child: Row( diff --git a/lib/features/create_diet/presentation/widgets/add_meal_widget.dart b/lib/features/create_diet/presentation/widgets/add_meal_widget.dart index f705adb4..c0c76b03 100644 --- a/lib/features/create_diet/presentation/widgets/add_meal_widget.dart +++ b/lib/features/create_diet/presentation/widgets/add_meal_widget.dart @@ -199,8 +199,7 @@ class _AddMealWidgetState extends State { final portion = DailySchedule( dailyNutrition: DailyNutrition(energy: calories, proteins: proteins, fats: fats, carbohydrates: carbohydrates), quantity: int.parse(quantityController.text), - name: glossary.name, - uniq: '', + name: glossary.name, productNutritionalValueId: glossary.id.toString(), portion: measurementUnits, ); @@ -240,7 +239,6 @@ class _AddMealWidgetState extends State { dailyNutrition: DailyNutrition(energy: calories, proteins: proteins, fats: fats, carbohydrates: carbohydrates), quantity: int.parse(quantityController.text), name: newGlossary.name, - uniq: '', productNutritionalValueId: newGlossary.id.toString(), portion: measurementUnits, ); diff --git a/lib/features/create_diet/presentation/widgets/day_widget.dart b/lib/features/create_diet/presentation/widgets/day_widget.dart index ba97708d..8ca5cc11 100644 --- a/lib/features/create_diet/presentation/widgets/day_widget.dart +++ b/lib/features/create_diet/presentation/widgets/day_widget.dart @@ -35,7 +35,7 @@ class _DayWidgetState extends State { return BlocBuilder( builder: (context, state) { return MainBackgroundBouble( - hasError: state.dailyScheduleErrors[widget.index].where((element) => element.isNotEmpty).isNotEmpty, + hasError: state.dailyScheduleErrors[widget.index].isNotEmpty, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -216,16 +216,15 @@ class _DayWidgetState extends State { ), ], ), - if (state.dailyScheduleErrors[widget.index][_selectedMealIndex].isNotEmpty) ...[ + if (state.dailyScheduleErrors[widget.index].isNotEmpty) ...[ SizedBox(height: 12), - CustomErrorWidget(error: state.dailyScheduleErrors[widget.index][_selectedMealIndex]), + CustomErrorWidget(error: state.dailyScheduleErrors[widget.index]), ], SizedBox(height: 20), ], ), ), BlocBuilder( - buildWhen: (previous, current) => previous.dietData.dailySchedule != current.dietData.dailySchedule, builder: (context, state) { if (state.dietData.dailySchedule != null) { final day = state.dietData.dailySchedule?[widget.index]; diff --git a/lib/features/create_diet/presentation/widgets/diet_image_picker_widget.dart b/lib/features/create_diet/presentation/widgets/diet_image_picker_widget.dart index 0e195a09..c15c70b6 100644 --- a/lib/features/create_diet/presentation/widgets/diet_image_picker_widget.dart +++ b/lib/features/create_diet/presentation/widgets/diet_image_picker_widget.dart @@ -71,6 +71,8 @@ class _DietImagePickerWidgetState extends State { final s = S.of(context); return BlocBuilder( builder: (context, state) { + final hasImage = state.dietData.image != null || state.dietData.imageUrl != null; + final hasFile = state.dietData.image != null; return Column( children: [ GestureDetector( @@ -81,17 +83,26 @@ class _DietImagePickerWidgetState extends State { height: 240, decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), - image: state.dietData.image != null ? DecorationImage(fit: BoxFit.cover, image: FileImage(File(state.dietData.image?.path ?? ''))) : null, + image: hasImage + ? DecorationImage( + fit: BoxFit.cover, + image: (hasFile + ? FileImage( + File(state.dietData.image?.path ?? ''), + ) + : NetworkImage(state.dietData.imageUrl ?? '')) as ImageProvider, + ) + : null, color: appTheme.gray50, ), width: MediaQuery.sizeOf(context).width, - padding: state.dietData.image != null ? null : EdgeInsets.all(8), - child: state.dietData.image != null + padding: hasImage ? null : EdgeInsets.all(8), + child: hasImage ? Padding( padding: const EdgeInsets.only(top: 6, right: 6), child: GestureDetector( onTap: () { - context.read().setImage(null); + context.read().setImage(null, imageUrl: null); }, child: Align( alignment: Alignment.topRight, diff --git a/lib/features/create_diet/presentation/widgets/diet_type_choice_widget.dart b/lib/features/create_diet/presentation/widgets/diet_type_choice_widget.dart index 184f43bd..112ddbad 100644 --- a/lib/features/create_diet/presentation/widgets/diet_type_choice_widget.dart +++ b/lib/features/create_diet/presentation/widgets/diet_type_choice_widget.dart @@ -18,8 +18,7 @@ class DietTypeChoiceWidget extends StatefulWidget { class _DietTypeChoiceWidgetState extends State { final List<(String, String)> choiceContent = []; @override - void didChangeDependencies() { - // const List typeValues = ['veg', 'light', 'carb', 'meat']; + void didChangeDependencies() { super.didChangeDependencies(); final s = S.of(context); if (choiceContent.isEmpty) { diff --git a/lib/features/create_diet/presentation/widgets/status_drop_down_widget.dart b/lib/features/create_diet/presentation/widgets/status_drop_down_widget.dart index 47443b58..b178eccd 100644 --- a/lib/features/create_diet/presentation/widgets/status_drop_down_widget.dart +++ b/lib/features/create_diet/presentation/widgets/status_drop_down_widget.dart @@ -4,6 +4,7 @@ import 'package:eatvio_app/features/create_diet/presentation/cubit/create_diet_c import 'package:eatvio_app/features/create_diet/presentation/cubit/create_diet_state.dart'; import 'package:eatvio_app/core/widgets/custom_drop_down_button.dart'; import 'package:eatvio_app/features/create_diet/presentation/widgets/error_widget.dart'; +import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart'; import 'package:eatvio_app/generated/l10n.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -18,14 +19,23 @@ class StatusDropDownWidget extends StatefulWidget { class _StatusDropDownWidgetState extends State { final List dietStateTitles = []; + late final List dietStates = DietState.values; @override void didChangeDependencies() { super.didChangeDependencies(); final s = S.of(context); if (dietStateTitles.isEmpty) { - dietStateTitles.add(s.active_diet_state); - dietStateTitles.add(s.personal_diet_state); + dietStates.forEach((elem) { + switch (elem) { + case DietState.active: + dietStateTitles.add(s.active_diet_state); + case DietState.draft: + dietStateTitles.add(s.draft); + case DietState.personal: + dietStateTitles.add(s.personal_diet_state); + } + }); } } @@ -45,7 +55,8 @@ class _StatusDropDownWidgetState extends State { ), onChanged: (value) { if (value != null) { - context.read().setDietState(dietStateTitles.indexOf(value)); + final index = dietStates.indexOf(value); + context.read().setDietState(value, index); } }, items: dietStateTitles @@ -55,11 +66,11 @@ class _StatusDropDownWidgetState extends State { dietStateTitles[index], style: TextStyles.textMMedium.copyWith(color: appTheme.black), ), - value: dietStateTitles[index], + value: dietStates[index], ), ) .toList(), - value: state.dietStateIndex == null ? null : dietStateTitles[state.dietStateIndex ?? 0], + value: state.dietStateIndex == null ? null : dietStates[state.dietStateIndex ?? 0], ), if (state.dietStateError != null) ...[ SizedBox( diff --git a/lib/features/create_diet/repositories/create_diet_repository.dart b/lib/features/create_diet/repositories/create_diet_repository.dart index 1dbed20d..ba9a6fa4 100644 --- a/lib/features/create_diet/repositories/create_diet_repository.dart +++ b/lib/features/create_diet/repositories/create_diet_repository.dart @@ -1,5 +1,5 @@ import 'dart:io'; - +import 'package:dio/dio.dart'; import 'package:eatvio_app/features/create_diet/services/create_diet_service.dart'; import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart'; @@ -8,40 +8,54 @@ class CreateDietRepository { CreateDietRepository({required this.createDietService}); Future createDiet({ - String? name, - int? duration, - String? state, - String? meatType, - String? carbType, - String? vegType, - String? lightType, - String? details, - File? image, + required String name, + required int duration, + required DietState state, + required String meatType, + required String carbType, + required String vegType, + required String lightType, + required String details, + required File image, + required List>> dailySchedule, }) { - return createDietService.createDiet( - name: name, - duration: duration, - state: state, - meatType: meatType, - carbType: carbType, - vegType: vegType, - lightType: lightType, - details: details, - image: image, - ); - } + final dietMap = {}; + for (final (dayIndex, day) in dailySchedule.indexed) { + for (final (mealIndex, meal) in day.indexed) { + for (final (productIndex, product) in meal.indexed) { + final jsonProduct = product.toJson(); + dietMap.addAll({ + 'daily_schedule[${dayIndex + 1}][${mealIndex + 1}][${productIndex}][name]': jsonProduct['name'], + 'daily_schedule[${dayIndex + 1}][${mealIndex + 1}][${productIndex}][portion]': jsonProduct['portion'], + 'daily_schedule[${dayIndex + 1}][${mealIndex + 1}][${productIndex}][quantity]': jsonProduct['quantity'], + 'daily_schedule[${dayIndex + 1}][${mealIndex + 1}][${productIndex}][product_nutritional_value_id]': jsonProduct['product_nutritional_value_id'], + }); + } + } + } - Future createDietDailySchedule({ - required int id, - required Map model, - }) { - try { - return createDietService.createDietDailySchedule( - id: id, - model: model, - ); - } catch (e) { - rethrow; + dietMap.addAll({ + 'name': name, + 'details': details, + 'duration': duration, + 'type[meat]': meatType, + 'type[carb]': carbType, + 'type[veg]': vegType, + 'type[light]': lightType, + 'image': MultipartFile.fromFileSync( + image.path, + filename: image.path.split(Platform.pathSeparator).last, + ), + }); + + final formData = FormData.fromMap(dietMap); + switch (state) { + case DietState.active: + return createDietService.createPublishedDiet(formData: formData); + case DietState.draft: + return createDietService.createDraftedDiet(formData: formData); + case DietState.personal: + return createDietService.createPersonalDiet(formData: formData); } } } diff --git a/lib/features/create_diet/services/create_diet_service.dart b/lib/features/create_diet/services/create_diet_service.dart index 07432e3a..471681ee 100644 --- a/lib/features/create_diet/services/create_diet_service.dart +++ b/lib/features/create_diet/services/create_diet_service.dart @@ -1,5 +1,3 @@ -import 'dart:io'; - import 'package:dio/dio.dart'; import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart'; import 'package:retrofit/retrofit.dart'; @@ -10,23 +8,21 @@ part 'create_diet_service.g.dart'; abstract class CreateDietService { factory CreateDietService(Dio dio, {String baseUrl}) = _CreateDietService; - @POST('/api/diets/create') + @POST('/api/diets/create/published') @MultiPart() - Future createDiet({ - @Part(name: 'name') String? name, - @Part(name: 'duration') int? duration, - @Part(name: 'state') String? state, - @Part(name: 'type[meat]') String? meatType, - @Part(name: 'type[carb]') String? carbType, - @Part(name: 'type[veg]') String? vegType, - @Part(name: 'type[light]') String? lightType, - @Part(name: 'details') String? details, - @Part(name: 'image') File? image, + Future createPublishedDiet({ + @Body() required FormData formData, }); - @POST('/api/diets/create/{id}/daily_schedule') - Future createDietDailySchedule({ - @Path() required int id, - @Body() required Map model, + @POST('/api/diets/create/drafted') + @MultiPart() + Future createDraftedDiet({ + @Body() required FormData formData, + }); + + @POST('/api/diets/create/personalized') + @MultiPart() + Future createPersonalDiet({ + @Body() required FormData formData, }); } diff --git a/lib/features/create_diet/services/create_diet_service.g.dart b/lib/features/create_diet/services/create_diet_service.g.dart index 0c5a4606..8d039872 100644 --- a/lib/features/create_diet/services/create_diet_service.g.dart +++ b/lib/features/create_diet/services/create_diet_service.g.dart @@ -9,11 +9,7 @@ part of 'create_diet_service.dart'; // ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers,unused_element,unnecessary_string_interpolations class _CreateDietService implements CreateDietService { - _CreateDietService( - this._dio, { - this.baseUrl, - this.errorLogger, - }); + _CreateDietService(this._dio, {this.baseUrl, this.errorLogger}); final Dio _dio; @@ -22,98 +18,26 @@ class _CreateDietService implements CreateDietService { final ParseErrorLogger? errorLogger; @override - Future createDiet({ - String? name, - int? duration, - String? state, - String? meatType, - String? carbType, - String? vegType, - String? lightType, - String? details, - File? image, - }) async { + Future createPublishedDiet({required FormData formData}) async { final _extra = {}; final queryParameters = {}; - queryParameters.removeWhere((k, v) => v == null); final _headers = {}; - final _data = FormData(); - if (name != null) { - _data.fields.add(MapEntry( - 'name', - name, - )); - } - if (duration != null) { - _data.fields.add(MapEntry( - 'duration', - duration.toString(), - )); - } - if (state != null) { - _data.fields.add(MapEntry( - 'state', - state, - )); - } - if (meatType != null) { - _data.fields.add(MapEntry( - 'type[meat]', - meatType, - )); - } - if (carbType != null) { - _data.fields.add(MapEntry( - 'type[carb]', - carbType, - )); - } - if (vegType != null) { - _data.fields.add(MapEntry( - 'type[veg]', - vegType, - )); - } - if (lightType != null) { - _data.fields.add(MapEntry( - 'type[light]', - lightType, - )); - } - if (details != null) { - _data.fields.add(MapEntry( - 'details', - details, - )); - } - 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, - '/api/diets/create', - queryParameters: queryParameters, - data: _data, - ) - .copyWith( - baseUrl: _combineBaseUrls( - _dio.options.baseUrl, - baseUrl, - ))); + final _data = formData; + final _options = _setStreamType( + Options( + method: 'POST', + headers: _headers, + extra: _extra, + contentType: 'multipart/form-data', + ) + .compose( + _dio.options, + '/api/diets/create/published', + queryParameters: queryParameters, + data: _data, + ) + .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)), + ); final _result = await _dio.fetch>(_options); late CreatedDiet _value; try { @@ -126,32 +50,67 @@ class _CreateDietService implements CreateDietService { } @override - Future createDietDailySchedule({ - required int id, - required Map model, - }) async { + Future createDraftedDiet({required FormData formData}) async { final _extra = {}; final queryParameters = {}; final _headers = {}; - final _data = {}; - _data.addAll(model); - final _options = _setStreamType(Options( - method: 'POST', - headers: _headers, - extra: _extra, - ) - .compose( - _dio.options, - '/api/diets/create/${id}/daily_schedule', - queryParameters: queryParameters, - data: _data, - ) - .copyWith( - baseUrl: _combineBaseUrls( - _dio.options.baseUrl, - baseUrl, - ))); - await _dio.fetch(_options); + final _data = formData; + final _options = _setStreamType( + Options( + method: 'POST', + headers: _headers, + extra: _extra, + contentType: 'multipart/form-data', + ) + .compose( + _dio.options, + '/api/diets/create/drafted', + queryParameters: queryParameters, + data: _data, + ) + .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)), + ); + final _result = await _dio.fetch>(_options); + late CreatedDiet _value; + try { + _value = CreatedDiet.fromJson(_result.data!); + } on Object catch (e, s) { + errorLogger?.logError(e, s, _options); + rethrow; + } + return _value; + } + + @override + Future createPersonalDiet({required FormData formData}) async { + final _extra = {}; + final queryParameters = {}; + final _headers = {}; + final _data = formData; + final _options = _setStreamType( + Options( + method: 'POST', + headers: _headers, + extra: _extra, + contentType: 'multipart/form-data', + ) + .compose( + _dio.options, + '/api/diets/create/personalized', + queryParameters: queryParameters, + data: _data, + ) + .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)), + ); + final _result = await _dio.fetch>(_options); + late CreatedDiet _value; + try { + _value = CreatedDiet.fromJson(_result.data!); + } on Object catch (e, s) { + errorLogger?.logError(e, s, _options); + rethrow; + } + return _value; } RequestOptions _setStreamType(RequestOptions requestOptions) { @@ -167,10 +126,7 @@ class _CreateDietService implements CreateDietService { return requestOptions; } - String _combineBaseUrls( - String dioBaseUrl, - String? baseUrl, - ) { + String _combineBaseUrls(String dioBaseUrl, String? baseUrl) { if (baseUrl == null || baseUrl.trim().isEmpty) { return dioBaseUrl; } diff --git a/lib/features/create_plan/services/create_plan_service.g.dart b/lib/features/create_plan/services/create_plan_service.g.dart index af44f549..c93251e7 100644 --- a/lib/features/create_plan/services/create_plan_service.g.dart +++ b/lib/features/create_plan/services/create_plan_service.g.dart @@ -9,11 +9,7 @@ part of 'create_plan_service.dart'; // ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers,unused_element,unnecessary_string_interpolations class _CreatePlanService implements CreatePlanService { - _CreatePlanService( - this._dio, { - this.baseUrl, - this.errorLogger, - }); + _CreatePlanService(this._dio, {this.baseUrl, this.errorLogger}); final Dio _dio; @@ -28,23 +24,21 @@ class _CreatePlanService implements CreatePlanService { final _headers = {}; final _data = {}; _data.addAll(model); - final _options = _setStreamType(Options( - method: 'POST', - headers: _headers, - extra: _extra, - contentType: 'multipart/form-data', - ) - .compose( - _dio.options, - '/api/plans/create', - queryParameters: queryParameters, - data: _data, - ) - .copyWith( - baseUrl: _combineBaseUrls( - _dio.options.baseUrl, - baseUrl, - ))); + final _options = _setStreamType( + Options( + method: 'POST', + headers: _headers, + extra: _extra, + contentType: 'multipart/form-data', + ) + .compose( + _dio.options, + '/api/plans/create', + queryParameters: queryParameters, + data: _data, + ) + .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)), + ); await _dio.fetch(_options); } @@ -58,22 +52,16 @@ class _CreatePlanService implements CreatePlanService { final _headers = {}; final _data = {}; _data.addAll(model); - final _options = _setStreamType(Options( - method: 'POST', - headers: _headers, - extra: _extra, - ) - .compose( - _dio.options, - '/api/plans/create/${slug}/packet_option', - queryParameters: queryParameters, - data: _data, - ) - .copyWith( - baseUrl: _combineBaseUrls( - _dio.options.baseUrl, - baseUrl, - ))); + final _options = _setStreamType( + Options(method: 'POST', headers: _headers, extra: _extra) + .compose( + _dio.options, + '/api/plans/create/${slug}/packet_option', + queryParameters: queryParameters, + data: _data, + ) + .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)), + ); await _dio.fetch(_options); } @@ -87,22 +75,16 @@ class _CreatePlanService implements CreatePlanService { final _headers = {}; final _data = {}; _data.addAll(model); - final _options = _setStreamType(Options( - method: 'POST', - headers: _headers, - extra: _extra, - ) - .compose( - _dio.options, - '/api/plans/create/${slug}/packet_options', - queryParameters: queryParameters, - data: _data, - ) - .copyWith( - baseUrl: _combineBaseUrls( - _dio.options.baseUrl, - baseUrl, - ))); + final _options = _setStreamType( + Options(method: 'POST', headers: _headers, extra: _extra) + .compose( + _dio.options, + '/api/plans/create/${slug}/packet_options', + queryParameters: queryParameters, + data: _data, + ) + .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)), + ); await _dio.fetch(_options); } @@ -119,10 +101,7 @@ class _CreatePlanService implements CreatePlanService { return requestOptions; } - String _combineBaseUrls( - String dioBaseUrl, - String? baseUrl, - ) { + String _combineBaseUrls(String dioBaseUrl, String? baseUrl) { if (baseUrl == null || baseUrl.trim().isEmpty) { return dioBaseUrl; } diff --git a/lib/features/diary/services/diary_service.g.dart b/lib/features/diary/services/diary_service.g.dart index 4544f6bf..c7b3820f 100644 --- a/lib/features/diary/services/diary_service.g.dart +++ b/lib/features/diary/services/diary_service.g.dart @@ -9,11 +9,7 @@ part of 'diary_service.dart'; // ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers,unused_element,unnecessary_string_interpolations class _DiaryService implements DiaryService { - _DiaryService( - this._dio, { - this.baseUrl, - this.errorLogger, - }); + _DiaryService(this._dio, {this.baseUrl, this.errorLogger}); final Dio _dio; @@ -22,33 +18,30 @@ class _DiaryService implements DiaryService { final ParseErrorLogger? errorLogger; @override - Future> getDiaryIndexByDate( - {required String date}) async { + Future> getDiaryIndexByDate({ + required String date, + }) async { final _extra = {}; final queryParameters = {}; final _headers = {}; const Map? _data = null; - final _options = _setStreamType>(Options( - method: 'GET', - headers: _headers, - extra: _extra, - ) - .compose( - _dio.options, - '/api/diary/index/${date}', - queryParameters: queryParameters, - data: _data, - ) - .copyWith( - baseUrl: _combineBaseUrls( - _dio.options.baseUrl, - baseUrl, - ))); + final _options = _setStreamType>( + Options(method: 'GET', headers: _headers, extra: _extra) + .compose( + _dio.options, + '/api/diary/index/${date}', + queryParameters: queryParameters, + data: _data, + ) + .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)), + ); final _result = await _dio.fetch>(_options); late List _value; try { _value = _result.data! - .map((dynamic i) => DiaryDayModel.fromJson(i as Map)) + .map( + (dynamic i) => DiaryDayModel.fromJson(i as Map), + ) .toList(); } on Object catch (e, s) { errorLogger?.logError(e, s, _options); @@ -63,22 +56,16 @@ class _DiaryService implements DiaryService { final queryParameters = {}; final _headers = {}; const Map? _data = null; - final _options = _setStreamType(Options( - method: 'GET', - headers: _headers, - extra: _extra, - ) - .compose( - _dio.options, - '/api/diary/${diary}/show', - queryParameters: queryParameters, - data: _data, - ) - .copyWith( - baseUrl: _combineBaseUrls( - _dio.options.baseUrl, - baseUrl, - ))); + final _options = _setStreamType( + Options(method: 'GET', headers: _headers, extra: _extra) + .compose( + _dio.options, + '/api/diary/${diary}/show', + queryParameters: queryParameters, + data: _data, + ) + .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)), + ); final _result = await _dio.fetch>(_options); late DiaryDayWrapperModel _value; try { @@ -96,22 +83,16 @@ class _DiaryService implements DiaryService { final queryParameters = {}; final _headers = {}; const Map? _data = null; - final _options = _setStreamType(Options( - method: 'GET', - headers: _headers, - extra: _extra, - ) - .compose( - _dio.options, - '/api/diary/${diary}/dailyNutritionData', - queryParameters: queryParameters, - data: _data, - ) - .copyWith( - baseUrl: _combineBaseUrls( - _dio.options.baseUrl, - baseUrl, - ))); + final _options = _setStreamType( + Options(method: 'GET', headers: _headers, extra: _extra) + .compose( + _dio.options, + '/api/diary/${diary}/dailyNutritionData', + queryParameters: queryParameters, + data: _data, + ) + .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)), + ); final _result = await _dio.fetch>(_options); late NutritionDataModel _value; try { @@ -133,30 +114,30 @@ class _DiaryService implements DiaryService { final queryParameters = {}; final _headers = {}; final _data = FormData(); - _data.files.add(MapEntry( - 'photo', - MultipartFile.fromFileSync( - photo.path, - filename: photo.path.split(Platform.pathSeparator).last, + _data.files.add( + MapEntry( + 'photo', + MultipartFile.fromFileSync( + photo.path, + filename: photo.path.split(Platform.pathSeparator).last, + ), ), - )); - final _options = _setStreamType(Options( - method: 'POST', - headers: _headers, - extra: _extra, - contentType: 'multipart/form-data', - ) - .compose( - _dio.options, - '/api/diary/${diary}/meals/${typeNo}/photo/upload', - queryParameters: queryParameters, - data: _data, - ) - .copyWith( - baseUrl: _combineBaseUrls( - _dio.options.baseUrl, - baseUrl, - ))); + ); + final _options = _setStreamType( + Options( + method: 'POST', + headers: _headers, + extra: _extra, + contentType: 'multipart/form-data', + ) + .compose( + _dio.options, + '/api/diary/${diary}/meals/${typeNo}/photo/upload', + queryParameters: queryParameters, + data: _data, + ) + .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)), + ); await _dio.fetch(_options); } @@ -170,22 +151,16 @@ class _DiaryService implements DiaryService { final queryParameters = {}; final _headers = {}; const Map? _data = null; - final _options = _setStreamType(Options( - method: 'DELETE', - headers: _headers, - extra: _extra, - ) - .compose( - _dio.options, - '/api/diary/${diaryId}/meals/${typeNo}/photo/${userAttachment}/delete', - queryParameters: queryParameters, - data: _data, - ) - .copyWith( - baseUrl: _combineBaseUrls( - _dio.options.baseUrl, - baseUrl, - ))); + final _options = _setStreamType( + Options(method: 'DELETE', headers: _headers, extra: _extra) + .compose( + _dio.options, + '/api/diary/${diaryId}/meals/${typeNo}/photo/${userAttachment}/delete', + queryParameters: queryParameters, + data: _data, + ) + .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)), + ); await _dio.fetch(_options); } @@ -203,40 +178,27 @@ class _DiaryService implements DiaryService { queryParameters.removeWhere((k, v) => v == null); final _headers = {}; final _data = FormData(); - _data.fields.add(MapEntry( - 'product_name', - productName, - )); - _data.fields.add(MapEntry( - 'portion', - portion, - )); - _data.fields.add(MapEntry( - 'quantity', - quantity.toString(), - )); + _data.fields.add(MapEntry('product_name', productName)); + _data.fields.add(MapEntry('portion', portion)); + _data.fields.add(MapEntry('quantity', quantity.toString())); if (productNutritionalValueId != null) { - _data.fields.add(MapEntry( - 'product_nutritional_value_id', - productNutritionalValueId.toString(), - )); + _data.fields.add( + MapEntry( + 'product_nutritional_value_id', + productNutritionalValueId.toString(), + ), + ); } - final _options = _setStreamType(Options( - method: 'POST', - headers: _headers, - extra: _extra, - ) - .compose( - _dio.options, - '/api/diary/${diary}/meals/${typeNo}/portion/create', - queryParameters: queryParameters, - data: _data, - ) - .copyWith( - baseUrl: _combineBaseUrls( - _dio.options.baseUrl, - baseUrl, - ))); + final _options = _setStreamType( + Options(method: 'POST', headers: _headers, extra: _extra) + .compose( + _dio.options, + '/api/diary/${diary}/meals/${typeNo}/portion/create', + queryParameters: queryParameters, + data: _data, + ) + .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)), + ); await _dio.fetch(_options); } @@ -252,22 +214,16 @@ class _DiaryService implements DiaryService { final _headers = {}; final _data = {}; _data.addAll(productInMeal); - final _options = _setStreamType(Options( - method: 'PUT', - headers: _headers, - extra: _extra, - ) - .compose( - _dio.options, - '/api/diary/${diary}/meals/${typeNo}/portions/${portionModel}/update', - queryParameters: queryParameters, - data: _data, - ) - .copyWith( - baseUrl: _combineBaseUrls( - _dio.options.baseUrl, - baseUrl, - ))); + final _options = _setStreamType( + Options(method: 'PUT', headers: _headers, extra: _extra) + .compose( + _dio.options, + '/api/diary/${diary}/meals/${typeNo}/portions/${portionModel}/update', + queryParameters: queryParameters, + data: _data, + ) + .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)), + ); await _dio.fetch(_options); } @@ -281,22 +237,16 @@ class _DiaryService implements DiaryService { final queryParameters = {}; final _headers = {}; const Map? _data = null; - final _options = _setStreamType(Options( - method: 'POST', - headers: _headers, - extra: _extra, - ) - .compose( - _dio.options, - '/api/diary/${diary}/meals/${typeNo}/${portionModel}/mark_eaten', - queryParameters: queryParameters, - data: _data, - ) - .copyWith( - baseUrl: _combineBaseUrls( - _dio.options.baseUrl, - baseUrl, - ))); + final _options = _setStreamType( + Options(method: 'POST', headers: _headers, extra: _extra) + .compose( + _dio.options, + '/api/diary/${diary}/meals/${typeNo}/${portionModel}/mark_eaten', + queryParameters: queryParameters, + data: _data, + ) + .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)), + ); await _dio.fetch(_options); } @@ -310,22 +260,16 @@ class _DiaryService implements DiaryService { final queryParameters = {}; final _headers = {}; const Map? _data = null; - final _options = _setStreamType(Options( - method: 'POST', - headers: _headers, - extra: _extra, - ) - .compose( - _dio.options, - '/api/diary/${diary}/meals/${typeNo}/${portionModel}/unmark_eaten', - queryParameters: queryParameters, - data: _data, - ) - .copyWith( - baseUrl: _combineBaseUrls( - _dio.options.baseUrl, - baseUrl, - ))); + final _options = _setStreamType( + Options(method: 'POST', headers: _headers, extra: _extra) + .compose( + _dio.options, + '/api/diary/${diary}/meals/${typeNo}/${portionModel}/unmark_eaten', + queryParameters: queryParameters, + data: _data, + ) + .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)), + ); await _dio.fetch(_options); } @@ -339,22 +283,16 @@ class _DiaryService implements DiaryService { final queryParameters = {}; final _headers = {}; const Map? _data = null; - final _options = _setStreamType(Options( - method: 'POST', - headers: _headers, - extra: _extra, - ) - .compose( - _dio.options, - '/api/diary/${diary}/meals/${typeNo}/portions/${portionModel}/restore', - queryParameters: queryParameters, - data: _data, - ) - .copyWith( - baseUrl: _combineBaseUrls( - _dio.options.baseUrl, - baseUrl, - ))); + final _options = _setStreamType( + Options(method: 'POST', headers: _headers, extra: _extra) + .compose( + _dio.options, + '/api/diary/${diary}/meals/${typeNo}/portions/${portionModel}/restore', + queryParameters: queryParameters, + data: _data, + ) + .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)), + ); await _dio.fetch(_options); } @@ -368,22 +306,16 @@ class _DiaryService implements DiaryService { final queryParameters = {}; final _headers = {}; const Map? _data = null; - final _options = _setStreamType(Options( - method: 'DELETE', - headers: _headers, - extra: _extra, - ) - .compose( - _dio.options, - '/api/diary/${diary}/meals/${typeNo}/portions/${portionModel}/delete', - queryParameters: queryParameters, - data: _data, - ) - .copyWith( - baseUrl: _combineBaseUrls( - _dio.options.baseUrl, - baseUrl, - ))); + final _options = _setStreamType( + Options(method: 'DELETE', headers: _headers, extra: _extra) + .compose( + _dio.options, + '/api/diary/${diary}/meals/${typeNo}/portions/${portionModel}/delete', + queryParameters: queryParameters, + data: _data, + ) + .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)), + ); await _dio.fetch(_options); } @@ -396,22 +328,16 @@ class _DiaryService implements DiaryService { final queryParameters = {}; final _headers = {}; final _data = finishDiaryDayModel; - final _options = _setStreamType(Options( - method: 'POST', - headers: _headers, - extra: _extra, - ) - .compose( - _dio.options, - '/api/diary/${diary}/finishDay', - queryParameters: queryParameters, - data: _data, - ) - .copyWith( - baseUrl: _combineBaseUrls( - _dio.options.baseUrl, - baseUrl, - ))); + final _options = _setStreamType( + Options(method: 'POST', headers: _headers, extra: _extra) + .compose( + _dio.options, + '/api/diary/${diary}/finishDay', + queryParameters: queryParameters, + data: _data, + ) + .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)), + ); await _dio.fetch(_options); } @@ -425,22 +351,16 @@ class _DiaryService implements DiaryService { final queryParameters = {}; final _headers = {}; final _data = {'satiety': satiety}; - final _options = _setStreamType(Options( - method: 'POST', - headers: _headers, - extra: _extra, - ) - .compose( - _dio.options, - '/api/diary/${diary}/meals/${typeNo}/satiety/fill', - queryParameters: queryParameters, - data: _data, - ) - .copyWith( - baseUrl: _combineBaseUrls( - _dio.options.baseUrl, - baseUrl, - ))); + final _options = _setStreamType( + Options(method: 'POST', headers: _headers, extra: _extra) + .compose( + _dio.options, + '/api/diary/${diary}/meals/${typeNo}/satiety/fill', + queryParameters: queryParameters, + data: _data, + ) + .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)), + ); await _dio.fetch(_options); } @@ -454,49 +374,38 @@ class _DiaryService implements DiaryService { final queryParameters = {}; final _headers = {}; final _data = {'time': time}; - final _options = _setStreamType(Options( - method: 'POST', - headers: _headers, - extra: _extra, - ) - .compose( - _dio.options, - '/api/diary/${diary}/meals/${typeNo}/time/fill', - queryParameters: queryParameters, - data: _data, - ) - .copyWith( - baseUrl: _combineBaseUrls( - _dio.options.baseUrl, - baseUrl, - ))); + final _options = _setStreamType( + Options(method: 'POST', headers: _headers, extra: _extra) + .compose( + _dio.options, + '/api/diary/${diary}/meals/${typeNo}/time/fill', + queryParameters: queryParameters, + data: _data, + ) + .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)), + ); await _dio.fetch(_options); } @override - Future sendProductToModeration( - {required Map data}) async { + Future sendProductToModeration({ + required Map data, + }) async { final _extra = {}; final queryParameters = {}; final _headers = {}; final _data = {}; _data.addAll(data); - final _options = _setStreamType(Options( - method: 'POST', - headers: _headers, - extra: _extra, - ) - .compose( - _dio.options, - '/api/diary/product_nutritional_values/sendToModeration', - queryParameters: queryParameters, - data: _data, - ) - .copyWith( - baseUrl: _combineBaseUrls( - _dio.options.baseUrl, - baseUrl, - ))); + final _options = _setStreamType( + Options(method: 'POST', headers: _headers, extra: _extra) + .compose( + _dio.options, + '/api/diary/product_nutritional_values/sendToModeration', + queryParameters: queryParameters, + data: _data, + ) + .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)), + ); final _result = await _dio.fetch>(_options); late GlossaryModerationModel _value; try { @@ -521,10 +430,7 @@ class _DiaryService implements DiaryService { return requestOptions; } - String _combineBaseUrls( - String dioBaseUrl, - String? baseUrl, - ) { + String _combineBaseUrls(String dioBaseUrl, String? baseUrl) { if (baseUrl == null || baseUrl.trim().isEmpty) { return dioBaseUrl; } diff --git a/lib/features/diets/presentation/pages/about_diet_screen.dart b/lib/features/diets/presentation/pages/about_diet_screen.dart index 1c63c8f9..a701d978 100644 --- a/lib/features/diets/presentation/pages/about_diet_screen.dart +++ b/lib/features/diets/presentation/pages/about_diet_screen.dart @@ -454,7 +454,7 @@ class _AboutDietScreenState extends State { physics: NeverScrollableScrollPhysics(), shrinkWrap: true, itemBuilder: (context, index) { - return _dayOfPlan(widget.diet.dailySchedule[index], index, s); + return _dayOfPlan(widget.diet.dailySchedule[index + 1] ?? {}, index, s); }, itemCount: widget.diet.dailySchedule.length, ), @@ -500,7 +500,7 @@ class _AboutDietScreenState extends State { ); } - _dayOfPlan(List> day, int dayNumber, S s) { + _dayOfPlan(Map> day, int dayNumber, S s) { _choiceChipWidget(int index) { return ChoiceChip( selectedColor: appTheme.blueA400, @@ -568,8 +568,8 @@ class _AboutDietScreenState extends State { ListView.builder( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), - itemBuilder: (context, index) => _daily(day[widget.selectedIndexes[dayNumber]][index], s), - itemCount: day[widget.selectedIndexes[dayNumber]].length, + itemBuilder: (context, index) => _daily(day[widget.selectedIndexes[dayNumber] + 1]?[index + 1], s), + itemCount: day[widget.selectedIndexes[dayNumber] + 1]?.length ?? 0, ), SizedBox(height: 8), ], @@ -587,7 +587,8 @@ class _AboutDietScreenState extends State { ); } - _daily(DailySchedule daily, S s) { + _daily(DailySchedule? daily, S s) { + if (daily == null) return const SizedBox(); return Padding( padding: const EdgeInsets.only(bottom: 12), child: Row( diff --git a/lib/features/feed/models/diets_pagination_model.dart b/lib/features/feed/models/diets_pagination_model.dart index ba9fecf7..c67b8e07 100644 --- a/lib/features/feed/models/diets_pagination_model.dart +++ b/lib/features/feed/models/diets_pagination_model.dart @@ -23,10 +23,10 @@ class Diet with _$Diet { @JsonKey(name: "name") required String name, @JsonKey(name: "image") required String image, @JsonKey(name: "duration") required int duration, - @JsonKey(name: "state") required String state, + @JsonKey(name: "state") required DietState state, @JsonKey(name: "type") required DietType type, @JsonKey(name: "details") required String details, - @JsonKey(name: "daily_schedule") required List>> dailySchedule, + @JsonKey(name: "daily_schedule") required Map>> dailySchedule, @JsonKey(name: "author") required Author author, @JsonKey(name: "total_likes") required int totalLikes, @JsonKey(name: "total_views") required int totalViews, @@ -39,10 +39,30 @@ class Diet with _$Diet { factory Diet.fromJson(Map json) => _$DietFromJson(json); } +enum DietState { + @JsonKey(name: 'active') + active, + @JsonKey(name: 'draft') + draft, + @JsonKey(name: 'personal') + personal; + + String toString() { + switch (this) { + case DietState.active: + return 'active'; + case DietState.draft: + return 'draft'; + case DietState.personal: + return 'personal'; + } + } +} + @freezed class CreatedDiet with _$CreatedDiet { const factory CreatedDiet({ - @JsonKey(name: "id") required int id, + @JsonKey(name: "id") required int id, }) = _CreatedDiet; factory CreatedDiet.fromJson(Map json) => _$CreatedDietFromJson(json); @@ -51,9 +71,9 @@ class CreatedDiet with _$CreatedDiet { int parseDuration(dynamic duration) { if (duration is String) { return int.parse(duration); - }else { + } else { return duration; - } + } } @freezed @@ -74,9 +94,8 @@ class Author with _$Author { class DailySchedule with _$DailySchedule { const factory DailySchedule({ @JsonKey(name: "name") required String name, - @JsonKey(name: "uniq", includeToJson: false) required String uniq, @JsonKey(name: "portion") required Portion portion, - @JsonKey(name: "quantity") required int quantity, + @JsonKey(name: "quantity", fromJson: _parseQuantity) required int quantity, @JsonKey(includeToJson: false, includeFromJson: false) DailyNutrition? dailyNutrition, @JsonKey(name: 'product_nutritional_value_id', includeToJson: true, includeFromJson: false, includeIfNull: false) String? productNutritionalValueId, }) = _DailySchedule; @@ -84,8 +103,16 @@ class DailySchedule with _$DailySchedule { factory DailySchedule.fromJson(Map json) => _$DailyScheduleFromJson(json); } +int _parseQuantity(dynamic quantity) { + if (quantity is String) { + return int.parse(quantity); + } else { + return quantity; + } +} + enum Portion { - @JsonValue("grams") + @JsonValue("gr") GRAMS, @JsonValue("milliliters") MILLILITERS, diff --git a/lib/features/feed/models/diets_pagination_model.freezed.dart b/lib/features/feed/models/diets_pagination_model.freezed.dart index 37c410bc..cc185651 100644 --- a/lib/features/feed/models/diets_pagination_model.freezed.dart +++ b/lib/features/feed/models/diets_pagination_model.freezed.dart @@ -268,13 +268,13 @@ mixin _$Diet { @JsonKey(name: "duration") int get duration => throw _privateConstructorUsedError; @JsonKey(name: "state") - String get state => throw _privateConstructorUsedError; + DietState get state => throw _privateConstructorUsedError; @JsonKey(name: "type") DietType get type => throw _privateConstructorUsedError; @JsonKey(name: "details") String get details => throw _privateConstructorUsedError; @JsonKey(name: "daily_schedule") - List>> get dailySchedule => + Map>> get dailySchedule => throw _privateConstructorUsedError; @JsonKey(name: "author") Author get author => throw _privateConstructorUsedError; @@ -310,11 +310,11 @@ abstract class $DietCopyWith<$Res> { @JsonKey(name: "name") String name, @JsonKey(name: "image") String image, @JsonKey(name: "duration") int duration, - @JsonKey(name: "state") String state, + @JsonKey(name: "state") DietState state, @JsonKey(name: "type") DietType type, @JsonKey(name: "details") String details, @JsonKey(name: "daily_schedule") - List>> dailySchedule, + Map>> dailySchedule, @JsonKey(name: "author") Author author, @JsonKey(name: "total_likes") int totalLikes, @JsonKey(name: "total_views") int totalViews, @@ -378,7 +378,7 @@ class _$DietCopyWithImpl<$Res, $Val extends Diet> state: null == state ? _value.state : state // ignore: cast_nullable_to_non_nullable - as String, + as DietState, type: null == type ? _value.type : type // ignore: cast_nullable_to_non_nullable @@ -390,7 +390,7 @@ class _$DietCopyWithImpl<$Res, $Val extends Diet> dailySchedule: null == dailySchedule ? _value.dailySchedule : dailySchedule // ignore: cast_nullable_to_non_nullable - as List>>, + as Map>>, author: null == author ? _value.author : author // ignore: cast_nullable_to_non_nullable @@ -455,11 +455,11 @@ abstract class _$$DietImplCopyWith<$Res> implements $DietCopyWith<$Res> { @JsonKey(name: "name") String name, @JsonKey(name: "image") String image, @JsonKey(name: "duration") int duration, - @JsonKey(name: "state") String state, + @JsonKey(name: "state") DietState state, @JsonKey(name: "type") DietType type, @JsonKey(name: "details") String details, @JsonKey(name: "daily_schedule") - List>> dailySchedule, + Map>> dailySchedule, @JsonKey(name: "author") Author author, @JsonKey(name: "total_likes") int totalLikes, @JsonKey(name: "total_views") int totalViews, @@ -522,7 +522,7 @@ class __$$DietImplCopyWithImpl<$Res> state: null == state ? _value.state : state // ignore: cast_nullable_to_non_nullable - as String, + as DietState, type: null == type ? _value.type : type // ignore: cast_nullable_to_non_nullable @@ -534,7 +534,7 @@ class __$$DietImplCopyWithImpl<$Res> dailySchedule: null == dailySchedule ? _value._dailySchedule : dailySchedule // ignore: cast_nullable_to_non_nullable - as List>>, + as Map>>, author: null == author ? _value.author : author // ignore: cast_nullable_to_non_nullable @@ -579,7 +579,7 @@ class _$DietImpl implements _Diet { @JsonKey(name: "type") required this.type, @JsonKey(name: "details") required this.details, @JsonKey(name: "daily_schedule") - required final List>> dailySchedule, + required final Map>> dailySchedule, @JsonKey(name: "author") required this.author, @JsonKey(name: "total_likes") required this.totalLikes, @JsonKey(name: "total_views") required this.totalViews, @@ -608,20 +608,20 @@ class _$DietImpl implements _Diet { final int duration; @override @JsonKey(name: "state") - final String state; + final DietState state; @override @JsonKey(name: "type") final DietType type; @override @JsonKey(name: "details") final String details; - final List>> _dailySchedule; + final Map>> _dailySchedule; @override @JsonKey(name: "daily_schedule") - List>> get dailySchedule { - if (_dailySchedule is EqualUnmodifiableListView) return _dailySchedule; + Map>> get dailySchedule { + if (_dailySchedule is EqualUnmodifiableMapView) return _dailySchedule; // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_dailySchedule); + return EqualUnmodifiableMapView(_dailySchedule); } @override @@ -728,11 +728,11 @@ abstract class _Diet implements Diet { @JsonKey(name: "name") required final String name, @JsonKey(name: "image") required final String image, @JsonKey(name: "duration") required final int duration, - @JsonKey(name: "state") required final String state, + @JsonKey(name: "state") required final DietState state, @JsonKey(name: "type") required final DietType type, @JsonKey(name: "details") required final String details, @JsonKey(name: "daily_schedule") - required final List>> dailySchedule, + required final Map>> dailySchedule, @JsonKey(name: "author") required final Author author, @JsonKey(name: "total_likes") required final int totalLikes, @JsonKey(name: "total_views") required final int totalViews, @@ -758,7 +758,7 @@ abstract class _Diet implements Diet { int get duration; @override @JsonKey(name: "state") - String get state; + DietState get state; @override @JsonKey(name: "type") DietType get type; @@ -767,7 +767,7 @@ abstract class _Diet implements Diet { String get details; @override @JsonKey(name: "daily_schedule") - List>> get dailySchedule; + Map>> get dailySchedule; @override @JsonKey(name: "author") Author get author; @@ -1242,11 +1242,9 @@ DailySchedule _$DailyScheduleFromJson(Map json) { mixin _$DailySchedule { @JsonKey(name: "name") String get name => throw _privateConstructorUsedError; - @JsonKey(name: "uniq", includeToJson: false) - String get uniq => throw _privateConstructorUsedError; @JsonKey(name: "portion") Portion get portion => throw _privateConstructorUsedError; - @JsonKey(name: "quantity") + @JsonKey(name: "quantity", fromJson: _parseQuantity) int get quantity => throw _privateConstructorUsedError; @JsonKey(includeToJson: false, includeFromJson: false) DailyNutrition? get dailyNutrition => throw _privateConstructorUsedError; @@ -1275,9 +1273,8 @@ abstract class $DailyScheduleCopyWith<$Res> { @useResult $Res call( {@JsonKey(name: "name") String name, - @JsonKey(name: "uniq", includeToJson: false) String uniq, @JsonKey(name: "portion") Portion portion, - @JsonKey(name: "quantity") int quantity, + @JsonKey(name: "quantity", fromJson: _parseQuantity) int quantity, @JsonKey(includeToJson: false, includeFromJson: false) DailyNutrition? dailyNutrition, @JsonKey( @@ -1306,7 +1303,6 @@ class _$DailyScheduleCopyWithImpl<$Res, $Val extends DailySchedule> @override $Res call({ Object? name = null, - Object? uniq = null, Object? portion = null, Object? quantity = null, Object? dailyNutrition = freezed, @@ -1317,10 +1313,6 @@ class _$DailyScheduleCopyWithImpl<$Res, $Val extends DailySchedule> ? _value.name : name // ignore: cast_nullable_to_non_nullable as String, - uniq: null == uniq - ? _value.uniq - : uniq // ignore: cast_nullable_to_non_nullable - as String, portion: null == portion ? _value.portion : portion // ignore: cast_nullable_to_non_nullable @@ -1365,9 +1357,8 @@ abstract class _$$DailyScheduleImplCopyWith<$Res> @useResult $Res call( {@JsonKey(name: "name") String name, - @JsonKey(name: "uniq", includeToJson: false) String uniq, @JsonKey(name: "portion") Portion portion, - @JsonKey(name: "quantity") int quantity, + @JsonKey(name: "quantity", fromJson: _parseQuantity) int quantity, @JsonKey(includeToJson: false, includeFromJson: false) DailyNutrition? dailyNutrition, @JsonKey( @@ -1395,7 +1386,6 @@ class __$$DailyScheduleImplCopyWithImpl<$Res> @override $Res call({ Object? name = null, - Object? uniq = null, Object? portion = null, Object? quantity = null, Object? dailyNutrition = freezed, @@ -1406,10 +1396,6 @@ class __$$DailyScheduleImplCopyWithImpl<$Res> ? _value.name : name // ignore: cast_nullable_to_non_nullable as String, - uniq: null == uniq - ? _value.uniq - : uniq // ignore: cast_nullable_to_non_nullable - as String, portion: null == portion ? _value.portion : portion // ignore: cast_nullable_to_non_nullable @@ -1435,9 +1421,9 @@ class __$$DailyScheduleImplCopyWithImpl<$Res> class _$DailyScheduleImpl implements _DailySchedule { const _$DailyScheduleImpl( {@JsonKey(name: "name") required this.name, - @JsonKey(name: "uniq", includeToJson: false) required this.uniq, @JsonKey(name: "portion") required this.portion, - @JsonKey(name: "quantity") required this.quantity, + @JsonKey(name: "quantity", fromJson: _parseQuantity) + required this.quantity, @JsonKey(includeToJson: false, includeFromJson: false) this.dailyNutrition, @JsonKey( @@ -1454,13 +1440,10 @@ class _$DailyScheduleImpl implements _DailySchedule { @JsonKey(name: "name") final String name; @override - @JsonKey(name: "uniq", includeToJson: false) - final String uniq; - @override @JsonKey(name: "portion") final Portion portion; @override - @JsonKey(name: "quantity") + @JsonKey(name: "quantity", fromJson: _parseQuantity) final int quantity; @override @JsonKey(includeToJson: false, includeFromJson: false) @@ -1475,7 +1458,7 @@ class _$DailyScheduleImpl implements _DailySchedule { @override String toString() { - return 'DailySchedule(name: $name, uniq: $uniq, portion: $portion, quantity: $quantity, dailyNutrition: $dailyNutrition, productNutritionalValueId: $productNutritionalValueId)'; + return 'DailySchedule(name: $name, portion: $portion, quantity: $quantity, dailyNutrition: $dailyNutrition, productNutritionalValueId: $productNutritionalValueId)'; } @override @@ -1484,7 +1467,6 @@ class _$DailyScheduleImpl implements _DailySchedule { (other.runtimeType == runtimeType && other is _$DailyScheduleImpl && (identical(other.name, name) || other.name == name) && - (identical(other.uniq, uniq) || other.uniq == uniq) && (identical(other.portion, portion) || other.portion == portion) && (identical(other.quantity, quantity) || other.quantity == quantity) && @@ -1497,7 +1479,7 @@ class _$DailyScheduleImpl implements _DailySchedule { @JsonKey(includeFromJson: false, includeToJson: false) @override - int get hashCode => Object.hash(runtimeType, name, uniq, portion, quantity, + int get hashCode => Object.hash(runtimeType, name, portion, quantity, dailyNutrition, productNutritionalValueId); /// Create a copy of DailySchedule @@ -1519,9 +1501,9 @@ class _$DailyScheduleImpl implements _DailySchedule { abstract class _DailySchedule implements DailySchedule { const factory _DailySchedule( {@JsonKey(name: "name") required final String name, - @JsonKey(name: "uniq", includeToJson: false) required final String uniq, @JsonKey(name: "portion") required final Portion portion, - @JsonKey(name: "quantity") required final int quantity, + @JsonKey(name: "quantity", fromJson: _parseQuantity) + required final int quantity, @JsonKey(includeToJson: false, includeFromJson: false) final DailyNutrition? dailyNutrition, @JsonKey( @@ -1538,13 +1520,10 @@ abstract class _DailySchedule implements DailySchedule { @JsonKey(name: "name") String get name; @override - @JsonKey(name: "uniq", includeToJson: false) - String get uniq; - @override @JsonKey(name: "portion") Portion get portion; @override - @JsonKey(name: "quantity") + @JsonKey(name: "quantity", fromJson: _parseQuantity) int get quantity; @override @JsonKey(includeToJson: false, includeFromJson: false) diff --git a/lib/features/feed/models/diets_pagination_model.g.dart b/lib/features/feed/models/diets_pagination_model.g.dart index 7a54baa0..021fbfcf 100644 --- a/lib/features/feed/models/diets_pagination_model.g.dart +++ b/lib/features/feed/models/diets_pagination_model.g.dart @@ -29,16 +29,21 @@ _$DietImpl _$$DietImplFromJson(Map json) => _$DietImpl( name: json['name'] as String, image: json['image'] as String, duration: (json['duration'] as num).toInt(), - state: json['state'] as String, + state: $enumDecode(_$DietStateEnumMap, json['state']), type: DietType.fromJson(json['type'] as Map), details: json['details'] as String, - dailySchedule: (json['daily_schedule'] as List) - .map((e) => (e as List) - .map((e) => (e as List) - .map((e) => DailySchedule.fromJson(e as Map)) - .toList()) - .toList()) - .toList(), + dailySchedule: (json['daily_schedule'] as Map).map( + (k, e) => MapEntry( + int.parse(k), + (e as Map).map( + (k, e) => MapEntry( + int.parse(k), + (e as Map).map( + (k, e) => MapEntry(int.parse(k), + DailySchedule.fromJson(e as Map)), + )), + )), + ), author: Author.fromJson(json['author'] as Map), totalLikes: (json['total_likes'] as num).toInt(), totalViews: (json['total_views'] as num).toInt(), @@ -56,10 +61,13 @@ Map _$$DietImplToJson(_$DietImpl instance) => 'name': instance.name, 'image': instance.image, 'duration': instance.duration, - 'state': instance.state, + 'state': _$DietStateEnumMap[instance.state]!, 'type': instance.type, 'details': instance.details, - 'daily_schedule': instance.dailySchedule, + 'daily_schedule': instance.dailySchedule.map((k, e) => MapEntry( + k.toString(), + e.map((k, e) => MapEntry( + k.toString(), e.map((k, e) => MapEntry(k.toString(), e)))))), 'author': instance.author, 'total_likes': instance.totalLikes, 'total_views': instance.totalViews, @@ -69,6 +77,12 @@ Map _$$DietImplToJson(_$DietImpl instance) => 'daily_nutrition': instance.dailyNutrition, }; +const _$DietStateEnumMap = { + DietState.active: 'active', + DietState.draft: 'draft', + DietState.personal: 'personal', +}; + _$CreatedDietImpl _$$CreatedDietImplFromJson(Map json) => _$CreatedDietImpl( id: (json['id'] as num).toInt(), @@ -101,9 +115,8 @@ Map _$$AuthorImplToJson(_$AuthorImpl instance) => _$DailyScheduleImpl _$$DailyScheduleImplFromJson(Map json) => _$DailyScheduleImpl( name: json['name'] as String, - uniq: json['uniq'] as String, portion: $enumDecode(_$PortionEnumMap, json['portion']), - quantity: (json['quantity'] as num).toInt(), + quantity: _parseQuantity(json['quantity']), ); Map _$$DailyScheduleImplToJson(_$DailyScheduleImpl instance) => @@ -116,7 +129,7 @@ Map _$$DailyScheduleImplToJson(_$DailyScheduleImpl instance) => }; const _$PortionEnumMap = { - Portion.GRAMS: 'grams', + Portion.GRAMS: 'gr', Portion.MILLILITERS: 'milliliters', Portion.PCS: 'pcs', }; diff --git a/lib/features/feed/presentation/bloc/consultants_cubit/consultants_cubit.dart b/lib/features/feed/presentation/bloc/consultants_cubit/consultants_cubit.dart index 9b30f4cc..cf96b2a6 100644 --- a/lib/features/feed/presentation/bloc/consultants_cubit/consultants_cubit.dart +++ b/lib/features/feed/presentation/bloc/consultants_cubit/consultants_cubit.dart @@ -1,3 +1,5 @@ +import 'dart:developer'; + import 'package:eatvio_app/features/feed/presentation/bloc/consultants_cubit/consultants_state.dart'; import 'package:eatvio_app/features/feed/repositories/feed_repository.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -18,7 +20,8 @@ class ConsultantsCubit extends Cubit { isLoading: false, ), ); - } catch (e) { + } catch (e, st) { + log(e.toString(), stackTrace: st); emit(ConsultantsState.error(errorMessage: e.toString())); } } diff --git a/lib/features/feed/presentation/bloc/diets_cubit/diets_cubit.dart b/lib/features/feed/presentation/bloc/diets_cubit/diets_cubit.dart index 1f585375..de59de7e 100644 --- a/lib/features/feed/presentation/bloc/diets_cubit/diets_cubit.dart +++ b/lib/features/feed/presentation/bloc/diets_cubit/diets_cubit.dart @@ -107,9 +107,10 @@ class DietsCubit extends Cubit { errorMessage: null, ), ); - } catch (e) { + } catch (e, st) { + log(e.toString(), stackTrace: st); emit( - state.copyWith(errorMessage: e.toString()), + state.copyWith(isLoading: false), ); } } @@ -143,14 +144,13 @@ class DietsCubit extends Cubit { ); } } catch (e) { - emit(state.copyWith(errorMessage: e.toString())); + emit(state.copyWith(isLoading: false)); } } } Future likeDiet(int id, int index) async { - final currentDiets = state.dietsList; - // if (currentDiets.isEmpty) return; + final currentDiets = state.dietsList; final dietsForModification = [...currentDiets]; final currentDiet = currentDiets[index]; if (currentDiet.wasLikedByMe) { diff --git a/lib/features/feed/presentation/pages/feed_screen.dart b/lib/features/feed/presentation/pages/feed_screen.dart index 0dbe0b31..4527c0af 100644 --- a/lib/features/feed/presentation/pages/feed_screen.dart +++ b/lib/features/feed/presentation/pages/feed_screen.dart @@ -1,5 +1,6 @@ import 'package:easy_debounce/easy_debounce.dart'; import 'package:eatvio_app/core/widgets/add_item_button.dart'; +import 'package:eatvio_app/core/widgets/bottom_sheet_with_blur.dart'; import 'package:eatvio_app/core/widgets/custom_search_bar.dart'; import 'package:eatvio_app/features/create_diet/presentation/create_diet_screen.dart'; import 'package:eatvio_app/features/create_plan/presentation/create_plan_screen.dart'; @@ -14,6 +15,7 @@ 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'; +import 'package:eatvio_app/features/feed/presentation/widgets/success_published_diet_bottom_sheet.dart'; import 'package:eatvio_app/features/feed/presentation/widgets/tags_modal_widget.dart'; import 'package:eatvio_app/features/feed/presentation/bloc/plans_cubit/plans_cubit.dart'; import 'package:eatvio_app/core/locator_service.dart'; @@ -25,7 +27,6 @@ import 'package:go_router/go_router.dart'; import '../widgets/recipes.dart'; - class FeedScreen extends StatelessWidget { const FeedScreen({super.key}); @@ -260,10 +261,23 @@ class _FeedScreenContentState extends State { onTap: () => GoRouter.of(context).pushNamed(RecipeCreateScreen.name), ), 2 => AddItemButton( - onTap: state.profile?.role == 'consultant' ? () => GoRouter.of(context).pushNamed(CreateDietScreen.name) : null, + onTap: state.profile?.roles.contains('consultant') ?? false + ? () async { + final result = await GoRouter.of(context).pushNamed(CreateDietScreen.name); + if (result != null) { + showBottomSheetWithBlur( + userRootNavigator: true, + context: context, + child: SuccessPublishedDietBottomSheet( + title: result, + ), + ); + } + } + : null, ), 3 => AddItemButton( - onTap: state.profile?.role == 'consultant' ? () => GoRouter.of(context).pushNamed(CreatePlanScreen.name) : null, + onTap: state.profile?.roles.contains('consultant') ?? false ? () => GoRouter.of(context).pushNamed(CreatePlanScreen.name) : null, ), _ => AddItemButton() }, diff --git a/lib/features/feed/presentation/widgets/diets.dart b/lib/features/feed/presentation/widgets/diets.dart index 4d1ca2f3..b698065b 100644 --- a/lib/features/feed/presentation/widgets/diets.dart +++ b/lib/features/feed/presentation/widgets/diets.dart @@ -63,4 +63,4 @@ class Diets extends StatelessWidget { ), ); } -} \ No newline at end of file +} diff --git a/lib/features/feed/presentation/widgets/success_published_diet_bottom_sheet.dart b/lib/features/feed/presentation/widgets/success_published_diet_bottom_sheet.dart new file mode 100644 index 00000000..eade82c2 --- /dev/null +++ b/lib/features/feed/presentation/widgets/success_published_diet_bottom_sheet.dart @@ -0,0 +1,54 @@ +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/bottom_sheet_handler_bar.dart'; +import 'package:eatvio_app/core/widgets/custom_outlined_button.dart'; +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; + +class SuccessPublishedDietBottomSheet extends StatelessWidget { + const SuccessPublishedDietBottomSheet({super.key, required this.title}); + final String title; + @override + Widget build(BuildContext context) { + return Column( + children: [ + SizedBox( + height: 20, + ), + BottomSheetHandlerBar(), + SizedBox( + height: 20, + ), + Text( + title, + style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black), + ), + Text( + 'опубликованна', + style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black), + ), + SizedBox( + height: 20, + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: CustomOutlinedButton( + onPressed: () { + GoRouter.of(context).pop(); + }, + text: 'Хорошо', + height: 52, + buttonTextStyle: TextStyles.textSSemibold.copyWith(color: appTheme.whiteA700), + buttonStyle: ButtonStyle( + side: WidgetStatePropertyAll(BorderSide.none), + backgroundColor: WidgetStatePropertyAll(appTheme.blueA400), + ), + ), + ), + SizedBox( + height: 20, + ), + ], + ); + } +} diff --git a/lib/features/glossary/services/glossary_service.g.dart b/lib/features/glossary/services/glossary_service.g.dart index bd3a1137..cd6584f6 100644 --- a/lib/features/glossary/services/glossary_service.g.dart +++ b/lib/features/glossary/services/glossary_service.g.dart @@ -9,11 +9,7 @@ part of 'glossary_service.dart'; // ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers,unused_element,unnecessary_string_interpolations class _GlossaryService implements GlossaryService { - _GlossaryService( - this._dio, { - this.baseUrl, - this.errorLogger, - }); + _GlossaryService(this._dio, {this.baseUrl, this.errorLogger}); final Dio _dio; @@ -22,28 +18,23 @@ class _GlossaryService implements GlossaryService { final ParseErrorLogger? errorLogger; @override - Future getGlossaryByQuery( - {required String query}) async { + Future getGlossaryByQuery({ + required String query, + }) async { final _extra = {}; final queryParameters = {r'search': query}; final _headers = {}; const Map? _data = null; - final _options = _setStreamType(Options( - method: 'GET', - headers: _headers, - extra: _extra, - ) - .compose( - _dio.options, - '/api/glossaries/search', - queryParameters: queryParameters, - data: _data, - ) - .copyWith( - baseUrl: _combineBaseUrls( - _dio.options.baseUrl, - baseUrl, - ))); + final _options = _setStreamType( + Options(method: 'GET', headers: _headers, extra: _extra) + .compose( + _dio.options, + '/api/glossaries/search', + queryParameters: queryParameters, + data: _data, + ) + .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)), + ); final _result = await _dio.fetch>(_options); late GlossaryPaginationModel _value; try { @@ -68,10 +59,7 @@ class _GlossaryService implements GlossaryService { return requestOptions; } - String _combineBaseUrls( - String dioBaseUrl, - String? baseUrl, - ) { + String _combineBaseUrls(String dioBaseUrl, String? baseUrl) { if (baseUrl == null || baseUrl.trim().isEmpty) { return dioBaseUrl; } diff --git a/lib/features/my_diets/presentation/cubit/my_diets_cubit.dart b/lib/features/my_diets/presentation/cubit/my_diets_cubit.dart index 7a998163..09753d80 100644 --- a/lib/features/my_diets/presentation/cubit/my_diets_cubit.dart +++ b/lib/features/my_diets/presentation/cubit/my_diets_cubit.dart @@ -1,5 +1,8 @@ +import 'dart:developer'; + import 'package:easy_debounce/easy_debounce.dart'; import 'package:eatvio_app/core/const.dart'; +import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart'; import 'package:eatvio_app/features/my_diets/presentation/cubit/my_diets_state.dart'; import 'package:eatvio_app/features/my_diets/repositories/my_diets_repository.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -28,6 +31,7 @@ class MyDietsCubit extends Cubit { 'name', 'name:desc', ]; + static const List _durationValues = [null, ...durationValues]; static const List _types = [null, ...dietTypeValues]; @@ -39,7 +43,7 @@ class MyDietsCubit extends Cubit { EasyDebounce.debounce( 'loadDiets', Duration(seconds: 1), - () => loadDiets( + () => loadCurrentDiets( query: _activeQuery, ), ); @@ -48,7 +52,7 @@ class MyDietsCubit extends Cubit { EasyDebounce.debounce( 'loadDiets', Duration(seconds: 1), - () => loadDiets( + () => loadCurrentDiets( query: _draftQuery, ), ); @@ -57,7 +61,7 @@ class MyDietsCubit extends Cubit { EasyDebounce.debounce( 'loadDiets', Duration(seconds: 1), - () => loadDiets( + () => loadCurrentDiets( query: _personalQuery, ), ); @@ -70,7 +74,7 @@ class MyDietsCubit extends Cubit { EasyDebounce.debounce( 'loadDiets', Duration(seconds: 1), - () => loadDiets( + () => loadCurrentDiets( query: _activeQuery, ), ); @@ -79,7 +83,7 @@ class MyDietsCubit extends Cubit { EasyDebounce.debounce( 'loadDiets', Duration(seconds: 1), - () => loadDiets( + () => loadCurrentDiets( query: _draftQuery, ), ); @@ -88,7 +92,7 @@ class MyDietsCubit extends Cubit { EasyDebounce.debounce( 'loadDiets', Duration(seconds: 1), - () => loadDiets( + () => loadCurrentDiets( query: _personalQuery, ), ); @@ -101,7 +105,7 @@ class MyDietsCubit extends Cubit { EasyDebounce.debounce( 'loadDiets', Duration(seconds: 1), - () => loadDiets( + () => loadCurrentDiets( query: _activeQuery, ), ); @@ -110,7 +114,7 @@ class MyDietsCubit extends Cubit { EasyDebounce.debounce( 'loadDiets', Duration(seconds: 1), - () => loadDiets( + () => loadCurrentDiets( query: _draftQuery, ), ); @@ -119,7 +123,7 @@ class MyDietsCubit extends Cubit { EasyDebounce.debounce( 'loadDiets', Duration(seconds: 1), - () => loadDiets( + () => loadCurrentDiets( query: _personalQuery, ), ); @@ -128,9 +132,34 @@ class MyDietsCubit extends Cubit { void changeSelectedStateIndex(int index) { emit(state.copyWith(selectedStateIndex: index)); + if (state.activeDiets.isEmpty) { + EasyDebounce.debounce( + 'loadActiveDiets', + Duration(seconds: 1), + () => loadCurrentDiets( + query: _activeQuery, + ), + ); + } else if (state.draftDiets.isEmpty) { + EasyDebounce.debounce( + 'loadDraftDiets', + Duration(seconds: 1), + () => loadCurrentDiets( + query: _draftQuery, + ), + ); + } else if (state.personalDiets.isEmpty) { + EasyDebounce.debounce( + 'loadPersonalDiets', + Duration(seconds: 1), + () => loadCurrentDiets( + query: _personalQuery, + ), + ); + } } - Future loadDiets({ + Future loadCurrentDiets({ String? query, }) async { switch (state.selectedStateIndex) { @@ -160,11 +189,13 @@ class MyDietsCubit extends Cubit { final type = _types[state.activeFilterState.typeIndex]; final duration = _durationValues[state.activeFilterState.durationIndex]; final sorting = _sortingValues[state.activeFilterState.sortingIndex]; + final dietState = DietState.active; final dietsPagination = await myDietsRepository.getDiets( query: query, types: type, duration: duration, sorting: sorting, + state: dietState.toString(), ); lastActivePage = dietsPagination.meta.lastPage; emit( @@ -174,7 +205,8 @@ class MyDietsCubit extends Cubit { error: null, ), ); - } catch (e) { + } catch (e, st) { + log(e.toString(), stackTrace: st); emit( state.copyWith(error: e.toString()), ); @@ -187,11 +219,13 @@ class MyDietsCubit extends Cubit { final type = _types[state.draftFilterState.typeIndex]; final duration = _durationValues[state.draftFilterState.durationIndex]; final sorting = _sortingValues[state.draftFilterState.sortingIndex]; + final dietState = DietState.draft; final dietsPagination = await myDietsRepository.getDiets( query: query, types: type, duration: duration, sorting: sorting, + state: dietState.toString(), ); lastActivePage = dietsPagination.meta.lastPage; emit( @@ -214,11 +248,13 @@ class MyDietsCubit extends Cubit { final type = _types[state.personalFilterState.typeIndex]; final duration = _durationValues[state.personalFilterState.durationIndex]; final sorting = _sortingValues[state.personalFilterState.sortingIndex]; + final dietState = DietState.personal; final dietsPagination = await myDietsRepository.getDiets( query: query, types: type, duration: duration, sorting: sorting, + state: dietState.toString(), ); lastActivePage = dietsPagination.meta.lastPage; emit( diff --git a/lib/features/my_diets/presentation/my_diets_screen.dart b/lib/features/my_diets/presentation/my_diets_screen.dart index 65ac8441..a58f3bdc 100644 --- a/lib/features/my_diets/presentation/my_diets_screen.dart +++ b/lib/features/my_diets/presentation/my_diets_screen.dart @@ -33,7 +33,7 @@ class MyDietsScreenContent extends StatelessWidget { final activeDietsScrollController = ScrollController(); final draftDietsScrollController = ScrollController(); final personalDietsScrollController = ScrollController(); - + final activeDietsSearchController = SearchController(); final draftDietsSearchController = SearchController(); final personalDietsSearchController = SearchController(); @@ -111,7 +111,7 @@ class MyDietsScreenContent extends StatelessWidget { 'searchDiets', Duration(milliseconds: 500), () { - context.read().loadDiets(query: query == '' ? null : query); + context.read().loadCurrentDiets(query: query == '' ? null : query); }, ); }, @@ -133,7 +133,7 @@ class MyDietsScreenContent extends StatelessWidget { ]; }, body: RefreshIndicator( - onRefresh: () => context.read().loadDiets(), + onRefresh: () => context.read().loadCurrentDiets(), child: BlocBuilder( builder: (BuildContext context, MyDietsState state) { if (state.isLoading) { diff --git a/lib/features/my_diets/presentation/widgets/my_diet_widget.dart b/lib/features/my_diets/presentation/widgets/my_diet_widget.dart index d173117e..ff8d060a 100644 --- a/lib/features/my_diets/presentation/widgets/my_diet_widget.dart +++ b/lib/features/my_diets/presentation/widgets/my_diet_widget.dart @@ -6,6 +6,7 @@ import 'package:eatvio_app/core/widgets/bottom_sheet_handler_bar.dart'; import 'package:eatvio_app/core/widgets/bottom_sheet_with_blur.dart'; import 'package:eatvio_app/core/widgets/custom_outlined_button.dart'; import 'package:eatvio_app/core/widgets/info_row.dart'; +import 'package:eatvio_app/features/create_diet/presentation/create_diet_screen.dart'; import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart'; import 'package:eatvio_app/features/my_diets/presentation/cubit/my_diets_cubit.dart'; import 'package:eatvio_app/features/my_diets/presentation/cubit/my_diets_state.dart'; @@ -110,6 +111,13 @@ class _MyDietWidgetState extends State { children: [ Expanded( child: CustomOutlinedButton( + onPressed: () { + if (state.selectedStateIndex == 1) { + } else if (state.selectedStateIndex == 2) { + } else { + GoRouter.of(context).pushNamed(CreateDietScreen.name, extra: widget.diet); + } + }, text: switch (state.selectedStateIndex) { 1 => s.publish, 2 => s.toOffer, @@ -160,6 +168,12 @@ class _MyDietWidgetState extends State { children: [ Expanded( child: CustomOutlinedButton( + onPressed: () { + if (state.selectedStateIndex == 0) { + } else { + GoRouter.of(context).pushNamed(CreateDietScreen.name, extra: widget.diet); + } + }, text: switch (state.selectedStateIndex) { 0 => s.removeFromActive, _ => s.edit, @@ -178,51 +192,49 @@ class _MyDietWidgetState extends State { onPressed: () async { final result = await context.read().deleteDiet(widget.diet.id); if (result) - Future.delayed(Duration(seconds: 3), () { - showBottomSheetWithBlur( - context: context, - child: Column( - children: [ - SizedBox( - height: 20, - ), - BottomSheetHandlerBar(), - SizedBox( - height: 20, - ), - Text( - '${widget.diet.name}', - style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black), - ), - Text( - s.deleted, - style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black), - ), - SizedBox( - height: 20, - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: CustomOutlinedButton( - onPressed: () { - context.pop(); - }, - text: 'Хорошо', - height: 52, - buttonTextStyle: TextStyles.textSSemibold.copyWith(color: appTheme.whiteA700), - buttonStyle: ButtonStyle( - side: WidgetStatePropertyAll(BorderSide.none), - backgroundColor: WidgetStatePropertyAll(appTheme.blueA400), - ), + showBottomSheetWithBlur( + context: context, + child: Column( + children: [ + SizedBox( + height: 20, + ), + BottomSheetHandlerBar(), + SizedBox( + height: 20, + ), + Text( + '${widget.diet.name}', + style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black), + ), + Text( + s.deleted, + style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black), + ), + SizedBox( + height: 20, + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: CustomOutlinedButton( + onPressed: () { + context.pop(); + }, + text: 'Хорошо', + height: 52, + buttonTextStyle: TextStyles.textSSemibold.copyWith(color: appTheme.whiteA700), + buttonStyle: ButtonStyle( + side: WidgetStatePropertyAll(BorderSide.none), + backgroundColor: WidgetStatePropertyAll(appTheme.blueA400), ), ), - SizedBox( - height: 20, - ), - ], - ), - ); - }); + ), + SizedBox( + height: 20, + ), + ], + ), + ); }, height: 52, width: 52, diff --git a/lib/features/my_diets/repositories/my_diets_repository.dart b/lib/features/my_diets/repositories/my_diets_repository.dart index 8c15a546..e3ec0dd1 100644 --- a/lib/features/my_diets/repositories/my_diets_repository.dart +++ b/lib/features/my_diets/repositories/my_diets_repository.dart @@ -20,6 +20,7 @@ class MyDietsRepository { String? types, int? duration, String? sorting, + String? state, }) async { try { return (await myDietsService.getDiets( @@ -28,6 +29,7 @@ class MyDietsRepository { types: types, duration: duration, sorting: sorting, + state: state, )); } catch (e) { rethrow; diff --git a/lib/features/my_diets/services/my_diets_service.dart b/lib/features/my_diets/services/my_diets_service.dart index 3a78d25e..41e3174f 100644 --- a/lib/features/my_diets/services/my_diets_service.dart +++ b/lib/features/my_diets/services/my_diets_service.dart @@ -12,13 +12,14 @@ abstract class MyDietsService { @GET('/api/profile/my_diets') Future getDiets({ @Query('page') required int page, - @Query('filters[name][\$startsWith]') String? query, + @Query('filters[name][\$startsWith]') String? query, + @Query('filters[state][\$eq]') String? state, @Query('filters[type][\$contains]') String? types, @Query('filters[duration][\$eq]') int? duration, @Query('sort') String? sorting, }); - @POST('/api/diets/delete/{dietId}') + @DELETE('/api/diets/delete/{dietId}') Future deleteDiet({ @Path() required int dietId, }); diff --git a/lib/features/my_diets/services/my_diets_service.g.dart b/lib/features/my_diets/services/my_diets_service.g.dart index f9244073..4c2ccf28 100644 --- a/lib/features/my_diets/services/my_diets_service.g.dart +++ b/lib/features/my_diets/services/my_diets_service.g.dart @@ -21,6 +21,7 @@ class _MyDietsService implements MyDietsService { Future getDiets({ required int page, String? query, + String? state, String? types, int? duration, String? sorting, @@ -29,6 +30,7 @@ class _MyDietsService implements MyDietsService { final queryParameters = { r'page': page, r'filters[name][$startsWith]': query, + r'filters[state][$eq]': state, r'filters[type][$contains]': types, r'filters[duration][$eq]': duration, r'sort': sorting, @@ -64,7 +66,7 @@ class _MyDietsService implements MyDietsService { final _headers = {}; const Map? _data = null; final _options = _setStreamType( - Options(method: 'POST', headers: _headers, extra: _extra) + Options(method: 'DELETE', headers: _headers, extra: _extra) .compose( _dio.options, '/api/diets/delete/${dietId}', diff --git a/lib/features/profile/models/profile_model.dart b/lib/features/profile/models/profile_model.dart index b6debe84..93c61635 100644 --- a/lib/features/profile/models/profile_model.dart +++ b/lib/features/profile/models/profile_model.dart @@ -18,7 +18,7 @@ class ProfileModel with _$ProfileModel { @JsonKey(name: 'is_entity_subscribed_by_me') required bool isEntitySubscribedByMe, @JsonKey(name: "slug") required String slug, @JsonKey(name: "profile_photo_url") required String profilePhotoUrl, - @JsonKey(name: "role") required String role, + @JsonKey(name: "roles") required List roles, @JsonKey(name: "badges") required List badges, @JsonKey(name: "subscriptions") required List subscriptions, @JsonKey(name: "subscribers") required List subscribers, diff --git a/lib/features/profile/models/profile_model.freezed.dart b/lib/features/profile/models/profile_model.freezed.dart index c46e1027..0f3eee2c 100644 --- a/lib/features/profile/models/profile_model.freezed.dart +++ b/lib/features/profile/models/profile_model.freezed.dart @@ -42,8 +42,8 @@ mixin _$ProfileModel { String get slug => throw _privateConstructorUsedError; @JsonKey(name: "profile_photo_url") String get profilePhotoUrl => throw _privateConstructorUsedError; - @JsonKey(name: "role") - String get role => throw _privateConstructorUsedError; + @JsonKey(name: "roles") + List get roles => throw _privateConstructorUsedError; @JsonKey(name: "badges") List get badges => throw _privateConstructorUsedError; @JsonKey(name: "subscriptions") @@ -86,7 +86,7 @@ abstract class $ProfileModelCopyWith<$Res> { @JsonKey(name: 'is_entity_subscribed_by_me') bool isEntitySubscribedByMe, @JsonKey(name: "slug") String slug, @JsonKey(name: "profile_photo_url") String profilePhotoUrl, - @JsonKey(name: "role") String role, + @JsonKey(name: "roles") List roles, @JsonKey(name: "badges") List badges, @JsonKey(name: "subscriptions") List subscriptions, @JsonKey(name: "subscribers") List subscribers, @@ -123,7 +123,7 @@ class _$ProfileModelCopyWithImpl<$Res, $Val extends ProfileModel> Object? isEntitySubscribedByMe = null, Object? slug = null, Object? profilePhotoUrl = null, - Object? role = null, + Object? roles = null, Object? badges = null, Object? subscriptions = null, Object? subscribers = null, @@ -175,10 +175,10 @@ class _$ProfileModelCopyWithImpl<$Res, $Val extends ProfileModel> ? _value.profilePhotoUrl : profilePhotoUrl // ignore: cast_nullable_to_non_nullable as String, - role: null == role - ? _value.role - : role // ignore: cast_nullable_to_non_nullable - as String, + roles: null == roles + ? _value.roles + : roles // ignore: cast_nullable_to_non_nullable + as List, badges: null == badges ? _value.badges : badges // ignore: cast_nullable_to_non_nullable @@ -237,7 +237,7 @@ abstract class _$$ProfileModelImplCopyWith<$Res> @JsonKey(name: 'is_entity_subscribed_by_me') bool isEntitySubscribedByMe, @JsonKey(name: "slug") String slug, @JsonKey(name: "profile_photo_url") String profilePhotoUrl, - @JsonKey(name: "role") String role, + @JsonKey(name: "roles") List roles, @JsonKey(name: "badges") List badges, @JsonKey(name: "subscriptions") List subscriptions, @JsonKey(name: "subscribers") List subscribers, @@ -273,7 +273,7 @@ class __$$ProfileModelImplCopyWithImpl<$Res> Object? isEntitySubscribedByMe = null, Object? slug = null, Object? profilePhotoUrl = null, - Object? role = null, + Object? roles = null, Object? badges = null, Object? subscriptions = null, Object? subscribers = null, @@ -325,10 +325,10 @@ class __$$ProfileModelImplCopyWithImpl<$Res> ? _value.profilePhotoUrl : profilePhotoUrl // ignore: cast_nullable_to_non_nullable as String, - role: null == role - ? _value.role - : role // ignore: cast_nullable_to_non_nullable - as String, + roles: null == roles + ? _value._roles + : roles // ignore: cast_nullable_to_non_nullable + as List, badges: null == badges ? _value._badges : badges // ignore: cast_nullable_to_non_nullable @@ -369,7 +369,7 @@ class _$ProfileModelImpl implements _ProfileModel { required this.isEntitySubscribedByMe, @JsonKey(name: "slug") required this.slug, @JsonKey(name: "profile_photo_url") required this.profilePhotoUrl, - @JsonKey(name: "role") required this.role, + @JsonKey(name: "roles") required final List roles, @JsonKey(name: "badges") required final List badges, @JsonKey(name: "subscriptions") required final List subscriptions, @@ -378,7 +378,8 @@ class _$ProfileModelImpl implements _ProfileModel { @JsonKey(name: "education_resource") this.educationResource, @JsonKey(name: "cert_photos") final List? diplomasAndCertificates}) - : _badges = badges, + : _roles = roles, + _badges = badges, _subscriptions = subscriptions, _subscribers = subscribers, _diplomasAndCertificates = diplomasAndCertificates; @@ -419,9 +420,15 @@ class _$ProfileModelImpl implements _ProfileModel { @override @JsonKey(name: "profile_photo_url") final String profilePhotoUrl; + final List _roles; @override - @JsonKey(name: "role") - final String role; + @JsonKey(name: "roles") + List get roles { + if (_roles is EqualUnmodifiableListView) return _roles; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_roles); + } + final List _badges; @override @JsonKey(name: "badges") @@ -466,7 +473,7 @@ class _$ProfileModelImpl implements _ProfileModel { @override String toString() { - return 'ProfileModel(id: $id, name: $name, email: $email, ratingPlace: $ratingPlace, emailVerifiedAt: $emailVerifiedAt, createdAt: $createdAt, updatedAt: $updatedAt, isPublicProfile: $isPublicProfile, isEntitySubscribedByMe: $isEntitySubscribedByMe, slug: $slug, profilePhotoUrl: $profilePhotoUrl, role: $role, badges: $badges, subscriptions: $subscriptions, subscribers: $subscribers, educationResource: $educationResource, diplomasAndCertificates: $diplomasAndCertificates)'; + return 'ProfileModel(id: $id, name: $name, email: $email, ratingPlace: $ratingPlace, emailVerifiedAt: $emailVerifiedAt, createdAt: $createdAt, updatedAt: $updatedAt, isPublicProfile: $isPublicProfile, isEntitySubscribedByMe: $isEntitySubscribedByMe, slug: $slug, profilePhotoUrl: $profilePhotoUrl, roles: $roles, badges: $badges, subscriptions: $subscriptions, subscribers: $subscribers, educationResource: $educationResource, diplomasAndCertificates: $diplomasAndCertificates)'; } @override @@ -492,7 +499,7 @@ class _$ProfileModelImpl implements _ProfileModel { (identical(other.slug, slug) || other.slug == slug) && (identical(other.profilePhotoUrl, profilePhotoUrl) || other.profilePhotoUrl == profilePhotoUrl) && - (identical(other.role, role) || other.role == role) && + const DeepCollectionEquality().equals(other._roles, _roles) && const DeepCollectionEquality().equals(other._badges, _badges) && const DeepCollectionEquality() .equals(other._subscriptions, _subscriptions) && @@ -519,7 +526,7 @@ class _$ProfileModelImpl implements _ProfileModel { isEntitySubscribedByMe, slug, profilePhotoUrl, - role, + const DeepCollectionEquality().hash(_roles), const DeepCollectionEquality().hash(_badges), const DeepCollectionEquality().hash(_subscriptions), const DeepCollectionEquality().hash(_subscribers), @@ -556,7 +563,7 @@ abstract class _ProfileModel implements ProfileModel { required final bool isEntitySubscribedByMe, @JsonKey(name: "slug") required final String slug, @JsonKey(name: "profile_photo_url") required final String profilePhotoUrl, - @JsonKey(name: "role") required final String role, + @JsonKey(name: "roles") required final List roles, @JsonKey(name: "badges") required final List badges, @JsonKey(name: "subscriptions") required final List subscriptions, @@ -605,8 +612,8 @@ abstract class _ProfileModel implements ProfileModel { @JsonKey(name: "profile_photo_url") String get profilePhotoUrl; @override - @JsonKey(name: "role") - String get role; + @JsonKey(name: "roles") + List get roles; @override @JsonKey(name: "badges") List get badges; diff --git a/lib/features/profile/models/profile_model.g.dart b/lib/features/profile/models/profile_model.g.dart index c7d0d444..e07e74c6 100644 --- a/lib/features/profile/models/profile_model.g.dart +++ b/lib/features/profile/models/profile_model.g.dart @@ -25,7 +25,7 @@ _$ProfileModelImpl _$$ProfileModelImplFromJson(Map json) => isEntitySubscribedByMe: json['is_entity_subscribed_by_me'] as bool, slug: json['slug'] as String, profilePhotoUrl: json['profile_photo_url'] as String, - role: json['role'] as String, + roles: (json['roles'] as List).map((e) => e as String).toList(), badges: (json['badges'] as List) .map((e) => BadgeModel.fromJson(e as Map)) .toList(), @@ -58,7 +58,7 @@ Map _$$ProfileModelImplToJson(_$ProfileModelImpl instance) => 'is_entity_subscribed_by_me': instance.isEntitySubscribedByMe, 'slug': instance.slug, 'profile_photo_url': instance.profilePhotoUrl, - 'role': instance.role, + 'roles': instance.roles, 'badges': instance.badges, 'subscriptions': instance.subscriptions, 'subscribers': instance.subscribers, diff --git a/lib/features/profile/presentation/pages/profile_screen.dart b/lib/features/profile/presentation/pages/profile_screen.dart index 788a5f3f..0b8b7af6 100644 --- a/lib/features/profile/presentation/pages/profile_screen.dart +++ b/lib/features/profile/presentation/pages/profile_screen.dart @@ -81,7 +81,7 @@ class _ProfileScreenState extends State { } if (isFirstFetch) { canSub = !state.profile.isEntitySubscribedByMe; - if (state.profile.role == 'consultant') { + if (state.profile.roles.contains('consultant')) { plansCubit..getPlans(state.profile.slug); } isFirstFetch = false; @@ -127,7 +127,7 @@ class _ProfileScreenState extends State { crossAxisAlignment: CrossAxisAlignment.center, children: [ _header(state), - if (state.profile.role == 'consultant') + if (state.profile.roles.contains('consultant')) Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -192,10 +192,10 @@ class _ProfileScreenState extends State { ), _subscriptionsAndSubscribers(state), if (state.profile.badges.isNotEmpty) Padding(padding: EdgeInsets.only(bottom: 16), child: _badges(state)), - if (state.profile.role == 'consultant') _socialLinks(state), - if (state.profile.role == 'consultant') _education(state), - if (state.profile.role == 'consultant') _diplomasAndCertificates(state), - if (state.profile.role == 'consultant') _activePlans(state), + if (state.profile.roles.contains('consultant')) _socialLinks(state), + if (state.profile.roles.contains('consultant')) _education(state), + if (state.profile.roles.contains('consultant')) _diplomasAndCertificates(state), + if (state.profile.roles.contains('consultant')) _activePlans(state), _blogPosts(state), ], ), @@ -228,11 +228,11 @@ class _ProfileScreenState extends State { ), _subscriptionsAndSubscribers(state), if (state.profile.badges.isNotEmpty) Padding(padding: EdgeInsets.only(bottom: 16), child: _badges(state)), - if (state.profile.role == 'consultant') _socialLinks(state), - if (state.profile.role == 'consultant') _education(state), - if (state.profile.role == 'consultant') _diplomasAndCertificates(state), + if (state.profile.roles.contains('consultant')) _socialLinks(state), + if (state.profile.roles.contains('consultant')) _education(state), + if (state.profile.roles.contains('consultant')) _diplomasAndCertificates(state), Padding(padding: EdgeInsets.only(bottom: 16), child: _charts()), - if (state.profile.role == 'consultant') _activePlans(state), + if (state.profile.roles.contains('consultant')) _activePlans(state), _newBlogPostButton(), _blogPosts(state), ], @@ -242,7 +242,7 @@ class _ProfileScreenState extends State { } _activePlans(ProfileStateLoaded state) { - if (state.profile.role != 'consultant') { + if (!state.profile.roles.contains('consultant')) { return const SizedBox.shrink(); } return BlocBuilder( @@ -372,7 +372,7 @@ class _ProfileScreenState extends State { } _diplomasAndCertificates(ProfileStateLoaded state) { - if (state.profile.role != 'consultant' || state.profile.diplomasAndCertificates == null || state.profile.diplomasAndCertificates!.isEmpty) { + if (!state.profile.roles.contains('consultant') || state.profile.diplomasAndCertificates == null || state.profile.diplomasAndCertificates!.isEmpty) { return const SizedBox.shrink(); } return Padding( @@ -456,7 +456,7 @@ class _ProfileScreenState extends State { } _education(ProfileStateLoaded state) { - if (state.profile.role != 'consultant' || + if (!state.profile.roles.contains('consultant') || state.profile.educationResource?.completedCourses == null || state.profile.educationResource?.completedCourses?.length == 0) { return const SizedBox.shrink(); @@ -549,7 +549,7 @@ class _ProfileScreenState extends State { } _socialLinks(ProfileStateLoaded state) { - if (state.profile.role != 'consultant') { + if (!state.profile.roles.contains('consultant')) { return const SizedBox.shrink(); } return Padding( diff --git a/lib/features/recipes/entities/recipe_create_entity.dart b/lib/features/recipes/entities/recipe_create_entity.dart index 92f7d642..6ca01db3 100644 --- a/lib/features/recipes/entities/recipe_create_entity.dart +++ b/lib/features/recipes/entities/recipe_create_entity.dart @@ -84,8 +84,10 @@ class RecipeCreateEntity with _$RecipeCreateEntity { TypeOfRecipe? typeOfRecipe, int? countOfPortions, double? energyInOnePortion, - String? totalCookTime, // hh:mm - String? activeCookTime, // hh:mm + int? totalCookHours, + int? totalCookMinutes, + int? activeCookHours, + int? activeCookMinutes, String? description, File? image, String? videoUrl, @@ -99,8 +101,8 @@ class RecipeCreateEntity with _$RecipeCreateEntity { 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)), + totalCookingTime: ((totalCookHours ?? 0) * 60) + (totalCookMinutes ?? 0), + activeCookTime: ((activeCookHours ?? 0) * 60) + (activeCookMinutes ?? 0), description: description, image: image?.path, video_url: videoUrl, diff --git a/lib/features/recipes/entities/recipe_create_entity.freezed.dart b/lib/features/recipes/entities/recipe_create_entity.freezed.dart index 57535cc7..c5f4178d 100644 --- a/lib/features/recipes/entities/recipe_create_entity.freezed.dart +++ b/lib/features/recipes/entities/recipe_create_entity.freezed.dart @@ -20,8 +20,10 @@ mixin _$RecipeCreateEntity { TypeOfRecipe? get typeOfRecipe => throw _privateConstructorUsedError; int? get countOfPortions => throw _privateConstructorUsedError; double? get energyInOnePortion => throw _privateConstructorUsedError; - String? get totalCookTime => throw _privateConstructorUsedError; // hh:mm - String? get activeCookTime => throw _privateConstructorUsedError; // hh:mm + int? get totalCookHours => throw _privateConstructorUsedError; + int? get totalCookMinutes => throw _privateConstructorUsedError; + int? get activeCookHours => throw _privateConstructorUsedError; + int? get activeCookMinutes => throw _privateConstructorUsedError; String? get description => throw _privateConstructorUsedError; File? get image => throw _privateConstructorUsedError; String? get videoUrl => throw _privateConstructorUsedError; @@ -47,8 +49,10 @@ abstract class $RecipeCreateEntityCopyWith<$Res> { TypeOfRecipe? typeOfRecipe, int? countOfPortions, double? energyInOnePortion, - String? totalCookTime, - String? activeCookTime, + int? totalCookHours, + int? totalCookMinutes, + int? activeCookHours, + int? activeCookMinutes, String? description, File? image, String? videoUrl, @@ -76,8 +80,10 @@ class _$RecipeCreateEntityCopyWithImpl<$Res, $Val extends RecipeCreateEntity> Object? typeOfRecipe = freezed, Object? countOfPortions = freezed, Object? energyInOnePortion = freezed, - Object? totalCookTime = freezed, - Object? activeCookTime = freezed, + Object? totalCookHours = freezed, + Object? totalCookMinutes = freezed, + Object? activeCookHours = freezed, + Object? activeCookMinutes = freezed, Object? description = freezed, Object? image = freezed, Object? videoUrl = freezed, @@ -102,14 +108,22 @@ class _$RecipeCreateEntityCopyWithImpl<$Res, $Val extends RecipeCreateEntity> ? _value.energyInOnePortion : energyInOnePortion // ignore: cast_nullable_to_non_nullable as double?, - totalCookTime: freezed == totalCookTime - ? _value.totalCookTime - : totalCookTime // ignore: cast_nullable_to_non_nullable - as String?, - activeCookTime: freezed == activeCookTime - ? _value.activeCookTime - : activeCookTime // ignore: cast_nullable_to_non_nullable - as String?, + totalCookHours: freezed == totalCookHours + ? _value.totalCookHours + : totalCookHours // ignore: cast_nullable_to_non_nullable + as int?, + totalCookMinutes: freezed == totalCookMinutes + ? _value.totalCookMinutes + : totalCookMinutes // ignore: cast_nullable_to_non_nullable + as int?, + activeCookHours: freezed == activeCookHours + ? _value.activeCookHours + : activeCookHours // ignore: cast_nullable_to_non_nullable + as int?, + activeCookMinutes: freezed == activeCookMinutes + ? _value.activeCookMinutes + : activeCookMinutes // ignore: cast_nullable_to_non_nullable + as int?, description: freezed == description ? _value.description : description // ignore: cast_nullable_to_non_nullable @@ -151,8 +165,10 @@ abstract class _$$RecipeCreateEntityImplCopyWith<$Res> TypeOfRecipe? typeOfRecipe, int? countOfPortions, double? energyInOnePortion, - String? totalCookTime, - String? activeCookTime, + int? totalCookHours, + int? totalCookMinutes, + int? activeCookHours, + int? activeCookMinutes, String? description, File? image, String? videoUrl, @@ -178,8 +194,10 @@ class __$$RecipeCreateEntityImplCopyWithImpl<$Res> Object? typeOfRecipe = freezed, Object? countOfPortions = freezed, Object? energyInOnePortion = freezed, - Object? totalCookTime = freezed, - Object? activeCookTime = freezed, + Object? totalCookHours = freezed, + Object? totalCookMinutes = freezed, + Object? activeCookHours = freezed, + Object? activeCookMinutes = freezed, Object? description = freezed, Object? image = freezed, Object? videoUrl = freezed, @@ -204,14 +222,22 @@ class __$$RecipeCreateEntityImplCopyWithImpl<$Res> ? _value.energyInOnePortion : energyInOnePortion // ignore: cast_nullable_to_non_nullable as double?, - totalCookTime: freezed == totalCookTime - ? _value.totalCookTime - : totalCookTime // ignore: cast_nullable_to_non_nullable - as String?, - activeCookTime: freezed == activeCookTime - ? _value.activeCookTime - : activeCookTime // ignore: cast_nullable_to_non_nullable - as String?, + totalCookHours: freezed == totalCookHours + ? _value.totalCookHours + : totalCookHours // ignore: cast_nullable_to_non_nullable + as int?, + totalCookMinutes: freezed == totalCookMinutes + ? _value.totalCookMinutes + : totalCookMinutes // ignore: cast_nullable_to_non_nullable + as int?, + activeCookHours: freezed == activeCookHours + ? _value.activeCookHours + : activeCookHours // ignore: cast_nullable_to_non_nullable + as int?, + activeCookMinutes: freezed == activeCookMinutes + ? _value.activeCookMinutes + : activeCookMinutes // ignore: cast_nullable_to_non_nullable + as int?, description: freezed == description ? _value.description : description // ignore: cast_nullable_to_non_nullable @@ -248,8 +274,10 @@ class _$RecipeCreateEntityImpl extends _RecipeCreateEntity { this.typeOfRecipe, this.countOfPortions, this.energyInOnePortion, - this.totalCookTime, - this.activeCookTime, + this.totalCookHours, + this.totalCookMinutes, + this.activeCookHours, + this.activeCookMinutes, this.description, this.image, this.videoUrl, @@ -270,11 +298,13 @@ class _$RecipeCreateEntityImpl extends _RecipeCreateEntity { @override final double? energyInOnePortion; @override - final String? totalCookTime; -// hh:mm + final int? totalCookHours; @override - final String? activeCookTime; -// hh:mm + final int? totalCookMinutes; + @override + final int? activeCookHours; + @override + final int? activeCookMinutes; @override final String? description; @override @@ -307,7 +337,7 @@ class _$RecipeCreateEntityImpl extends _RecipeCreateEntity { @override String toString() { - return 'RecipeCreateEntity(title: $title, typeOfRecipe: $typeOfRecipe, countOfPortions: $countOfPortions, energyInOnePortion: $energyInOnePortion, totalCookTime: $totalCookTime, activeCookTime: $activeCookTime, description: $description, image: $image, videoUrl: $videoUrl, ingredients: $ingredients, steps: $steps, tags: $tags)'; + return 'RecipeCreateEntity(title: $title, typeOfRecipe: $typeOfRecipe, countOfPortions: $countOfPortions, energyInOnePortion: $energyInOnePortion, totalCookHours: $totalCookHours, totalCookMinutes: $totalCookMinutes, activeCookHours: $activeCookHours, activeCookMinutes: $activeCookMinutes, description: $description, image: $image, videoUrl: $videoUrl, ingredients: $ingredients, steps: $steps, tags: $tags)'; } @override @@ -322,10 +352,14 @@ class _$RecipeCreateEntityImpl extends _RecipeCreateEntity { other.countOfPortions == countOfPortions) && (identical(other.energyInOnePortion, energyInOnePortion) || other.energyInOnePortion == energyInOnePortion) && - (identical(other.totalCookTime, totalCookTime) || - other.totalCookTime == totalCookTime) && - (identical(other.activeCookTime, activeCookTime) || - other.activeCookTime == activeCookTime) && + (identical(other.totalCookHours, totalCookHours) || + other.totalCookHours == totalCookHours) && + (identical(other.totalCookMinutes, totalCookMinutes) || + other.totalCookMinutes == totalCookMinutes) && + (identical(other.activeCookHours, activeCookHours) || + other.activeCookHours == activeCookHours) && + (identical(other.activeCookMinutes, activeCookMinutes) || + other.activeCookMinutes == activeCookMinutes) && (identical(other.description, description) || other.description == description) && (identical(other.image, image) || other.image == image) && @@ -344,8 +378,10 @@ class _$RecipeCreateEntityImpl extends _RecipeCreateEntity { typeOfRecipe, countOfPortions, energyInOnePortion, - totalCookTime, - activeCookTime, + totalCookHours, + totalCookMinutes, + activeCookHours, + activeCookMinutes, description, image, videoUrl, @@ -369,8 +405,10 @@ abstract class _RecipeCreateEntity extends RecipeCreateEntity { final TypeOfRecipe? typeOfRecipe, final int? countOfPortions, final double? energyInOnePortion, - final String? totalCookTime, - final String? activeCookTime, + final int? totalCookHours, + final int? totalCookMinutes, + final int? activeCookHours, + final int? activeCookMinutes, final String? description, final File? image, final String? videoUrl, @@ -388,9 +426,13 @@ abstract class _RecipeCreateEntity extends RecipeCreateEntity { @override double? get energyInOnePortion; @override - String? get totalCookTime; // hh:mm + int? get totalCookHours; @override - String? get activeCookTime; // hh:mm + int? get totalCookMinutes; + @override + int? get activeCookHours; + @override + int? get activeCookMinutes; @override String? get description; @override 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 00d4961b..a91e44dd 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 @@ -101,12 +101,20 @@ class CreateRecipeCubit extends Cubit { emit(state.copyWith.entity(energyInOnePortion: energy)); } - replaceTotalTime(String time) { - emit(state.copyWith.entity(totalCookTime: time)); + setTotalCookingMinutes(int minutes) { + emit(state.copyWith.entity(totalCookMinutes: minutes)); } - replaceActiveTime(String time) { - emit(state.copyWith.entity(activeCookTime: time)); + setTotalCookingHours(int hours) { + emit(state.copyWith.entity(totalCookHours: hours)); + } + + setActiveCookingMinutes(int minutes) { + emit(state.copyWith.entity(activeCookMinutes: minutes)); + } + + setActiveCookingHours(int hours) { + emit(state.copyWith.entity(activeCookHours: hours)); } replaceVideoUrl(String videoUrl) { @@ -190,7 +198,7 @@ class CreateRecipeCubit extends Cubit { } bool validateActiveCookTime() { - if (state.entity.activeCookTime == null || state.entity.activeCookTime == '00:00') { + if (state.entity.activeCookHours == null && state.entity.activeCookMinutes == null) { emit(state.copyWith(activeTimeValidationError: 'Поле не заполнено')); return false; } else { @@ -200,7 +208,7 @@ class CreateRecipeCubit extends Cubit { } bool validateTotalCookTime() { - if (state.entity.totalCookTime == null || state.entity.totalCookTime == '00:00') { + if (state.entity.totalCookHours == null || state.entity.totalCookMinutes == null) { emit(state.copyWith(cookingTimeValidationError: 'Поле не заполнено')); return false; } else { diff --git a/lib/features/recipes/presentation/widgets/time_of_cook_widget.dart b/lib/features/recipes/presentation/widgets/time_of_cook_widget.dart index 1c4e0306..e845a923 100644 --- a/lib/features/recipes/presentation/widgets/time_of_cook_widget.dart +++ b/lib/features/recipes/presentation/widgets/time_of_cook_widget.dart @@ -72,11 +72,9 @@ class _TimeOfCookWidgetState extends State { hasError: state.cookingTimeValidationError != null, textInputType: TextInputType.number, onChanged: (value) { - if (createRecipeCubit.state.entity.totalCookTime == null) { - createRecipeCubit.replaceTotalTime('${value.padLeft(2, '0')}:00'); - } else { - createRecipeCubit.replaceTotalTime('${value.padLeft(2, '0')}:${createRecipeCubit.state.entity.totalCookTime!.substring(3, 5)}'); - } + final hours = int.tryParse(value); + if (hours == null) return; + createRecipeCubit.setTotalCookingHours(hours); if (state.cookingTimeValidationError != null) { createRecipeCubit.validateTotalCookTime(); } @@ -100,11 +98,9 @@ class _TimeOfCookWidgetState extends State { hasError: state.cookingTimeValidationError != null, textInputType: TextInputType.number, onChanged: (value) { - if (createRecipeCubit.state.entity.totalCookTime == null) { - createRecipeCubit.replaceTotalTime('00:${value.padLeft(2, '0')}'); - } else { - createRecipeCubit.replaceTotalTime('${createRecipeCubit.state.entity.totalCookTime!.substring(0, 2)}:${value.padLeft(2, '0')}'); - } + final minutes = int.tryParse(value); + if (minutes == null) return; + createRecipeCubit.setTotalCookingMinutes(minutes); if (state.cookingTimeValidationError != null) { createRecipeCubit.validateTotalCookTime(); } @@ -162,11 +158,9 @@ class _TimeOfCookWidgetState extends State { hasError: state.activeTimeValidationError != null, textInputType: TextInputType.number, onChanged: (value) { - if (createRecipeCubit.state.entity.activeCookTime == null) { - createRecipeCubit.replaceActiveTime('${value.padLeft(2, '0')}:00'); - } else { - createRecipeCubit.replaceActiveTime('${value.padLeft(2, '0')}:${createRecipeCubit.state.entity.activeCookTime!.substring(3, 5)}'); - } + final hours = int.tryParse(value); + if (hours == null) return; + createRecipeCubit.setActiveCookingHours(hours); if (state.activeTimeValidationError != null) { createRecipeCubit.validateActiveCookTime(); @@ -191,11 +185,9 @@ class _TimeOfCookWidgetState extends State { hasError: state.activeTimeValidationError != null, textInputType: TextInputType.number, onChanged: (value) { - if (createRecipeCubit.state.entity.activeCookTime == null) { - createRecipeCubit.replaceActiveTime('00:${value.padLeft(2, '0')}'); - } else { - createRecipeCubit.replaceActiveTime('${createRecipeCubit.state.entity.activeCookTime!.substring(0, 2)}:${value.padLeft(2, '0')}'); - } + final minutes = int.tryParse(value); + if (minutes == null) return; + createRecipeCubit.setActiveCookingMinutes(minutes); if (state.activeTimeValidationError != null) { createRecipeCubit.validateActiveCookTime(); diff --git a/lib/features/recipes/services/recipe_service.g.dart b/lib/features/recipes/services/recipe_service.g.dart index f9882b29..e1e9d280 100644 --- a/lib/features/recipes/services/recipe_service.g.dart +++ b/lib/features/recipes/services/recipe_service.g.dart @@ -9,11 +9,7 @@ part of 'recipe_service.dart'; // ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers,unused_element,unnecessary_string_interpolations class _RecipeService implements RecipeService { - _RecipeService( - this._dio, { - this.baseUrl, - this.errorLogger, - }); + _RecipeService(this._dio, {this.baseUrl, this.errorLogger}); final Dio _dio; @@ -27,22 +23,16 @@ class _RecipeService implements RecipeService { final queryParameters = {}; final _headers = {}; const Map? _data = null; - final _options = _setStreamType(Options( - method: 'GET', - headers: _headers, - extra: _extra, - ) - .compose( - _dio.options, - '/api/recipes/show/${id}', - queryParameters: queryParameters, - data: _data, - ) - .copyWith( - baseUrl: _combineBaseUrls( - _dio.options.baseUrl, - baseUrl, - ))); + final _options = _setStreamType( + Options(method: 'GET', headers: _headers, extra: _extra) + .compose( + _dio.options, + '/api/recipes/show/${id}', + queryParameters: queryParameters, + data: _data, + ) + .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)), + ); final _result = await _dio.fetch>(_options); late RecipeModel _value; try { @@ -60,30 +50,27 @@ class _RecipeService implements RecipeService { final queryParameters = {r'name': name}; final _headers = {}; const Map? _data = null; - final _options = _setStreamType>>(Options( - method: 'GET', - headers: _headers, - extra: _extra, - ) - .compose( - _dio.options, - '/api/diary/product_nutritional_values/filter', - queryParameters: queryParameters, - data: _data, - ) - .copyWith( - baseUrl: _combineBaseUrls( - _dio.options.baseUrl, - baseUrl, - ))); + final _options = _setStreamType>>( + Options(method: 'GET', headers: _headers, extra: _extra) + .compose( + _dio.options, + '/api/diary/product_nutritional_values/filter', + queryParameters: queryParameters, + data: _data, + ) + .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)), + ); final _result = await _dio.fetch>(_options); late Map> _value; try { - _value = _result.data!.map((k, dynamic v) => MapEntry( + _value = _result.data!.map( + (k, dynamic v) => MapEntry( k, (v as List) .map((i) => Product.fromJson(i as Map)) - .toList())); + .toList(), + ), + ); } on Object catch (e, s) { errorLogger?.logError(e, s, _options); rethrow; @@ -97,51 +84,40 @@ class _RecipeService implements RecipeService { final queryParameters = {}; final _headers = {}; final _data = formData; - final _options = _setStreamType(Options( - method: 'POST', - headers: _headers, - extra: _extra, - contentType: 'multipart/form-data', - ) - .compose( - _dio.options, - '/api/recipes/create', - queryParameters: queryParameters, - data: _data, - ) - .copyWith( - baseUrl: _combineBaseUrls( - _dio.options.baseUrl, - baseUrl, - ))); + final _options = _setStreamType( + Options( + method: 'POST', + headers: _headers, + extra: _extra, + contentType: 'multipart/form-data', + ) + .compose( + _dio.options, + '/api/recipes/create', + queryParameters: queryParameters, + data: _data, + ) + .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)), + ); await _dio.fetch(_options); } @override - Future editRecipe( - Map recipe, - String id, - ) async { + Future editRecipe(Map recipe, String id) async { final _extra = {}; final queryParameters = {}; final _headers = {}; final _data = FormData.fromMap(recipe); - final _options = _setStreamType(Options( - method: 'POST', - headers: _headers, - extra: _extra, - ) - .compose( - _dio.options, - '/api/recipes/update/{id}', - queryParameters: queryParameters, - data: _data, - ) - .copyWith( - baseUrl: _combineBaseUrls( - _dio.options.baseUrl, - baseUrl, - ))); + final _options = _setStreamType( + Options(method: 'POST', headers: _headers, extra: _extra) + .compose( + _dio.options, + '/api/recipes/update/{id}', + queryParameters: queryParameters, + data: _data, + ) + .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)), + ); final _result = await _dio.fetch>(_options); late RecipeModel _value; try { @@ -159,22 +135,16 @@ class _RecipeService implements RecipeService { final queryParameters = {}; final _headers = {}; const Map? _data = null; - final _options = _setStreamType(Options( - method: 'GET', - headers: _headers, - extra: _extra, - ) - .compose( - _dio.options, - '/api/recipes/tags', - queryParameters: queryParameters, - data: _data, - ) - .copyWith( - baseUrl: _combineBaseUrls( - _dio.options.baseUrl, - baseUrl, - ))); + final _options = _setStreamType( + Options(method: 'GET', headers: _headers, extra: _extra) + .compose( + _dio.options, + '/api/recipes/tags', + queryParameters: queryParameters, + data: _data, + ) + .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)), + ); final _result = await _dio.fetch>(_options); late RecipeTagModel _value; try { @@ -199,10 +169,7 @@ class _RecipeService implements RecipeService { return requestOptions; } - String _combineBaseUrls( - String dioBaseUrl, - String? baseUrl, - ) { + String _combineBaseUrls(String dioBaseUrl, String? baseUrl) { if (baseUrl == null || baseUrl.trim().isEmpty) { return dioBaseUrl; } diff --git a/lib/features/registration/services/registration_service.g.dart b/lib/features/registration/services/registration_service.g.dart index c7b90410..f9560e28 100644 --- a/lib/features/registration/services/registration_service.g.dart +++ b/lib/features/registration/services/registration_service.g.dart @@ -9,11 +9,7 @@ part of 'registration_service.dart'; // ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers,unused_element,unnecessary_string_interpolations class _RegistrationService implements RegistrationService { - _RegistrationService( - this._dio, { - this.baseUrl, - this.errorLogger, - }); + _RegistrationService(this._dio, {this.baseUrl, this.errorLogger}); final Dio _dio; @@ -22,29 +18,24 @@ class _RegistrationService implements RegistrationService { final ParseErrorLogger? errorLogger; @override - Future> register( - {required Map sheet}) async { + Future> register({ + required Map sheet, + }) async { final _extra = {}; final queryParameters = {}; final _headers = {}; final _data = {}; _data.addAll(sheet); - final _options = _setStreamType>(Options( - method: 'POST', - headers: _headers, - extra: _extra, - ) - .compose( - _dio.options, - '/api/register', - queryParameters: queryParameters, - data: _data, - ) - .copyWith( - baseUrl: _combineBaseUrls( - _dio.options.baseUrl, - baseUrl, - ))); + final _options = _setStreamType>( + Options(method: 'POST', headers: _headers, extra: _extra) + .compose( + _dio.options, + '/api/register', + queryParameters: queryParameters, + data: _data, + ) + .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)), + ); final _result = await _dio.fetch>(_options); late Map _value; try { @@ -62,22 +53,16 @@ class _RegistrationService implements RegistrationService { final queryParameters = {r'user_address': query}; final _headers = {}; const Map? _data = null; - final _options = _setStreamType(Options( - method: 'GET', - headers: _headers, - extra: _extra, - ) - .compose( - _dio.options, - '/api/location_by_address', - queryParameters: queryParameters, - data: _data, - ) - .copyWith( - baseUrl: _combineBaseUrls( - _dio.options.baseUrl, - baseUrl, - ))); + final _options = _setStreamType( + Options(method: 'GET', headers: _headers, extra: _extra) + .compose( + _dio.options, + '/api/location_by_address', + queryParameters: queryParameters, + data: _data, + ) + .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)), + ); final _result = await _dio.fetch>(_options); late LocationModel _value; try { @@ -101,22 +86,16 @@ class _RegistrationService implements RegistrationService { }; final _headers = {}; const Map? _data = null; - final _options = _setStreamType(Options( - method: 'GET', - headers: _headers, - extra: _extra, - ) - .compose( - _dio.options, - '/api/location_by_coordinate', - queryParameters: queryParameters, - data: _data, - ) - .copyWith( - baseUrl: _combineBaseUrls( - _dio.options.baseUrl, - baseUrl, - ))); + final _options = _setStreamType( + Options(method: 'GET', headers: _headers, extra: _extra) + .compose( + _dio.options, + '/api/location_by_coordinate', + queryParameters: queryParameters, + data: _data, + ) + .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)), + ); final _result = await _dio.fetch>(_options); late LocationModel _value; try { @@ -141,10 +120,7 @@ class _RegistrationService implements RegistrationService { return requestOptions; } - String _combineBaseUrls( - String dioBaseUrl, - String? baseUrl, - ) { + String _combineBaseUrls(String dioBaseUrl, String? baseUrl) { if (baseUrl == null || baseUrl.trim().isEmpty) { return dioBaseUrl; } diff --git a/lib/features/settings/presentation/pages/settings_screen.dart b/lib/features/settings/presentation/pages/settings_screen.dart index af2e9387..bd28f36a 100644 --- a/lib/features/settings/presentation/pages/settings_screen.dart +++ b/lib/features/settings/presentation/pages/settings_screen.dart @@ -56,7 +56,7 @@ class _SettingsScreenState extends State { const SizedBox(height: 12), _aboutEatvio(state), const SizedBox(height: 12), - if (state.profile.role != 'consultant') + if (!state.profile.roles.contains('consultant')) Padding( padding: const EdgeInsets.only(bottom: 8), child: _becomeConsultantButton(state), diff --git a/lib/features/settings/services/settings_service.g.dart b/lib/features/settings/services/settings_service.g.dart index d08c842a..0ba4263c 100644 --- a/lib/features/settings/services/settings_service.g.dart +++ b/lib/features/settings/services/settings_service.g.dart @@ -9,11 +9,7 @@ part of 'settings_service.dart'; // ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers,unused_element,unnecessary_string_interpolations class _SettingsService implements SettingsService { - _SettingsService( - this._dio, { - this.baseUrl, - this.errorLogger, - }); + _SettingsService(this._dio, {this.baseUrl, this.errorLogger}); final Dio _dio; @@ -27,22 +23,16 @@ class _SettingsService implements SettingsService { final queryParameters = {}; final _headers = {}; const Map? _data = null; - final _options = _setStreamType(Options( - method: 'GET', - headers: _headers, - extra: _extra, - ) - .compose( - _dio.options, - '/api/profile/settings', - queryParameters: queryParameters, - data: _data, - ) - .copyWith( - baseUrl: _combineBaseUrls( - _dio.options.baseUrl, - baseUrl, - ))); + final _options = _setStreamType( + Options(method: 'GET', headers: _headers, extra: _extra) + .compose( + _dio.options, + '/api/profile/settings', + queryParameters: queryParameters, + data: _data, + ) + .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)), + ); final _result = await _dio.fetch>(_options); late ProfileSettingsModel _value; try { @@ -55,29 +45,24 @@ class _SettingsService implements SettingsService { } @override - Future updateProfileSettings( - {required Map data}) async { + Future updateProfileSettings({ + required Map data, + }) async { final _extra = {}; final queryParameters = {}; final _headers = {}; final _data = {}; _data.addAll(data); - final _options = _setStreamType(Options( - method: 'PUT', - headers: _headers, - extra: _extra, - ) - .compose( - _dio.options, - '/api/profile/settings/update', - queryParameters: queryParameters, - data: _data, - ) - .copyWith( - baseUrl: _combineBaseUrls( - _dio.options.baseUrl, - baseUrl, - ))); + final _options = _setStreamType( + Options(method: 'PUT', headers: _headers, extra: _extra) + .compose( + _dio.options, + '/api/profile/settings/update', + queryParameters: queryParameters, + data: _data, + ) + .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)), + ); final _result = await _dio.fetch>(_options); late ProfileSettingsModel _value; try { @@ -90,29 +75,24 @@ class _SettingsService implements SettingsService { } @override - Future updateProfileDetails( - {required Map data}) async { + Future updateProfileDetails({ + required Map data, + }) async { final _extra = {}; final queryParameters = {}; final _headers = {}; final _data = {}; _data.addAll(data); - final _options = _setStreamType(Options( - method: 'PUT', - headers: _headers, - extra: _extra, - ) - .compose( - _dio.options, - '/api/profile/update', - queryParameters: queryParameters, - data: _data, - ) - .copyWith( - baseUrl: _combineBaseUrls( - _dio.options.baseUrl, - baseUrl, - ))); + final _options = _setStreamType( + Options(method: 'PUT', headers: _headers, extra: _extra) + .compose( + _dio.options, + '/api/profile/update', + queryParameters: queryParameters, + data: _data, + ) + .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)), + ); final _result = await _dio.fetch>(_options); late ProfileDetailsModel _value; try { @@ -137,10 +117,7 @@ class _SettingsService implements SettingsService { return requestOptions; } - String _combineBaseUrls( - String dioBaseUrl, - String? baseUrl, - ) { + String _combineBaseUrls(String dioBaseUrl, String? baseUrl) { if (baseUrl == null || baseUrl.trim().isEmpty) { return dioBaseUrl; } diff --git a/test/features/profile/services/profile_service_test.dart b/test/features/profile/services/profile_service_test.dart index 4db6526b..dc04bfd2 100644 --- a/test/features/profile/services/profile_service_test.dart +++ b/test/features/profile/services/profile_service_test.dart @@ -23,7 +23,7 @@ void main() { isEntitySubscribedByMe: false, slug: 'slug', profilePhotoUrl: '', - role: 'user', + roles: ['user'], badges: [], subscriptions: [], subscribers: [],