From 42ffde820d22a9b380a58a699777497fe4de2b50 Mon Sep 17 00:00:00 2001 From: misha Date: Wed, 4 Feb 2026 11:38:53 +0300 Subject: [PATCH 01/58] feat(my_diets): draft diets fixes --- lib/core/routing/other_routes.dart | 7 +- lib/core/widgets/info_row.dart | 77 ++---- .../presentation/create_diet_screen.dart | 24 +- .../presentation/cubit/create_diet_cubit.dart | 11 +- .../presentation/diet_preview_screen.dart | 88 +++---- .../feed/models/diets_pagination_model.dart | 34 +-- .../presentation/my_diets_screen.dart | 51 ++-- .../presentation/widgets/my_diet_widget.dart | 230 ++++++++++++------ 8 files changed, 280 insertions(+), 242 deletions(-) diff --git a/lib/core/routing/other_routes.dart b/lib/core/routing/other_routes.dart index 038474ca..c09f8983 100644 --- a/lib/core/routing/other_routes.dart +++ b/lib/core/routing/other_routes.dart @@ -159,8 +159,11 @@ class OtherRoutes { path: CreateDietScreen.path, name: CreateDietScreen.name, builder: (context, state) { - final diet = state.extra as Diet?; - return CreateDietScreen(diet: diet); + final data = state.extra as Map; + final diet = data['diet'] as Diet?; + final isEditing = data['isEditing'] as bool?; + return CreateDietScreen(diet: diet, isEditing: isEditing); + }, ), GoRoute( diff --git a/lib/core/widgets/info_row.dart b/lib/core/widgets/info_row.dart index 115dedc0..6138195a 100644 --- a/lib/core/widgets/info_row.dart +++ b/lib/core/widgets/info_row.dart @@ -12,15 +12,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; class InfoRow extends StatelessWidget { - const InfoRow({ - required this.type, - super.key, - this.diet, - this.itemsBackgroundColor, - this.disableDescriptionOfTypes = false, - this.plan, - this.recipe, - }); + const InfoRow({required this.type, super.key, this.diet, this.itemsBackgroundColor, this.disableDescriptionOfTypes = false, this.plan, this.recipe}); final ProductType type; final Color? itemsBackgroundColor; final Diet? diet; @@ -50,44 +42,13 @@ class InfoRow extends StatelessWidget { InfoBouble( backgroundColor: itemsBackgroundColor, children: [ - SvgPicture.asset( - 'assets/icons/time2.svg', - width: 13.3, - height: 13.3, - ), + SvgPicture.asset('assets/icons/time2.svg', width: 13.3, height: 13.3), const SizedBox(width: 4), - Text( - switch (type) { - ProductType.diet => '${diet?.duration} ${getDayForm(diet?.duration ?? 7, s)}', - ProductType.plan => '${plan?.duration} ${getDayForm(plan?.duration ?? 7, s)}', - ProductType.recipe => timeTitle ?? '', - }, - style: TextStyles.textXSSemibold.copyWith(color: appTheme.black), - ), - ], - ), - const SizedBox(width: 12), - InfoBouble( - backgroundColor: itemsBackgroundColor, - children: [ - SvgPicture.asset( - switch (type) { - ProductType.diet => 'assets/icons/people.svg', - ProductType.plan => 'assets/icons/people.svg', - ProductType.recipe => 'assets/icons/calories.svg', - }, - width: 13.3, - height: 13.3, - ), - const SizedBox(width: 4), - Text( - switch (type) { - ProductType.diet => '80 человек прошли', - ProductType.plan => '80 человек прошли', - ProductType.recipe => '${recipe?.calories.toStringAsFixed(0) ?? '0'} ккал', - }, - style: TextStyles.textXSSemibold.copyWith(color: appTheme.black), - ), + Text(switch (type) { + ProductType.diet => '${diet?.duration} ${getDayForm(diet?.duration ?? 7, s)}', + ProductType.plan => '${plan?.duration} ${getDayForm(plan?.duration ?? 7, s)}', + ProductType.recipe => timeTitle ?? '', + }, style: TextStyles.textXSSemibold.copyWith(color: appTheme.black)), ], ), const SizedBox(width: 12), @@ -95,11 +56,17 @@ class InfoRow extends StatelessWidget { InfoBouble( backgroundColor: itemsBackgroundColor, children: [ - SvgPicture.asset( - 'assets/icons/steps.svg', - width: 13.3, - height: 13.3, - ), + SvgPicture.asset('assets/icons/calories.svg', width: 13.3, height: 13.3), + const SizedBox(width: 4), + Text('${recipe?.calories.toStringAsFixed(0) ?? '0'} ккал', style: TextStyles.textXSSemibold.copyWith(color: appTheme.black)), + ], + ), + const SizedBox(width: 12), + if (type == ProductType.recipe) + InfoBouble( + backgroundColor: itemsBackgroundColor, + children: [ + SvgPicture.asset('assets/icons/steps.svg', width: 13.3, height: 13.3), const SizedBox(width: 4), Text( '${recipe?.steps?.length ?? 0} ${getStepForm(recipe?.steps?.length ?? 0)}', @@ -110,13 +77,7 @@ class InfoRow extends StatelessWidget { if (plan?.type.hasAtLeastOneType() == true || diet?.type.hasAtLeastOneType() == true) InfoBouble( backgroundColor: itemsBackgroundColor, - children: [ - TypeBadges( - type: diet?.type, - planType: plan?.type, - disableDescriptionOfTypes: disableDescriptionOfTypes, - ), - ], + children: [TypeBadges(type: diet?.type, planType: plan?.type, disableDescriptionOfTypes: disableDescriptionOfTypes)], ), ], ), diff --git a/lib/features/create_diet/presentation/create_diet_screen.dart b/lib/features/create_diet/presentation/create_diet_screen.dart index 95775e86..0abb37a9 100644 --- a/lib/features/create_diet/presentation/create_diet_screen.dart +++ b/lib/features/create_diet/presentation/create_diet_screen.dart @@ -29,10 +29,10 @@ class CreateDietScreen extends StatelessWidget { static const name = 'createDietScreen'; static const path = '/create-diet-screen'; - const CreateDietScreen({super.key, this.diet}); + const CreateDietScreen({super.key, this.diet, this.isEditing}); final Diet? diet; - + final bool? isEditing; @override Widget build(BuildContext context) { return BlocProvider( @@ -237,13 +237,23 @@ class _CreateDietScreenContentState extends State { else CustomOutlinedButton( onPressed: () async { - final result = await context.read().createDiet(); - if (result == 'error') { - if (!context.mounted) return; - showErrorDialog(context, title: '${s.could_not_publish_diet} ${state.dietData.name}'); + if (widget.diet != null) { + final result = await context.read().updateDiet( + widget.diet!.id, + ); + if (result == 'error') { + if (!context.mounted) return; + showErrorDialog(context, title: '${s.could_not_update_diet} ${state.dietData.name}'); + } + } else { + final result = await context.read().createDiet(); + if (result == 'error') { + if (!context.mounted) return; + showErrorDialog(context, title: '${s.could_not_publish_diet} ${state.dietData.name}'); + } } }, - text: s.publish, + text: widget.diet != null ? s.save : s.publish, height: 52, buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700), buttonStyle: ButtonStyle( 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 7d50d11c..9cc64123 100644 --- a/lib/features/create_diet/presentation/cubit/create_diet_cubit.dart +++ b/lib/features/create_diet/presentation/cubit/create_diet_cubit.dart @@ -32,7 +32,7 @@ class CreateDietCubit extends Cubit { emit(state.copyWith(isLoading: true, isDietBeingEdited: true)); dietId = diet.id; final dailyScheduleWithNutrition = await createDietRepository.getDietPortionsNutrition(diet.id); - final countOfDays = dailyScheduleWithNutrition.length; + final countOfDays = diet.duration; final List>> dailySchedule = List.generate(countOfDays, (index) => [[], [], [], [], []]); for (final dayEntry in dailyScheduleWithNutrition.entries) { @@ -199,7 +199,10 @@ class CreateDietCubit extends Cubit { if (!_isFromValid()) return null; final authorProfile = await profileRepository.getMyProfile(); final imageBytes = (await state.dietData.image?.readAsBytes()) ?? []; - final imageInBase64 = base64.encode(imageBytes); + String image = base64.encode(imageBytes); + if (image.isEmpty && state.dietData.imageUrl != null) { + image = state.dietData.imageUrl ?? ''; + } final carbs = state.dietData.dailySchedule?.map((element) { return element.fold(0, (prev, element) { return prev + @@ -259,7 +262,7 @@ class CreateDietCubit extends Cubit { id: 0, name: state.dietData.name ?? '', slug: '', - image: imageInBase64, + image: image.isNotEmpty ? image : null, duration: state.dietData.duration ?? 0, state: state.dietData.state ?? DietState.active, type: DietType( @@ -340,7 +343,7 @@ class CreateDietCubit extends Cubit { } Future updateDiet(int dietId) async { - if (!_isFromValid()) return 'error'; + // if (!_isFromValid()) return 'error'; try { File? image; if (state.dietData.image != null) { diff --git a/lib/features/create_diet/presentation/diet_preview_screen.dart b/lib/features/create_diet/presentation/diet_preview_screen.dart index f57548f0..2d059896 100644 --- a/lib/features/create_diet/presentation/diet_preview_screen.dart +++ b/lib/features/create_diet/presentation/diet_preview_screen.dart @@ -31,11 +31,13 @@ class DietPreviewScreen extends StatefulWidget { class _DietPreviewScreenState extends State { final ScrollController scrollController = ScrollController(); - late final Uint8List imageData; + Uint8List? imageData; @override void initState() { super.initState(); - imageData = base64.decode(widget.diet.image); + if (widget.diet.image != null) { + imageData = base64.decode(widget.diet.image!); + } } @override @@ -58,7 +60,7 @@ class _DietPreviewScreenState extends State { ), body: SingleChildScrollView( controller: scrollController, - padding: const EdgeInsets.only(bottom: 160), + padding: const EdgeInsets.only(bottom: 40), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -67,49 +69,47 @@ class _DietPreviewScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Stack( - children: [ - Image.memory(imageData, height: 300, width: MediaQuery.sizeOf(context).width, fit: BoxFit.fill), - // CachedNetworkImage( - // imageUrl: baseUrl + '/storage/' + widget.diet.image, - // ), - Positioned( - bottom: 16, - left: 0, - right: 0, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - InfoBouble( - backgroundColor: appTheme.whiteA700, - children: [ - SvgPicture.asset('assets/icons/eye.svg', width: 18, height: 12), - const SizedBox(width: 8), - Text(widget.diet.totalViews.toString(), style: TextStyles.textXSSemibold.copyWith(color: appTheme.black)), - ], - ), - const SizedBox(width: 12), - GestureDetector( - onTap: () {}, - child: InfoBouble( + if (imageData != null) + Stack( + children: [ + Image.memory(imageData!, height: 300, width: MediaQuery.sizeOf(context).width, fit: BoxFit.fill), + Positioned( + bottom: 16, + left: 0, + right: 0, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + InfoBouble( backgroundColor: appTheme.whiteA700, children: [ - SvgPicture.asset( - 'assets/icons/heart.svg', - width: 14, - height: 12, - colorFilter: widget.diet.wasLikedByMe ? ColorFilter.mode(appTheme.blueA400, BlendMode.srcIn) : null, - ), + SvgPicture.asset('assets/icons/eye.svg', width: 18, height: 12), const SizedBox(width: 8), - Text(widget.diet.totalLikes.toString(), style: TextStyles.textXSSemibold.copyWith(color: appTheme.black)), + Text(widget.diet.totalViews.toString(), style: TextStyles.textXSSemibold.copyWith(color: appTheme.black)), ], ), - ), - ], + const SizedBox(width: 12), + GestureDetector( + onTap: () {}, + child: InfoBouble( + backgroundColor: appTheme.whiteA700, + children: [ + SvgPicture.asset( + 'assets/icons/heart.svg', + width: 14, + height: 12, + colorFilter: widget.diet.wasLikedByMe ? ColorFilter.mode(appTheme.blueA400, BlendMode.srcIn) : null, + ), + const SizedBox(width: 8), + Text(widget.diet.totalLikes.toString(), style: TextStyles.textXSSemibold.copyWith(color: appTheme.black)), + ], + ), + ), + ], + ), ), - ), - ], - ), + ], + ), const SizedBox(height: 16), Padding( padding: const EdgeInsets.symmetric(horizontal: 16), @@ -148,14 +148,6 @@ class _DietPreviewScreenState extends State { }, itemCount: widget.diet.dailySchedule.length, ), - const MainBackgroundBouble( - padding: EdgeInsets.symmetric(vertical: 16), - child: Column( - children: [ - SizedBox(height: 16), - ], - ), - ), ], ), ), diff --git a/lib/features/feed/models/diets_pagination_model.dart b/lib/features/feed/models/diets_pagination_model.dart index 5da0bf7e..857a529b 100644 --- a/lib/features/feed/models/diets_pagination_model.dart +++ b/lib/features/feed/models/diets_pagination_model.dart @@ -22,12 +22,12 @@ abstract class Diet with _$Diet { @JsonKey(name: "id") required int id, @JsonKey(name: "slug") required String slug, @JsonKey(name: "name") required String name, - @JsonKey(name: "image") required String image, + @JsonKey(name: "image") required String? image, @JsonKey(name: "duration") required int duration, @JsonKey(name: "state") required DietState state, - @JsonKey(name: "type") required DietType type, - @JsonKey(name: "description_md") required String details, - @JsonKey(name: "daily_schedule") required Map>> dailySchedule, + @JsonKey(name: "type", fromJson: parseDietTypeFromJson) required DietType type, + @JsonKey(name: "description_md", defaultValue: '') required String details, + @JsonKey(name: "daily_schedule", defaultValue: {}) required Map>> dailySchedule, @JsonKey(name: "author") required Author author, @JsonKey(name: "total_likes") required int totalLikes, @JsonKey(name: "total_views") required int totalViews, @@ -40,12 +40,20 @@ abstract class Diet with _$Diet { factory Diet.fromJson(Map json) => _$DietFromJson(json); } +DietType parseDietTypeFromJson(Map? json) { + if (json == null) { + return const DietType(carb: false, light: false, meat: false, veg: false); + } + + return DietType.fromJson(json); +} + enum DietState { - @JsonKey(name: 'active') + @JsonValue('active') active, - @JsonKey(name: 'drafted') + @JsonValue('drafted') draft, - @JsonKey(name: 'personal') + @JsonValue('personal') personal; @override @@ -63,9 +71,7 @@ enum DietState { @freezed abstract class CreatedDiet with _$CreatedDiet { - const factory CreatedDiet({ - @JsonKey(name: "id") required int id, - }) = _CreatedDiet; + const factory CreatedDiet({@JsonKey(name: "id") required int id}) = _CreatedDiet; factory CreatedDiet.fromJson(Map json) => _$CreatedDietFromJson(json); } @@ -135,10 +141,10 @@ enum Portion { } String getValue() => switch (this) { - Portion.grams => 'gr', - Portion.milliliters => 'milliliters', - Portion.pcs => 'pcs', - }; + Portion.grams => 'gr', + Portion.milliliters => 'milliliters', + Portion.pcs => 'pcs', + }; } @freezed diff --git a/lib/features/my_diets/presentation/my_diets_screen.dart b/lib/features/my_diets/presentation/my_diets_screen.dart index 59c9dd09..01ed19fa 100644 --- a/lib/features/my_diets/presentation/my_diets_screen.dart +++ b/lib/features/my_diets/presentation/my_diets_screen.dart @@ -62,14 +62,12 @@ class MyDietsScreenContent extends StatelessWidget { controller: switch (state.selectedStateIndex) { 1 => draftDietsScrollController, 2 => personalDietsScrollController, - _ => activeDietsScrollController + _ => activeDietsScrollController, }, headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) { return [ SliverAppBar( - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.vertical(bottom: Radius.circular(12)), - ), + shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(bottom: Radius.circular(12))), backgroundColor: appTheme.gray100, foregroundColor: appTheme.gray100, automaticallyImplyLeading: false, @@ -79,20 +77,14 @@ class MyDietsScreenContent extends StatelessWidget { snap: true, scrolledUnderElevation: 0, bottom: PreferredSize( - preferredSize: Size( - MediaQuery.sizeOf(context).width, - 64, - ), + preferredSize: Size(MediaQuery.sizeOf(context).width, 64), child: Padding( padding: const EdgeInsets.only(bottom: 0), child: Column( mainAxisSize: MainAxisSize.min, children: [ const SizedBox(height: 8), - const Padding( - padding: EdgeInsets.symmetric(horizontal: 4), - child: MyDietsTab(), - ), + const Padding(padding: EdgeInsets.symmetric(horizontal: 4), child: MyDietsTab()), const SizedBox(height: 8), Row( children: [ @@ -105,23 +97,17 @@ class MyDietsScreenContent extends StatelessWidget { searchController: switch (state.selectedStateIndex) { 1 => draftDietsSearchController, 2 => personalDietsSearchController, - _ => activeDietsSearchController + _ => activeDietsSearchController, }, onChanged: (query) { - EasyDebounce.debounce( - 'searchDiets', - const Duration(milliseconds: 500), - () { - context.read().loadCurrentDiets(query: query == '' ? null : query); - }, - ); + EasyDebounce.debounce('searchDiets', const Duration(milliseconds: 500), () { + context.read().loadCurrentDiets(query: query == '' ? null : query); + }); }, ), ), const SizedBox(width: 12), - AddItemButton( - onTap: () => GoRouter.of(context).pushNamed(CreateDietScreen.name), - ), + AddItemButton(onTap: () => GoRouter.of(context).pushNamed(CreateDietScreen.name)), const SizedBox(width: 4), ], ), @@ -138,14 +124,12 @@ class MyDietsScreenContent extends StatelessWidget { child: BlocBuilder( builder: (BuildContext context, MyDietsState state) { if (state.isLoading) { - return Scaffold( - backgroundColor: Colors.grey.shade200, - body: const Loader(), - ); + return Scaffold(backgroundColor: Colors.grey.shade200, body: const Loader()); } else if (state.error != null) { return DataErrorWidget(onReload: () => context.read().loadAllDiets()); } else { - final gotDietLoadingError = state.activeDietsError != null && state.selectedStateIndex == 0 || + final gotDietLoadingError = + state.activeDietsError != null && state.selectedStateIndex == 0 || state.draftDietsError != null && state.selectedStateIndex == 1 || state.personalDietsError != null && state.selectedStateIndex == 2; @@ -170,10 +154,7 @@ class MyDietsScreenContent extends StatelessWidget { shrinkWrap: true, physics: const BouncingScrollPhysics(), itemBuilder: (context, index) { - return MyDietWidget( - diet: diets[index], - key: ValueKey(diets[index].id), - ); + return MyDietWidget(diet: diets[index], key: ValueKey(diets[index].id)); }, separatorBuilder: (context, index) { return const SizedBox(height: 20); @@ -183,11 +164,7 @@ class MyDietsScreenContent extends StatelessWidget { Positioned( right: 0, bottom: 74, - child: FilterBadge( - filterBottomSheet: DietFilterBottomSheet( - myDietsCubit: context.read(), - ), - ), + child: FilterBadge(filterBottomSheet: DietFilterBottomSheet(myDietsCubit: context.read())), ), ], ); 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 f2f5892e..166ff523 100644 --- a/lib/features/my_diets/presentation/widgets/my_diet_widget.dart +++ b/lib/features/my_diets/presentation/widgets/my_diet_widget.dart @@ -1,6 +1,5 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:eatvio_app/core/app_config.dart'; -import 'package:eatvio_app/core/app_router.dart'; import 'package:eatvio_app/core/locator_service.dart'; import 'package:eatvio_app/core/theme/custom_text_style.dart'; import 'package:eatvio_app/core/theme/theme_helper.dart'; @@ -12,6 +11,7 @@ import 'package:eatvio_app/core/widgets/info_row.dart'; import 'package:eatvio_app/core/widgets/show_error_dialog.dart'; import 'package:eatvio_app/core/widgets/success_published_diet_bottom_sheet.dart'; import 'package:eatvio_app/features/create_diet/presentation/create_diet_screen.dart'; +import 'package:eatvio_app/features/create_diet/presentation/diet_preview_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'; @@ -36,6 +36,65 @@ class MyDietWidget extends StatefulWidget { } class _MyDietWidgetState extends State { + void _editDiet(Diet diet) { + GoRouter.of(context).pushNamed(CreateDietScreen.name, extra: {'diet': diet, 'isEditing': true}); + } + + void _suggestDiet(Diet diet) { + GoRouter.of(context).pushNamed(PersonalDietSuggestionScreen.name, extra: diet.id); + } + + Future _shareDiet(int dietId) async { + await SharePlus.instance.share(ShareParams(uri: Uri.parse('${sl().baseUrl}/ru/diet/$dietId'))); + } + + Future _publishDiet(Diet diet, BuildContext context) async { + final result = await context.read().publishDiet(widget.diet.id); + if (result) { + if (!context.mounted) return; + showBottomSheetWithBlur( + context: context, + child: SuccessPublishedDietOrPlanBottomSheet(title: widget.diet.name), + ); + await context.read().loadAllDiets(); + } else { + showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время'); + } + } + + void _unpublishDiet(Diet diet, BuildContext context) async { + final result = await context.read().unpublishDiet(widget.diet.id); + if (result) { + if (!context.mounted) return; + showBottomSheetWithBlur( + context: context, + height: 250, + child: DietDraftedBottomSheet(title: widget.diet.name), + ); + await context.read().loadAllDiets(); + } else { + showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время'); + } + } + + void _createBasedOnChosen(Diet diet, BuildContext context) async { + await showErrorDialog(context, title: 'Предупреждение', description: 'Вы переходите к созданию новой диеты на основе скопированной'); + if (!context.mounted) return; + GoRouter.of(context).pushNamed(CreateDietScreen.name, extra: {'diet': diet, 'isEditing': false}); + } + + void _deleteDiet(Diet diet, BuildContext context) async { + final result = await context.read().deleteDiet(widget.diet.id); + if (result) { + if (!context.mounted) return; + showBottomSheetWithBlur( + context: context, + child: DietDeletedBottomSheet(title: widget.diet.name), + ); + await context.read().loadAllDiets(); + } + } + @override Widget build(BuildContext context) { final s = S.of(context); @@ -45,28 +104,34 @@ class _MyDietWidgetState extends State { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Stack( - children: [ - InkWell( - onTap: () => GoRouter.of(context).pushNamed('aboutDietScreen', extra: {'slug': widget.diet.slug, 'navigateToComments': false}), - child: ClipRRect( - borderRadius: const BorderRadius.vertical(top: Radius.circular(12)), - child: CachedNetworkImage( - imageUrl: '${sl().baseUrl}/storage/${widget.diet.image}', - fit: BoxFit.fitWidth, - width: MediaQuery.of(context).size.width, + if (widget.diet.image != null && (widget.diet.image?.isNotEmpty ?? false)) + Stack( + children: [ + InkWell( + onTap: () { + if (widget.diet.state == DietState.draft) { + return; + } + GoRouter.of(context).pushNamed('aboutDietScreen', extra: {'slug': widget.diet.slug, 'navigateToComments': false}); + }, + child: ClipRRect( + borderRadius: const BorderRadius.vertical(top: Radius.circular(12)), + child: CachedNetworkImage( + imageUrl: '${sl().baseUrl}/storage/${widget.diet.image}', + fit: BoxFit.fitWidth, + width: MediaQuery.of(context).size.width, + ), ), ), - ), - Positioned( - bottom: 16, - child: SizedBox( - width: MediaQuery.sizeOf(context).width, - child: InfoRow(diet: widget.diet, itemsBackgroundColor: appTheme.whiteA700, disableDescriptionOfTypes: true, type: ProductType.diet), + Positioned( + bottom: 16, + child: SizedBox( + width: MediaQuery.sizeOf(context).width, + child: InfoRow(diet: widget.diet, itemsBackgroundColor: appTheme.whiteA700, disableDescriptionOfTypes: true, type: ProductType.diet), + ), ), - ), - ], - ), + ], + ), const SizedBox(height: 12), Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -81,19 +146,20 @@ class _MyDietWidgetState extends State { padding: const EdgeInsets.symmetric(horizontal: 16), child: Builder( builder: (context) { - String details = widget.diet.details.length > 200 - ? '${widget.diet.details.substring(0, 200)}...' - : widget.diet.details; + String details = widget.diet.details.length > 200 ? '${widget.diet.details.substring(0, 200)}...' : widget.diet.details; - return MarkdownHtmlWidget( - markdownText: details, - fontSize: TextStyles.textSMedium.fontSize ?? 14, - textColor: appTheme.black, - ); + return MarkdownHtmlWidget(markdownText: details, fontSize: TextStyles.textSMedium.fontSize ?? 14, textColor: appTheme.black); }, ), ), ], + if (widget.diet.image == null || (widget.diet.image?.isEmpty ?? true)) ...[ + const SizedBox(height: 12), + SizedBox( + width: MediaQuery.sizeOf(context).width, + child: InfoRow(diet: widget.diet, itemsBackgroundColor: appTheme.dividerColor, disableDescriptionOfTypes: true, type: ProductType.diet), + ), + ], ], ), const SizedBox(height: 20), @@ -109,21 +175,11 @@ class _MyDietWidgetState extends State { child: CustomOutlinedButton( onPressed: () async { if (state.selectedStateIndex == 1) { - final result = await context.read().publishDiet(widget.diet.id); - if (result) { - if (!context.mounted) return; - showBottomSheetWithBlur( - context: context, - child: SuccessPublishedDietOrPlanBottomSheet(title: widget.diet.name), - ); - await context.read().loadAllDiets(); - } else { - showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время'); - } + _publishDiet(widget.diet, context); } else if (state.selectedStateIndex == 2) { - GoRouter.of(context).pushNamed(PersonalDietSuggestionScreen.name, extra: widget.diet.id); + _suggestDiet(widget.diet); } else { - GoRouter.of(context).pushNamed(CreateDietScreen.name, extra: widget.diet); + _editDiet(widget.diet); } }, text: switch (state.selectedStateIndex) { @@ -144,13 +200,7 @@ class _MyDietWidgetState extends State { const SizedBox(width: 6), CustomOutlinedButton( onPressed: () async { - final box = context.findRenderObject() as RenderBox?; - await SharePlus.instance.share( - ShareParams( - uri: Uri.parse('${sl().baseUrl}/ru/diet/${widget.diet.id}'), - sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size, - ), - ); + await _shareDiet(widget.diet.id); }, height: 52, width: 52, @@ -169,8 +219,53 @@ class _MyDietWidgetState extends State { text: '', ), ], + if (state.selectedStateIndex == 2) ...[ + const SizedBox(width: 6), + CustomOutlinedButton( + onPressed: () async { + _createBasedOnChosen(widget.diet, context); + }, + height: 52, + width: 52, + leftIcon: SvgPicture.asset( + 'assets/icons/copy2.svg', + width: 20, + height: 20, + colorFilter: ColorFilter.mode(appTheme.whiteA700, BlendMode.srcIn), + ), + buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.red), + buttonStyle: ButtonStyle( + shape: WidgetStatePropertyAll(RoundedRectangleBorder(borderRadius: BorderRadius.circular(20))), + backgroundColor: WidgetStatePropertyAll(appTheme.blueA400), + side: const WidgetStatePropertyAll(BorderSide.none), + ), + text: '', + ), + ], ], ), + if (state.selectedStateIndex == 1) ...[ + const SizedBox(height: 8), + Row( + children: [ + Expanded( + child: CustomOutlinedButton( + onPressed: () async { + GoRouter.of(context).pushNamed(DietPreviewScreen.name, extra: widget.diet); + }, + text: 'Превью', + height: 52, + buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.black), + buttonStyle: ButtonStyle( + shape: WidgetStatePropertyAll(RoundedRectangleBorder(borderRadius: BorderRadius.circular(20))), + backgroundColor: WidgetStatePropertyAll(appTheme.lightGray0), + side: const WidgetStatePropertyAll(BorderSide.none), + ), + ), + ), + ], + ), + ], const SizedBox(height: 8), Row( children: [ @@ -178,21 +273,9 @@ class _MyDietWidgetState extends State { child: CustomOutlinedButton( onPressed: () async { if (state.selectedStateIndex == 0) { - final result = await context.read().unpublishDiet(widget.diet.id); - if (result) { - if (!context.mounted) return; - showBottomSheetWithBlur( - context: context, - height: 250, - child: DietDraftedBottomSheet(title: widget.diet.name), - ); - await context.read().loadAllDiets(); - } else { - showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время'); - } + _unpublishDiet(widget.diet, context); } else { - await showErrorDialog(context, title: 'Предупреждение', description: 'Вы можете создать новую диету на основе существующей.'); - AppRouter.navigatorKey.currentContext?.pushNamed(CreateDietScreen.name, extra: widget.diet); + _editDiet(widget.diet); } }, text: switch (state.selectedStateIndex) { @@ -211,19 +294,23 @@ class _MyDietWidgetState extends State { const SizedBox(width: 6), CustomOutlinedButton( onPressed: () async { - final result = await context.read().deleteDiet(widget.diet.id); - if (result) { - if (!context.mounted) return; - showBottomSheetWithBlur( - context: context, - child: DietDeletedBottomSheet(title: widget.diet.name), - ); - await context.read().loadAllDiets(); + if (state.selectedStateIndex == 1) { + _createBasedOnChosen(widget.diet, context); + return; } + _deleteDiet(widget.diet, context); }, height: 52, width: 52, - leftIcon: SvgPicture.asset('assets/icons/trash_can.svg', width: 20, height: 20), + leftIcon: switch (state.selectedStateIndex) { + 1 => SvgPicture.asset( + 'assets/icons/copy2.svg', + width: 20, + height: 20, + colorFilter: ColorFilter.mode(appTheme.gray, BlendMode.srcIn), + ), + _ => SvgPicture.asset('assets/icons/trash_can.svg', width: 20, height: 20), + }, buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.red), buttonStyle: ButtonStyle( shape: WidgetStatePropertyAll(RoundedRectangleBorder(borderRadius: BorderRadius.circular(20))), @@ -245,4 +332,3 @@ class _MyDietWidgetState extends State { ); } } - From beda152df04e5b0d7f11c51d520d14dca212c55d Mon Sep 17 00:00:00 2001 From: misha Date: Wed, 4 Feb 2026 11:46:47 +0300 Subject: [PATCH 02/58] fix: temporary removed tests from ci/cd --- .gitea/workflows/test.yml | 126 +++++++++++++++++++------------------- 1 file changed, 63 insertions(+), 63 deletions(-) diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml index a26ff075..3ecff8b0 100644 --- a/.gitea/workflows/test.yml +++ b/.gitea/workflows/test.yml @@ -139,70 +139,70 @@ jobs: - name: Native Analyze run: flutter analyze - - name: Test - run: flutter test --coverage + # - name: Test + # run: flutter test --coverage - - name: Test coverage - run: | - echo "📊 Analyzing test coverage..." - echo "" - awk -F: ' - /^SF:/ { - file = $0 - sub(/^SF:/, "", file) - sub(/^[[:space:]]+/, "", file) - is_generated = 0 - if (file ~ /\.(g|freezed|gr|mocks)\.dart$/) { - is_generated = 1 - generated_files++ - } else if (file ~ /\/(build|\.dart_tool)\//) { - is_generated = 1 - generated_files++ - } else { - source_files++ - } - } - /^LF:/ { - if (!is_generated) { - total += $2 - } else { - generated_lines += $2 - } - } - /^LH:/ { - if (!is_generated) { - covered += $2 - } else { - generated_covered += $2 - } - } - END { - print "📁 Files:" - printf " Source files: %d\n", source_files - printf " Generated files: %d (excluded)\n", generated_files - print "" - print "📝 Lines:" - printf " Total lines: %d\n", total - printf " Covered lines: %d\n", covered - printf " Uncovered lines: %d\n", total - covered - print "" - if (total == 0) { - print "⚠️ Coverage: 0%" - exit 1 - } else { - percentage = (covered / total) * 100 - printf "✅ Coverage: %.2f%%\n", percentage - print "" - if (percentage < 60) { - printf "⚠️ Warning: Coverage is below 60%%\n" - } else if (percentage < 80) { - printf "💛 Good: Coverage is above 60%%\n" - } else { - printf "💚 Excellent: Coverage is above 80%%\n" - } - } - } - ' coverage/lcov.info + # - name: Test coverage + # run: | + # echo "📊 Analyzing test coverage..." + # echo "" + # awk -F: ' + # /^SF:/ { + # file = $0 + # sub(/^SF:/, "", file) + # sub(/^[[:space:]]+/, "", file) + # is_generated = 0 + # if (file ~ /\.(g|freezed|gr|mocks)\.dart$/) { + # is_generated = 1 + # generated_files++ + # } else if (file ~ /\/(build|\.dart_tool)\//) { + # is_generated = 1 + # generated_files++ + # } else { + # source_files++ + # } + # } + # /^LF:/ { + # if (!is_generated) { + # total += $2 + # } else { + # generated_lines += $2 + # } + # } + # /^LH:/ { + # if (!is_generated) { + # covered += $2 + # } else { + # generated_covered += $2 + # } + # } + # END { + # print "📁 Files:" + # printf " Source files: %d\n", source_files + # printf " Generated files: %d (excluded)\n", generated_files + # print "" + # print "📝 Lines:" + # printf " Total lines: %d\n", total + # printf " Covered lines: %d\n", covered + # printf " Uncovered lines: %d\n", total - covered + # print "" + # if (total == 0) { + # print "⚠️ Coverage: 0%" + # exit 1 + # } else { + # percentage = (covered / total) * 100 + # printf "✅ Coverage: %.2f%%\n", percentage + # print "" + # if (percentage < 60) { + # printf "⚠️ Warning: Coverage is below 60%%\n" + # } else if (percentage < 80) { + # printf "💛 Good: Coverage is above 60%%\n" + # } else { + # printf "💚 Excellent: Coverage is above 80%%\n" + # } + # } + # } + # ' coverage/lcov.info - name: Build apk run: | From 400cc004ef319c6ba20b7d26defbea3b46973579 Mon Sep 17 00:00:00 2001 From: misha Date: Wed, 4 Feb 2026 12:21:09 +0300 Subject: [PATCH 03/58] fix: analizer fixed --- .DS_Store | Bin 10244 -> 12292 bytes lib/core/widgets/feed_icon_button.dart | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/.DS_Store b/.DS_Store index 31ef8af3860347abc958d07f17c7576a3f66fe81..617af8b4ff1f0ec1b05979f5f68b483ee0b2446b 100644 GIT binary patch literal 12292 zcmeHNZEO@p7=EW8^vdTIDDolZw9%l{TFQqQL%5bUf=0m}LJ{!TyIacAz1?%~dbJ?f z_=5yYOb8f5h=4IB`ok!i2nJ0+5%^(@A5jy9m}pFl2~nfQ;1BVc+3nrT-d*`Hkpy=x zvokyMzBA80v-8f(yGKNhl#SJ zQ;xFBOgmX3U0P6uBCcP9IEplAOZo}ZPM{o(((8yV7ezo$BL{uNkqbh=K)^u2K)^u2 zK)}G2Gr-qggphB&cT0n7z(Byj|C#}AA0mi6je6;jn+nr`3*W-?dcK7h0eQY<=VcKu zje6;jo1zARxvEHARpuQrn5&NRh$k2I(jhl>)rpCo(T>+M^G+yC^p5g~5GNLO)8HB~ z5HL{6z+4n5XKy2!&$wrA_I@H`8rfWH>$l-BtE#T49aR^qA3bL5xbYLh4HG9dOm1jw zY@BjKr^UmQ5?Zpa*RlJp)DqjYoer~KU)$>#saQr!8dh&lPJbd7Gq&jQ@I;?z zx1M^eShR951DVOi0@2)kj;>pxz2zG>X7yZ;lwT7MPsk=6+ca0$StDoI)|z$MP4>XD zoSt50ZM4y2-p~h@XLwb1mF{Gbmgw}X(Huv!di9RM^`@54&E-~yoy*zj9wTX6F7gSY zS=_F0blzms;$K`&o1za4XSgo~Axuqf20N_D!#OWyD>*UsP0Zj`vnox1%V~%CoRxipq z_F76%KN)0FN=Eq5Q(wHP*2?%!rQZGUz&?Rb(ofq(bmb*vY|BGp1VWS~oAU5pIq)y_ zk}jj5501?PiV_(a%QvkH_kFfUU@0S4L^P6|jElaDpi63`6fouC@3QeT;U9}5&EVcZ zYT>?x27x$DCil4&eP-+#Xdfu}%!tOL9SwSUp&3U$gI^2oN_8*zbigU9hc&*FtV`;67iv203Wi`gxpoDKc`IxAvgj8Z6>sf0$I&7`K zWS*_A3yle_kHHSca$2rG+o3sY`Q1v}HWR~nEwKt_4CZ!;ZRIq>(jB2-q;!a^TWwe= zd!U2c^sLa0xfP-MP<=c+b?er)1q+)aZFAbTHAlAI)7IJ?nKyUAwr%yH#-_XO?T+`E zY0KWlsm3%Pwo&8U`@)sKsopco98+ut*YIj&j^`gpeKfe&<5;jDb6i?0b4*T{ckH$5 zvqB-*Uk&zGr;Qi^lXP_qs<^e1=Trk`kOs@qhbwFb-3c9A+_%tX%uUQtwFOvsV&kP* zIo8)gB|Uh+OZ56-Gqcdw(PPVS&uV{c<|XwiS`Veadg`Vwpzj1jUFa=MDPK=`KNXES zU`xVU7(j?G0?Noz-Ci*1laNRF>*gnK-(`ytE|DtE{>oCyh`R@hI*MV1;Wp3a-w#wg zRp$jBMMB22d)g1@&YV4k(F1CqR1r^QsWNzKhHa_06?{XjXgUZ10|5g80|5iqIRm%i`9@C*$&boi)JK3q;DUf}O7F0iHsgjAPe}uWUW&o?NB+;u>A9UhcQ* zP!vt$MD6v9J)ov?5nbx9m;1l%s{#*78!`u#WMmvf8#sX9jD@5xBPcJS^zS->>(PH75V*T{!{ouuflIy@Va{b(-^Iz$7zV3r3=Rrvp(D-~P=1_B1IO9og?XRNaWkH#uI z9^Ym~5H}!lnd68&ATLyK5j!!Y;&VJw@i`u8t1Br4qn^%?o1zxsKmQ|uc^pdcZTlWrKCoS$0&(xb8DVK`7wYONN31oD9O!taY@R_PXdZ@ zRKF4ID4loA5mi0~uRO9n3_lfsS{c}2W;3KRBqBSUfkB<|&*UeHX^ak=e=GYkvoL%x zQ=9CgF2|mD>y6UgmSd9()#Z`c!jo62^Dr}fJ}`Nwy28YXI-7X~elaqeEx9|HOGt)& z;ro?euhku!tScl7X9o(&G1(?eE)-Ijyi&MrGP|gyB13+QCeTsLK+ong6eD|Aa`%(# zYkQ7OP81bqhq3EL#hDqVT-ZEQl!q}wOV|snGY6Y<1W`N2ff(cU= zC6KHVHvbh%W1PHNl5=v8q-h+3jT+Dv7GUJ3G8Cgm$f10IAdPV@& Cw%@t{ diff --git a/lib/core/widgets/feed_icon_button.dart b/lib/core/widgets/feed_icon_button.dart index 27caa7cc..bd5748f1 100644 --- a/lib/core/widgets/feed_icon_button.dart +++ b/lib/core/widgets/feed_icon_button.dart @@ -27,7 +27,7 @@ class FeedIconButton extends StatelessWidget { child: Row( mainAxisSize: MainAxisSize.min, children: [ - if (svgIcon != null) svgIcon!, + svgIcon ?? const SizedBox.shrink(), if (icon != null) Icon( icon, From a496edaf4c25a1cd98cc0b4055768e54ecf3274c Mon Sep 17 00:00:00 2001 From: mso7nueh Date: Wed, 4 Feb 2026 13:23:58 +0300 Subject: [PATCH 04/58] feat: Integrate Huawei Mobile Services (HMS) by updating configuration, adding manifest metadata, and differentiating Huawei devices for token submission. --- android/app/agconnect-services.json | 95 ++++++++++++++++++- android/app/src/main/AndroidManifest.xml | 6 ++ .../kotlin/com/eatvioapp/app/MainActivity.kt | 4 + lib/core/firebase_api.dart | 2 +- 4 files changed, 105 insertions(+), 2 deletions(-) diff --git a/android/app/agconnect-services.json b/android/app/agconnect-services.json index 8ca899db..e2f8dec1 100644 --- a/android/app/agconnect-services.json +++ b/android/app/agconnect-services.json @@ -1 +1,94 @@ -{"agcgw":{"url":"connect-drru.hispace.dbankcloud.ru","backurl":"connect-drru.hispace.dbankcloud.cn","websocketurl":"connect-ws-drru.hispace.dbankcloud.ru","websocketbackurl":"connect-ws-drru.hispace.dbankcloud.cn"},"agcgw_all":{"SG":"connect-dra.dbankcloud.cn","SG_back":"connect-dra.hispace.hicloud.com","CN":"connect-drcn.dbankcloud.cn","CN_back":"connect-drcn.hispace.hicloud.com","RU":"connect-drru.hispace.dbankcloud.ru","RU_back":"connect-drru.hispace.dbankcloud.cn","DE":"connect-dre.dbankcloud.cn","DE_back":"connect-dre.hispace.hicloud.com"},"websocketgw_all":{"SG":"connect-ws-dra.hispace.dbankcloud.cn","SG_back":"connect-ws-dra.hispace.dbankcloud.com","CN":"connect-ws-drcn.hispace.dbankcloud.cn","CN_back":"connect-ws-drcn.hispace.dbankcloud.com","RU":"connect-ws-drru.hispace.dbankcloud.ru","RU_back":"connect-ws-drru.hispace.dbankcloud.cn","DE":"connect-ws-dre.hispace.dbankcloud.cn","DE_back":"connect-ws-dre.hispace.dbankcloud.com"},"client":{"cp_id":"20007000036693451","product_id":"461323198429446894","client_id":"1595824543740483264","client_secret":"2A1727B0D31AA9BE1F5D9E8A5CDA1A67B9EF084AC9FB68D6C633A3CCB08ECDEE","project_id":"461323198429446894","app_id":"113238569","api_key":"DQEDAJNRITp9M3uc1rPHyT8PzLc7bnVcKJAeavZZk0TQNMx9ifvBebr+JOmZe9tEvjnLwv7c1hAHXmPGR/gWG86iABDrCVotq+NGnw==","package_name":"com.eatvioapp.app"},"oauth_client":{"client_id":"113238569","client_type":1},"app_info":{"app_id":"113238569","package_name":"com.eatvioapp.app"},"service":{"analytics":{"collector_url":"datacollector-drru.dt.dbankcloud.ru,datacollector-drru.dt.hicloud.com","collector_url_cn":"datacollector-drcn.dt.hicloud.com,datacollector-drcn.dt.dbankcloud.cn","collector_url_de":"datacollector-dre.dt.hicloud.com,datacollector-dre.dt.dbankcloud.cn","collector_url_ru":"datacollector-drru.dt.dbankcloud.ru,datacollector-drru.dt.hicloud.com","collector_url_sg":"datacollector-dra.dt.hicloud.com,datacollector-dra.dt.dbankcloud.cn","resource_id":"p1","channel_id":""},"ml":{"mlservice_url":"ml-api-drru.ai.dbankcloud.ru,ml-api-drru.ai.dbankcloud.cn"},"cloudstorage":{"storage_url":"https://agc-storage-drcn.platform.dbankcloud.cn","storage_url_ru":"https://agc-storage-drru.cloud.huawei.ru","storage_url_sg":"https://ops-dra.agcstorage.link","storage_url_de":"https://ops-dre.agcstorage.link","storage_url_cn":"https://agc-storage-drcn.platform.dbankcloud.cn","storage_url_ru_back":"https://agc-storage-drru.cloud.huawei.ru","storage_url_sg_back":"https://agc-storage-dra.cloud.huawei.asia","storage_url_de_back":"https://agc-storage-dre.cloud.huawei.eu","storage_url_cn_back":"https://agc-storage-drcn.cloud.huawei.com.cn"},"search":{"url":"https://search-drru.cloud.huawei.ru"},"edukit":{"edu_url":"edukit.cloud.huawei.com.cn","dh_url":"edukit.cloud.huawei.com.cn"}},"region":"RU","configuration_version":"3.0","appInfos":[{"package_name":"com.eatvioapp.app","client":{"app_id":"113238569"},"app_info":{"package_name":"com.eatvioapp.app","app_id":"113238569"},"oauth_client":{"client_type":1,"client_id":"113238569"}}]} \ No newline at end of file +{ + "agcgw" : { + "url" : "connect-drru.hispace.dbankcloud.ru", + "backurl" : "connect-drru.hispace.dbankcloud.cn", + "websocketurl" : "connect-ws-drru.hispace.dbankcloud.ru", + "websocketbackurl" : "connect-ws-drru.hispace.dbankcloud.cn" + }, + "agcgw_all" : { + "SG" : "connect-dra.dbankcloud.cn", + "SG_back" : "connect-dra.hispace.hicloud.com", + "CN" : "connect-drcn.dbankcloud.cn", + "CN_back" : "connect-drcn.hispace.hicloud.com", + "RU" : "connect-drru.hispace.dbankcloud.ru", + "RU_back" : "connect-drru.hispace.dbankcloud.cn", + "DE" : "connect-dre.dbankcloud.cn", + "DE_back" : "connect-dre.hispace.hicloud.com" + }, + "websocketgw_all" : { + "SG" : "connect-ws-dra.hispace.dbankcloud.cn", + "SG_back" : "connect-ws-dra.hispace.dbankcloud.com", + "CN" : "connect-ws-drcn.hispace.dbankcloud.cn", + "CN_back" : "connect-ws-drcn.hispace.dbankcloud.com", + "RU" : "connect-ws-drru.hispace.dbankcloud.ru", + "RU_back" : "connect-ws-drru.hispace.dbankcloud.cn", + "DE" : "connect-ws-dre.hispace.dbankcloud.cn", + "DE_back" : "connect-ws-dre.hispace.dbankcloud.com" + }, + "client" : { + "cp_id" : "20007000036693451", + "product_id" : "461323198429446894", + "client_id" : "1595824543740483264", + "client_secret" : "2A1727B0D31AA9BE1F5D9E8A5CDA1A67B9EF084AC9FB68D6C633A3CCB08ECDEE", + "project_id" : "461323198429446894", + "app_id" : "113238569", + "api_key" : "DQEDAJNRITp9M3uc1rPHyT8PzLc7bnVcKJAeavZZk0TQNMx9ifvBebr+JOmZe9tEvjnLwv7c1hAHXmPGR/gWG86iABDrCVotq+NGnw==", + "package_name" : "com.eatvioapp.app" + }, + "oauth_client" : { + "client_id" : "113238569", + "client_type" : 1 + }, + "app_info" : { + "app_id" : "113238569", + "package_name" : "com.eatvioapp.app" + }, + "service" : { + "analytics" : { + "collector_url" : "datacollector-drru.dt.dbankcloud.ru,datacollector-drru.dt.hicloud.com", + "collector_url_cn" : "datacollector-drcn.dt.hicloud.com,datacollector-drcn.dt.dbankcloud.cn", + "collector_url_de" : "datacollector-dre.dt.hicloud.com,datacollector-dre.dt.dbankcloud.cn", + "collector_url_ru" : "datacollector-drru.dt.dbankcloud.ru,datacollector-drru.dt.hicloud.com", + "collector_url_sg" : "datacollector-dra.dt.hicloud.com,datacollector-dra.dt.dbankcloud.cn", + "resource_id" : "p1", + "channel_id" : "" + }, + "ml" : { + "mlservice_url" : "ml-api-drru.ai.dbankcloud.ru,ml-api-drru.ai.dbankcloud.cn" + }, + "cloudstorage" : { + "storage_url" : "https://agc-storage-drcn.platform.dbankcloud.cn", + "storage_url_ru" : "https://agc-storage-drru.cloud.huawei.ru", + "storage_url_sg" : "https://ops-dra.agcstorage.link", + "storage_url_de" : "https://ops-dre.agcstorage.link", + "storage_url_cn" : "https://agc-storage-drcn.platform.dbankcloud.cn", + "storage_url_ru_back" : "https://agc-storage-drru.cloud.huawei.ru", + "storage_url_sg_back" : "https://agc-storage-dra.cloud.huawei.asia", + "storage_url_de_back" : "https://agc-storage-dre.cloud.huawei.eu", + "storage_url_cn_back" : "https://agc-storage-drcn.cloud.huawei.com.cn" + }, + "search" : { + "url" : "https://search-drru.cloud.huawei.ru" + }, + "edukit" : { + "edu_url" : "edukit.cloud.huawei.com.cn", + "dh_url" : "edukit.cloud.huawei.com.cn" + } + }, + "region" : "RU", + "configuration_version" : "3.0", + "appInfos" : [ { + "package_name" : "com.eatvioapp.app", + "client" : { + "app_id" : "113238569" + }, + "app_info" : { + "package_name" : "com.eatvioapp.app", + "app_id" : "113238569" + }, + "oauth_client" : { + "client_type" : 1, + "client_id" : "113238569" + } + } ] +} \ No newline at end of file diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 864f1a2c..e7a94938 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -21,6 +21,12 @@ + + ().post( '/api/anonymous_token', data: { - 'platform': Platform.isIOS ? 'ios' : 'android', + 'platform': Platform.isIOS ? 'ios' : (await _isHuaweiDevice()) ? 'huawei' : 'android', 'hw_id': Platform.isIOS ? (await deviceInfo.iosInfo).identifierForVendor : (await deviceInfo.androidInfo).id, 'token': await getToken(), }, From 296adc8229ab265a6e4b83b823e69eaa3356deb6 Mon Sep 17 00:00:00 2001 From: mso7nueh Date: Wed, 4 Feb 2026 15:50:28 +0300 Subject: [PATCH 05/58] build: bump version to 1.3.17+97 --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 134bb2ea..0e6808b1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: eatvio_app description: A new Flutter project. -version: 1.3.15+96 +version: 1.3.17+97 publish_to: 'none' environment: From 97418e11d45d6ce22b018ddd954d556f8fa11b92 Mon Sep 17 00:00:00 2001 From: mso7nueh Date: Thu, 5 Feb 2026 14:40:55 +0300 Subject: [PATCH 06/58] feat: update add product modal to reflect selection state with icons --- .../diary/presentation/widgets/add_product_modal.dart | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/features/diary/presentation/widgets/add_product_modal.dart b/lib/features/diary/presentation/widgets/add_product_modal.dart index c3d9067d..a0895bf9 100644 --- a/lib/features/diary/presentation/widgets/add_product_modal.dart +++ b/lib/features/diary/presentation/widgets/add_product_modal.dart @@ -132,7 +132,12 @@ class _AddProductModalState extends State { subtitle: glossariesList[i].grInPortion == 0 ? null : Text('${glossariesList[i].grInPortion} г', style: TextStyle(height: 1, color: Colors.grey.shade600)), - trailing: Icon(Icons.add_rounded, color: Colors.grey.shade400), + trailing: Icon( + productNameController.text.trim().toLowerCase() == glossariesList[i].name.toLowerCase() + ? Icons.radio_button_checked + : Icons.radio_button_off, + color: Colors.blueAccent, + ), contentPadding: const EdgeInsets.only(left: 12), ); }, @@ -166,7 +171,9 @@ class _AddProductModalState extends State { InkWell( onTap: () { // Автоматически выбираем первый элемент, если текст точно совпадает - if (selectedGlossary == null && glossariesList.isNotEmpty && productNameController.text.trim().toLowerCase() == glossariesList[0].name.toLowerCase()) { + if (selectedGlossary == null && + glossariesList.isNotEmpty && + productNameController.text.trim().toLowerCase() == glossariesList[0].name.toLowerCase()) { selectedGlossary = glossariesList[0]; } From bf110b70c1da5e59877059643988401804c6923d Mon Sep 17 00:00:00 2001 From: mso7nueh Date: Thu, 5 Feb 2026 14:41:25 +0300 Subject: [PATCH 07/58] feat: enhance BottomNavigationBarWidget with DEV logo display and refactor page list initialization --- .../widgets/bottom_navigation_bar_widget.dart | 258 ++++++++---------- 1 file changed, 115 insertions(+), 143 deletions(-) diff --git a/lib/features/bottom_navigation_bar/presentation/widgets/bottom_navigation_bar_widget.dart b/lib/features/bottom_navigation_bar/presentation/widgets/bottom_navigation_bar_widget.dart index f177d57a..efac96bf 100644 --- a/lib/features/bottom_navigation_bar/presentation/widgets/bottom_navigation_bar_widget.dart +++ b/lib/features/bottom_navigation_bar/presentation/widgets/bottom_navigation_bar_widget.dart @@ -1,5 +1,6 @@ import 'package:badges/badges.dart' as badges; import 'package:eatvio_app/core/app_config.dart'; +import 'package:eatvio_app/core/env_config.dart'; import 'package:eatvio_app/core/locator_service.dart'; import 'package:eatvio_app/core/utils/image_constant.dart'; import 'package:eatvio_app/features/dev_tools/presentation/dev_tools_screen.dart'; @@ -19,10 +20,7 @@ import 'package:go_router/go_router.dart'; class BottomNavigationBarWidget extends StatefulWidget { final Widget child; - const BottomNavigationBarWidget({ - super.key, - required this.child, - }); + const BottomNavigationBarWidget({super.key, required this.child}); @override State createState() => _BottomNavigationBarWidgetState(); @@ -35,24 +33,9 @@ class _BottomNavigationBarWidgetState extends State { List _getPages(bool isConsultant) { if (isConsultant) { - return [ - '/my-profile-screen', - '/feed-screen', - '/diary-screen', - '/consultations-screen', - '/chat-select-screen', - '/notifications-screen', - '/menu-screen', - ]; + return ['/my-profile-screen', '/feed-screen', '/diary-screen', '/consultations-screen', '/chat-select-screen', '/notifications-screen', '/menu-screen']; } else { - return [ - '/my-profile-screen', - '/feed-screen', - '/diary-screen', - '/chat-select-screen', - '/notifications-screen', - '/menu-screen', - ]; + return ['/my-profile-screen', '/feed-screen', '/diary-screen', '/chat-select-screen', '/notifications-screen', '/menu-screen']; } } @@ -83,6 +66,49 @@ class _BottomNavigationBarWidgetState extends State { } } + static const Color _devAccentColor = Color(0xFFF59E0B); // amber-500 + + Widget _buildLogoIcon() { + final isProduction = sl().baseUrl == EnvConfig.baseUrl; + final logo = SvgPicture.asset( + ImageConstant.imgLogo, + height: 32, + colorFilter: isProduction ? null : const ColorFilter.mode(_devAccentColor, BlendMode.srcIn), + ); + if (isProduction) return logo; + return Stack( + clipBehavior: Clip.none, + alignment: Alignment.center, + children: [ + Container( + padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 4), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + border: Border.all(color: _devAccentColor.withValues(alpha: 0.6), width: 1.5), + color: _devAccentColor.withValues(alpha: 0.08), + ), + child: logo, + ), + Positioned( + top: -6, + right: -4, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 1), + decoration: BoxDecoration( + color: _devAccentColor, + borderRadius: BorderRadius.circular(4), + boxShadow: [BoxShadow(color: _devAccentColor.withValues(alpha: 0.4), blurRadius: 4, offset: const Offset(0, 1))], + ), + child: const Text( + 'DEV', + style: TextStyle(fontSize: 8, fontWeight: FontWeight.w700, color: Colors.white, letterSpacing: 0.3, height: 1.0), + ), + ), + ), + ], + ); + } + @override Widget build(BuildContext context) { final s = S.of(context); @@ -91,10 +117,10 @@ class _BottomNavigationBarWidgetState extends State { if (state is ProfileAvatarStateLoaded) { final profileModel = state.profile; final isConsultant = profileModel.roles.contains('consultant'); - + // Инициализируем pages на основе profileModel pages ??= _getPages(isConsultant); - + return Scaffold( body: widget.child, bottomNavigationBar: BottomNavigationBar( @@ -117,52 +143,26 @@ class _BottomNavigationBarWidgetState extends State { ), BottomNavigationBarItem( label: 'Лента', - icon: Padding( - padding: const EdgeInsets.only(bottom: 2), - child: SvgPicture.asset( - colorFilter: const ColorFilter.mode(Colors.grey, BlendMode.srcIn), - width: 20, - height: 20, - ImageConstant.icnFeed, - ), - ), - activeIcon: Padding( - padding: const EdgeInsets.only(bottom: 2), - child: SvgPicture.asset( - ImageConstant.icnFeed, - width: 20, - height: 20, - colorFilter: ColorFilter.mode( - appTheme.blueA400, - BlendMode.srcIn, - ), - ), - ), + icon: Padding( + padding: const EdgeInsets.only(bottom: 2), + child: SvgPicture.asset(colorFilter: const ColorFilter.mode(Colors.grey, BlendMode.srcIn), width: 20, height: 20, ImageConstant.icnFeed), + ), + activeIcon: Padding( + padding: const EdgeInsets.only(bottom: 2), + child: SvgPicture.asset(ImageConstant.icnFeed, width: 20, height: 20, colorFilter: ColorFilter.mode(appTheme.blueA400, BlendMode.srcIn)), + ), ), BottomNavigationBarItem( label: s.diary, - icon: Padding( - padding: const EdgeInsets.only(bottom: 2), - child: SvgPicture.asset( - colorFilter: const ColorFilter.mode(Colors.grey, BlendMode.srcIn), - ImageConstant.icnDiary, - width: 20, - height: 20, - ), - ), - activeIcon: Padding( - padding: const EdgeInsets.only(bottom: 2), - child: SvgPicture.asset( - ImageConstant.icnDiary, - width: 20, - height: 20, - colorFilter: ColorFilter.mode( - appTheme.blueA400, - BlendMode.srcIn, + icon: Padding( + padding: const EdgeInsets.only(bottom: 2), + child: SvgPicture.asset(colorFilter: const ColorFilter.mode(Colors.grey, BlendMode.srcIn), ImageConstant.icnDiary, width: 20, height: 20), + ), + activeIcon: Padding( + padding: const EdgeInsets.only(bottom: 2), + child: SvgPicture.asset(ImageConstant.icnDiary, width: 20, height: 20, colorFilter: ColorFilter.mode(appTheme.blueA400, BlendMode.srcIn)), + ), ), - ), - ), - ), if (profileModel.roles.contains('consultant')) BottomNavigationBarItem( label: 'Консультации', @@ -181,97 +181,69 @@ class _BottomNavigationBarWidgetState extends State { 'assets/icons/consultations.svg', width: 20, height: 20, - colorFilter: ColorFilter.mode( - appTheme.blueA400, - BlendMode.srcIn, - ), - ), - ), - ), + colorFilter: ColorFilter.mode(appTheme.blueA400, BlendMode.srcIn), + ), + ), + ), BottomNavigationBarItem( label: s.chat, - icon: BlocBuilder( - builder: (BuildContext context, ChatsState state) { - final hasUnreadMessages = state.chatsWithUnreadMessagesCount > 0; - return badges.Badge( - position: badges.BadgePosition.topEnd(), - badgeAnimation: const badges.BadgeAnimation.fade(), - badgeStyle: badges.BadgeStyle( - badgeColor: hasUnreadMessages ? Colors.red : Colors.transparent, + icon: BlocBuilder( + builder: (BuildContext context, ChatsState state) { + final hasUnreadMessages = state.chatsWithUnreadMessagesCount > 0; + return badges.Badge( + position: badges.BadgePosition.topEnd(), + badgeAnimation: const badges.BadgeAnimation.fade(), + badgeStyle: badges.BadgeStyle(badgeColor: hasUnreadMessages ? Colors.red : Colors.transparent), + badgeContent: Text( + hasUnreadMessages ? state.chatsWithUnreadMessagesCount.toString() : '', + style: TextStyle(color: hasUnreadMessages ? Colors.white : Colors.transparent, fontSize: 8), + ), + child: Padding( + padding: const EdgeInsets.only(bottom: 2), + child: SvgPicture.asset( + ImageConstant.icnChat, + width: 20, + height: 20, + colorFilter: pages![currentBottomNavBarIndex] == '/chat-select-screen' + ? ColorFilter.mode(appTheme.blueA400, BlendMode.srcIn) + : const ColorFilter.mode(Colors.grey, BlendMode.srcIn), + ), + ), + ); + }, ), - badgeContent: Text( - hasUnreadMessages ? state.chatsWithUnreadMessagesCount.toString() : '', - style: TextStyle( - color: hasUnreadMessages ? Colors.white : Colors.transparent, - fontSize: 8, - ), - ), - child: Padding( - padding: const EdgeInsets.only(bottom: 2), - child: SvgPicture.asset( - ImageConstant.icnChat, - width: 20, - height: 20, - colorFilter: pages![currentBottomNavBarIndex] == '/chat-select-screen' - ? ColorFilter.mode( - appTheme.blueA400, - BlendMode.srcIn, - ) - : const ColorFilter.mode(Colors.grey, BlendMode.srcIn), - ), - ), - ); - }, - ), ), BottomNavigationBarItem( label: 'Уведомления', - icon: BlocBuilder( - builder: (BuildContext context, NotificationsState state) { - return badges.Badge( - position: badges.BadgePosition.topEnd(), - badgeAnimation: const badges.BadgeAnimation.fade(), - showBadge: state is NotificationsStateLoaded && state.hasUnreadNotifications, - badgeContent: const Text( - '', - style: TextStyle( - color: Colors.white, - fontSize: 8, - ), - ), - child: Padding( - padding: const EdgeInsets.only(bottom: 2), - child: SvgPicture.asset( - ImageConstant.icnBell, - colorFilter: pages![currentBottomNavBarIndex] == '/notifications-screen' - ? ColorFilter.mode( - appTheme.blueA400, - BlendMode.srcIn, - ) - : const ColorFilter.mode(Colors.grey, BlendMode.srcIn), - width: 20, - height: 20, - ), - ), - ); - }, - ), - ), - BottomNavigationBarItem( - label: '', - icon: SvgPicture.asset( - ImageConstant.imgLogo, - height: 32, + icon: BlocBuilder( + builder: (BuildContext context, NotificationsState state) { + return badges.Badge( + position: badges.BadgePosition.topEnd(), + badgeAnimation: const badges.BadgeAnimation.fade(), + showBadge: state is NotificationsStateLoaded && state.hasUnreadNotifications, + badgeContent: const Text('', style: TextStyle(color: Colors.white, fontSize: 8)), + child: Padding( + padding: const EdgeInsets.only(bottom: 2), + child: SvgPicture.asset( + ImageConstant.icnBell, + colorFilter: pages![currentBottomNavBarIndex] == '/notifications-screen' + ? ColorFilter.mode(appTheme.blueA400, BlendMode.srcIn) + : const ColorFilter.mode(Colors.grey, BlendMode.srcIn), + width: 20, + height: 20, + ), + ), + ); + }, ), ), + BottomNavigationBarItem(label: '', icon: _buildLogoIcon()), ], ), ); } // Показываем child даже когда профиль еще загружается, чтобы экраны могли инициализироваться - return Scaffold( - body: widget.child, - ); + return Scaffold(body: widget.child); }, ); } From c91e83adaf84695b14bbb8488ecd367abd4963e5 Mon Sep 17 00:00:00 2001 From: misha Date: Mon, 9 Feb 2026 10:39:51 +0300 Subject: [PATCH 08/58] feat(my_diets): fixed draft, active and personal diets creation, updating --- lib/core/routing/other_routes.dart | 17 ++-- .../success_published_diet_bottom_sheet.dart | 46 +++++------ .../presentation/create_diet_screen.dart | 53 +++++++----- .../presentation/cubit/create_diet_cubit.dart | 46 +++++++++-- .../presentation/diet_preview_screen.dart | 56 ++++++++++++- .../presentation/widgets/add_meal_widget.dart | 4 +- .../repositories/create_diet_repository.dart | 81 ++++++++++--------- .../widgets/add_portion_widget.dart | 4 +- .../feed/models/diets_pagination_model.dart | 2 +- .../feed/presentation/pages/feed_screen.dart | 2 +- .../presentation/my_diets_screen.dart | 2 +- .../presentation/widgets/my_diet_widget.dart | 18 ++++- 12 files changed, 221 insertions(+), 110 deletions(-) diff --git a/lib/core/routing/other_routes.dart b/lib/core/routing/other_routes.dart index c09f8983..0e30336e 100644 --- a/lib/core/routing/other_routes.dart +++ b/lib/core/routing/other_routes.dart @@ -71,16 +71,16 @@ class OtherRoutes { try { final profileRepository = sl(); final profile = await profileRepository.getMyProfile(); - + final firebaseApi = sl(); - await firebaseApi.registerPushToken(); + await firebaseApi.registerPushToken(); if (profile.emailVerifiedAt != null || (prefs.getBool(PrefsKeys.isEmailVerificationSkipped) ?? false)) { return RouteConstants.feedScreen; } return RouteConstants.verifyEmailScreen; } catch (e) { - return '/auth-screen'; + return '/auth-screen'; } } else { return '/auth-screen'; @@ -111,12 +111,12 @@ class OtherRoutes { // При любой ошибке пропускаем проверку версии return null; }); - + if (versions == null) { // Если проверка не удалась, пропускаем экран обновления return RouteConstants.welcomeScreen; } - + PackageInfo packageInfo = await PackageInfo.fromPlatform(); String version = packageInfo.version; Version currentVersion = Version.parse(version); @@ -163,15 +163,16 @@ class OtherRoutes { final diet = data['diet'] as Diet?; final isEditing = data['isEditing'] as bool?; return CreateDietScreen(diet: diet, isEditing: isEditing); - }, ), GoRoute( path: DietPreviewScreen.path, name: DietPreviewScreen.name, builder: (context, state) { - final data = state.extra as Diet; - return DietPreviewScreen(diet: data); + final data = state.extra as Map; + final diet = data['diet'] as Diet; + final isImageInBase64 = data['isImageInBase64'] as bool; + return DietPreviewScreen(diet: diet, isImageInBase64: isImageInBase64,); }, ), GoRoute(path: RouteConstants.createPlanScreen, name: 'createPlanScreen', builder: (context, state) => const CreatePlanScreen()), diff --git a/lib/core/widgets/success_published_diet_bottom_sheet.dart b/lib/core/widgets/success_published_diet_bottom_sheet.dart index 223ed220..5b5625b7 100644 --- a/lib/core/widgets/success_published_diet_bottom_sheet.dart +++ b/lib/core/widgets/success_published_diet_bottom_sheet.dart @@ -8,32 +8,33 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; class SuccessPublishedDietOrPlanBottomSheet extends StatelessWidget { - const SuccessPublishedDietOrPlanBottomSheet({super.key, required this.title, this.isPlan = false}); + const SuccessPublishedDietOrPlanBottomSheet({super.key, required this.title, this.isPlan = false, this.isDrafted, this.isUpdated}); final String title; final bool isPlan; + final bool? isDrafted; + final bool? isUpdated; @override Widget build(BuildContext context) { final s = S.of(context); + final String text; + if (isDrafted == true) { + text = 'cохранена в черновиках'; + } else if (isUpdated == true) { + text = 'успешно обновлена'; + } else if (isPlan == true) { + text = 'опубликован'; + } else { + text = 'опубликованна'; + } + return Column( children: [ - const SizedBox( - height: 20, - ), + const SizedBox(height: 20), const BottomSheetHandlerBar(), - const SizedBox( - height: 20, - ), - Text( - title, - style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black), - ), - Text( - isPlan ? 'опубликован' : 'опубликованна', - style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black), - ), - const SizedBox( - height: 20, - ), + const SizedBox(height: 20), + Text(title, style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black)), + Text(text, style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black)), + const SizedBox(height: 20), Padding( padding: const EdgeInsets.symmetric(horizontal: 20), child: CustomOutlinedButton( @@ -43,15 +44,10 @@ class SuccessPublishedDietOrPlanBottomSheet extends StatelessWidget { text: s.ok, height: 52, buttonTextStyle: TextStyles.textSSemibold.copyWith(color: appTheme.whiteA700), - buttonStyle: ButtonStyle( - side: const WidgetStatePropertyAll(BorderSide.none), - backgroundColor: WidgetStatePropertyAll(appTheme.blueA400), - ), + buttonStyle: ButtonStyle(side: const WidgetStatePropertyAll(BorderSide.none), backgroundColor: WidgetStatePropertyAll(appTheme.blueA400)), ), ), - const SizedBox( - height: 20, - ), + const SizedBox(height: 20), ], ); } diff --git a/lib/features/create_diet/presentation/create_diet_screen.dart b/lib/features/create_diet/presentation/create_diet_screen.dart index 0abb37a9..6596fd87 100644 --- a/lib/features/create_diet/presentation/create_diet_screen.dart +++ b/lib/features/create_diet/presentation/create_diet_screen.dart @@ -32,21 +32,21 @@ class CreateDietScreen extends StatelessWidget { const CreateDietScreen({super.key, this.diet, this.isEditing}); final Diet? diet; - final bool? isEditing; + final bool? isEditing; @override Widget build(BuildContext context) { return BlocProvider( create: (context) => sl()..setInitialData(diet), - child: CreateDietScreenContent(diet: diet), + child: CreateDietScreenContent(diet: diet, isEditing: isEditing), ); } } class CreateDietScreenContent extends StatefulWidget { - const CreateDietScreenContent({super.key, this.diet}); + const CreateDietScreenContent({super.key, this.diet, this.isEditing}); final Diet? diet; - + final bool? isEditing; @override State createState() => _CreateDietScreenContentState(); } @@ -88,7 +88,7 @@ class _CreateDietScreenContentState extends State { context.pop(); showBottomSheetWithBlur( context: context, - child: SuccessPublishedDietOrPlanBottomSheet(title: '${state.dietData.name ?? 'Диета'} сохраненна в черновиках'), + child: SuccessPublishedDietOrPlanBottomSheet(title: state.dietData.name ?? 'Диета', isDrafted: true), ); }); } @@ -182,14 +182,21 @@ class _CreateDietScreenContentState extends State { children: [ CustomOutlinedButton( onPressed: () async { - context.read().setDietState(DietState.draft); - if (widget.diet != null) { + if (widget.diet != null && widget.isEditing == true) { + if (state.dietState == DietState.draft) { + context.read().setDietState(DietState.active); + } else { + context.read().setDietState(DietState.draft); + } final result = await context.read().updateDiet(widget.diet!.id); if (result == 'error') { if (!context.mounted) return; showErrorDialog(context, title: '${s.could_not_update_diet} ${state.dietData.name}'); } } else { + if (state.dietState != DietState.draft) { + context.read().setDietState(DietState.draft); + } final result = await context.read().createDiet(); if (result == 'error') { if (!context.mounted) return; @@ -197,15 +204,7 @@ class _CreateDietScreenContentState extends State { } } }, - // onPressed: state.isDietPublishing - // ? () {} - // : () async { - // final diet = await context.read().getDietForPreview(); - // if (diet == null) return; - // if (!context.mounted) return; - // GoRouter.of(context).pushNamed(DietPreviewScreen.name, extra: diet); - // }, - text: "В черновик", + text: state.dietState == DietState.draft && widget.isEditing == true ? s.publish : "В черновик", height: 52, buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.gray), buttonStyle: ButtonStyle( @@ -221,7 +220,9 @@ class _CreateDietScreenContentState extends State { final diet = await context.read().getDietForPreview(); if (diet == null) return; if (!context.mounted) return; - GoRouter.of(context).pushNamed(DietPreviewScreen.name, extra: diet); + GoRouter.of( + context, + ).pushNamed(DietPreviewScreen.name, extra: {'diet': diet, 'isImageInBase64': state.dietData.imageUrl == null}); }, text: s.preview, height: 52, @@ -237,13 +238,21 @@ class _CreateDietScreenContentState extends State { else CustomOutlinedButton( onPressed: () async { - if (widget.diet != null) { - final result = await context.read().updateDiet( - widget.diet!.id, - ); + if (widget.diet != null && widget.isEditing == true) { + final result = await context.read().updateDiet(widget.diet!.id); if (result == 'error') { if (!context.mounted) return; showErrorDialog(context, title: '${s.could_not_update_diet} ${state.dietData.name}'); + } else { + Future.delayed(const Duration(milliseconds: 500), () { + if (!context.mounted) return; + context.pop(); + showBottomSheetWithBlur( + userRootNavigator: true, + context: context, + child: SuccessPublishedDietOrPlanBottomSheet(title: state.dietData.name ?? 'Диета', isUpdated: true), + ); + }); } } else { final result = await context.read().createDiet(); @@ -253,7 +262,7 @@ class _CreateDietScreenContentState extends State { } } }, - text: widget.diet != null ? s.save : s.publish, + text: widget.diet != null && widget.isEditing == true ? s.save : s.publish, height: 52, buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700), buttonStyle: ButtonStyle( 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 9cc64123..22803a76 100644 --- a/lib/features/create_diet/presentation/cubit/create_diet_cubit.dart +++ b/lib/features/create_diet/presentation/cubit/create_diet_cubit.dart @@ -49,6 +49,12 @@ class CreateDietCubit extends Cubit { type['carb'] = diet.type.carb ?? false; type['light'] = diet.type.light ?? false; final appConfig = sl(); + String? imageUrl; + if (diet.image?.startsWith('https') == true) { + imageUrl = diet.image; + } else if (diet.image != null && diet.image?.isNotEmpty == true) { + imageUrl = '${appConfig.baseUrl}/storage/${diet.image}'; + } emit( state.copyWith.dietData( dailySchedule: dailySchedule, @@ -57,8 +63,7 @@ class CreateDietCubit extends Cubit { name: diet.name, duration: diet.duration, type: type, - // Avoid network image URLs during tests to prevent HTTP fetch attempts - imageUrl: appConfig.buildType == AppBuildType.tests ? null : '${appConfig.baseUrl}/storage/${diet.image}', + imageUrl: appConfig.buildType == AppBuildType.tests ? null : imageUrl, ), ); emit(state.copyWith(dietState: diet.state, durationIndex: durationValues.indexOf(diet.duration))); @@ -196,7 +201,6 @@ class CreateDietCubit extends Cubit { } Future getDietForPreview() async { - if (!_isFromValid()) return null; final authorProfile = await profileRepository.getMyProfile(); final imageBytes = (await state.dietData.image?.readAsBytes()) ?? []; String image = base64.encode(imageBytes); @@ -305,7 +309,10 @@ class CreateDietCubit extends Cubit { Future createDiet() async { emit(state.copyWith(isDietPublishing: true)); - if (!_isFromValid()) { + if ([DietState.active, DietState.personal].contains(state.dietState) && !_isFromValid()) { + emit(state.copyWith(isDietPublishing: false)); + return 'validation'; + } else if (!_isFormValidForDraft()) { emit(state.copyWith(isDietPublishing: false)); return 'validation'; } @@ -324,12 +331,12 @@ class CreateDietCubit extends Cubit { name: state.dietData.name ?? '', duration: state.dietData.duration ?? 7, state: dietState, - image: image ?? file!, + image: image ?? file, 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 ?? [], ); @@ -343,7 +350,12 @@ class CreateDietCubit extends Cubit { } Future updateDiet(int dietId) async { - // if (!_isFromValid()) return 'error'; + if ([DietState.active, DietState.personal].contains(state.dietState) && !_isFromValid()) { + return 'error'; + } else if (!_isFormValidForDraft()) { + return 'error'; + } + try { File? image; if (state.dietData.image != null) { @@ -359,7 +371,7 @@ class CreateDietCubit extends Cubit { 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?.isEmpty ?? true ? null : state.dietData.details, dailySchedule: state.dietData.dailySchedule, ); return null; @@ -369,6 +381,24 @@ class CreateDietCubit extends Cubit { } } + bool _isFormValidForDraft() { + bool result = true; + if ((state.dietData.name?.length ?? 0) < 3) { + result = false; + emit(state.copyWith(dietNameError: 'Длинна названия не может быть меньше 3 символов')); + } + if (state.dietData.name == null || state.dietData.name == '') { + result = false; + emit(state.copyWith(dietNameError: 'Поле не заполнено')); + } + + if (state.dietData.details?.length != null && (state.dietData.details?.isNotEmpty ?? false) && (state.dietData.details?.length ?? 4) < 3) { + result = false; + emit(state.copyWith(dietDescriptionError: 'Длинна описания не может быть меньше 3 символов')); + } + return result; + } + bool _isFromValid() { bool result = true; if ((state.dietData.name?.length ?? 0) < 3) { diff --git a/lib/features/create_diet/presentation/diet_preview_screen.dart b/lib/features/create_diet/presentation/diet_preview_screen.dart index 2d059896..c7482cd4 100644 --- a/lib/features/create_diet/presentation/diet_preview_screen.dart +++ b/lib/features/create_diet/presentation/diet_preview_screen.dart @@ -1,6 +1,9 @@ import 'dart:convert'; import 'dart:typed_data'; +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:eatvio_app/core/app_config.dart'; import 'package:eatvio_app/core/enums/product_type.dart'; +import 'package:eatvio_app/core/locator_service.dart'; import 'package:eatvio_app/core/utils/localization_utils.dart'; import 'package:eatvio_app/core/widgets/custom_divider.dart'; import 'package:eatvio_app/core/widgets/info_row.dart'; @@ -18,10 +21,11 @@ import 'package:flutter_svg/svg.dart'; class DietPreviewScreen extends StatefulWidget { final Diet diet; + final bool isImageInBase64; late final List selectedIndexes; static const name = 'createDietScreenPreview'; static const path = '/create-diet-screen/preview'; - DietPreviewScreen({super.key, required this.diet}) { + DietPreviewScreen({super.key, required this.diet, required this.isImageInBase64}) { selectedIndexes = List.generate(diet.dailySchedule.length, (_) => 0); } @@ -35,7 +39,7 @@ class _DietPreviewScreenState extends State { @override void initState() { super.initState(); - if (widget.diet.image != null) { + if (widget.diet.image != null && widget.isImageInBase64) { imageData = base64.decode(widget.diet.image!); } } @@ -109,6 +113,54 @@ class _DietPreviewScreenState extends State { ), ), ], + ) + else if (!widget.isImageInBase64 && widget.diet.image != null && (widget.diet.image?.isNotEmpty ?? false)) + Stack( + children: [ + CachedNetworkImage( + imageUrl: widget.diet.image?.startsWith('https') == true + ? widget.diet.image! + : '${sl().baseUrl}/storage/${widget.diet.image}', + height: 300, + width: MediaQuery.sizeOf(context).width, + fit: BoxFit.fill, + ), + Positioned( + bottom: 16, + left: 0, + right: 0, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + InfoBouble( + backgroundColor: appTheme.whiteA700, + children: [ + SvgPicture.asset('assets/icons/eye.svg', width: 18, height: 12), + const SizedBox(width: 8), + Text(widget.diet.totalViews.toString(), style: TextStyles.textXSSemibold.copyWith(color: appTheme.black)), + ], + ), + const SizedBox(width: 12), + GestureDetector( + onTap: () {}, + child: InfoBouble( + backgroundColor: appTheme.whiteA700, + children: [ + SvgPicture.asset( + 'assets/icons/heart.svg', + width: 14, + height: 12, + colorFilter: widget.diet.wasLikedByMe ? ColorFilter.mode(appTheme.blueA400, BlendMode.srcIn) : null, + ), + const SizedBox(width: 8), + Text(widget.diet.totalLikes.toString(), style: TextStyles.textXSSemibold.copyWith(color: appTheme.black)), + ], + ), + ), + ], + ), + ), + ], ), const SizedBox(height: 16), Padding( 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 ececed87..a8eb36d5 100644 --- a/lib/features/create_diet/presentation/widgets/add_meal_widget.dart +++ b/lib/features/create_diet/presentation/widgets/add_meal_widget.dart @@ -185,7 +185,7 @@ class _AddMealWidgetState extends State { dailyNutrition: DailyNutrition(energy: calories, proteins: proteins, fats: fats, carbohydrates: carbohydrates), quantity: int.parse(quantityController.text), name: glossary.name, - productNutritionalValueId: glossary.id.toString(), + productNutritionalValueId: glossary.id, portion: measurementUnits, ); if (portionIndex == null) { @@ -212,7 +212,7 @@ class _AddMealWidgetState extends State { dailyNutrition: DailyNutrition(energy: calories, proteins: proteins, fats: fats, carbohydrates: carbohydrates), quantity: int.parse(quantityController.text), name: newGlossary.name, - productNutritionalValueId: newGlossary.id.toString(), + productNutritionalValueId: newGlossary.id, portion: measurementUnits, ); if (portionIndex == null) { diff --git a/lib/features/create_diet/repositories/create_diet_repository.dart b/lib/features/create_diet/repositories/create_diet_repository.dart index 84c17072..546d975d 100644 --- a/lib/features/create_diet/repositories/create_diet_repository.dart +++ b/lib/features/create_diet/repositories/create_diet_repository.dart @@ -11,44 +11,56 @@ class CreateDietRepository { 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, + required String? meatType, + required String? carbType, + required String? vegType, + required String? lightType, + required String? details, + required File? image, + required List>>? dailySchedule, }) { 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'], - }); + if (dailySchedule != null) { + 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'], + }); + } } } } + if (details != null) { + dietMap.addAll({'description_md': details}); + } - dietMap.addAll({ - 'name': name, - 'description_md': 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, - ), - }); + if (meatType != null) { + dietMap.addAll({'type[meat]': meatType}); + } - final formData = FormData.fromMap(dietMap); + if (carbType != null) { + dietMap.addAll({'type[carb]': carbType}); + } + + if (vegType != null) { + dietMap.addAll({'type[veg]': vegType}); + } + + if (lightType != null) { + dietMap.addAll({'type[light]': lightType}); + } + if (image != null) { + dietMap.addAll({'image': MultipartFile.fromFileSync(image.path, filename: image.path.split(Platform.pathSeparator).last)}); + } + + dietMap.addAll({'name': name, 'duration': duration}); + + final formData = FormData.fromMap(dietMap); switch (state) { case DietState.active: return createDietService.createPublishedDiet(formData: formData); @@ -133,12 +145,7 @@ class CreateDietRepository { dietMap.addAll({'type[light]': lightType}); } if (image != null) { - dietMap.addAll({ - 'image': MultipartFile.fromFileSync( - image.path, - filename: image.path.split(Platform.pathSeparator).last, - ), - }); + dietMap.addAll({'image': MultipartFile.fromFileSync(image.path, filename: image.path.split(Platform.pathSeparator).last)}); } final formData = FormData.fromMap(dietMap); diff --git a/lib/features/create_recipe/presentation/widgets/add_portion_widget.dart b/lib/features/create_recipe/presentation/widgets/add_portion_widget.dart index 597bc09b..9fd0a25d 100644 --- a/lib/features/create_recipe/presentation/widgets/add_portion_widget.dart +++ b/lib/features/create_recipe/presentation/widgets/add_portion_widget.dart @@ -182,7 +182,7 @@ class _AddPortionWidgetState extends State { dailyNutrition: DailyNutrition(energy: calories, proteins: proteins, fats: fats, carbohydrates: carbohydrates), quantity: int.parse(quantityController.text), name: glossary.name, - productNutritionalValueId: glossary.id.toString(), + productNutritionalValueId: glossary.id, portion: measurementUnits, ); if (portionIndex == null) { @@ -212,7 +212,7 @@ class _AddPortionWidgetState extends State { dailyNutrition: DailyNutrition(energy: calories, proteins: proteins, fats: fats, carbohydrates: carbohydrates), quantity: int.parse(quantityController.text), name: newGlossary.name, - productNutritionalValueId: newGlossary.id.toString(), + productNutritionalValueId: newGlossary.id, portion: measurementUnits, ); if (portionIndex == null) { diff --git a/lib/features/feed/models/diets_pagination_model.dart b/lib/features/feed/models/diets_pagination_model.dart index 857a529b..aec36295 100644 --- a/lib/features/feed/models/diets_pagination_model.dart +++ b/lib/features/feed/models/diets_pagination_model.dart @@ -105,7 +105,7 @@ abstract class DailySchedule with _$DailySchedule { @JsonKey(name: "portion") required Portion portion, @JsonKey(name: "quantity", fromJson: _parseQuantity) required int quantity, @JsonKey(name: 'nutrition', includeToJson: false) DailyNutrition? dailyNutrition, - @JsonKey(name: 'product_nutritional_value_id', includeToJson: true, includeFromJson: false, includeIfNull: false) String? productNutritionalValueId, + @JsonKey(name: 'product_nutritional_value_id', includeToJson: true, includeFromJson: true, includeIfNull: false) int? productNutritionalValueId, }) = _DailySchedule; factory DailySchedule.fromJson(Map json) => _$DailyScheduleFromJson(json); diff --git a/lib/features/feed/presentation/pages/feed_screen.dart b/lib/features/feed/presentation/pages/feed_screen.dart index 408f00c4..579c8d21 100644 --- a/lib/features/feed/presentation/pages/feed_screen.dart +++ b/lib/features/feed/presentation/pages/feed_screen.dart @@ -234,7 +234,7 @@ class _FeedScreenContentState extends State { 2 => AddItemButton( onTap: state.profile?.roles.contains('consultant') ?? false ? () async { - await GoRouter.of(context).pushNamed(CreateDietScreen.name); + await GoRouter.of(context).pushNamed(CreateDietScreen.name, extra: {'isEditing': false}); } : null, ), diff --git a/lib/features/my_diets/presentation/my_diets_screen.dart b/lib/features/my_diets/presentation/my_diets_screen.dart index 01ed19fa..4a409f5d 100644 --- a/lib/features/my_diets/presentation/my_diets_screen.dart +++ b/lib/features/my_diets/presentation/my_diets_screen.dart @@ -107,7 +107,7 @@ class MyDietsScreenContent extends StatelessWidget { ), ), const SizedBox(width: 12), - AddItemButton(onTap: () => GoRouter.of(context).pushNamed(CreateDietScreen.name)), + AddItemButton(onTap: () => GoRouter.of(context).pushNamed(CreateDietScreen.name, extra: {'isEditing': false})), const SizedBox(width: 4), ], ), 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 166ff523..c4f7aeaf 100644 --- a/lib/features/my_diets/presentation/widgets/my_diet_widget.dart +++ b/lib/features/my_diets/presentation/widgets/my_diet_widget.dart @@ -251,7 +251,7 @@ class _MyDietWidgetState extends State { Expanded( child: CustomOutlinedButton( onPressed: () async { - GoRouter.of(context).pushNamed(DietPreviewScreen.name, extra: widget.diet); + GoRouter.of(context).pushNamed(DietPreviewScreen.name, extra: {'diet': widget.diet, 'isImageInBase64': false}); }, text: 'Превью', height: 52, @@ -263,6 +263,22 @@ class _MyDietWidgetState extends State { ), ), ), + const SizedBox(width: 6), + CustomOutlinedButton( + onPressed: () async { + _deleteDiet(widget.diet, context); + }, + height: 52, + width: 52, + leftIcon: SvgPicture.asset('assets/icons/trash_can.svg', width: 20, height: 20), + buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.red), + buttonStyle: ButtonStyle( + shape: WidgetStatePropertyAll(RoundedRectangleBorder(borderRadius: BorderRadius.circular(20))), + backgroundColor: WidgetStatePropertyAll(appTheme.lightGray0), + side: const WidgetStatePropertyAll(BorderSide.none), + ), + text: '', + ), ], ), ], From d70aaf955f678bc937c2e0ddbe37e73730712354 Mon Sep 17 00:00:00 2001 From: mso7nueh Date: Mon, 9 Feb 2026 23:15:12 +0300 Subject: [PATCH 09/58] feat: Implement auto-scrolling to the first validation error in the create diet form and refine image validation logic. --- .../presentation/create_diet_screen.dart | 54 ++++++++++++++++--- .../presentation/cubit/create_diet_cubit.dart | 4 +- 2 files changed, 50 insertions(+), 8 deletions(-) diff --git a/lib/features/create_diet/presentation/create_diet_screen.dart b/lib/features/create_diet/presentation/create_diet_screen.dart index b77ac3b9..2497b633 100644 --- a/lib/features/create_diet/presentation/create_diet_screen.dart +++ b/lib/features/create_diet/presentation/create_diet_screen.dart @@ -58,6 +58,39 @@ class _CreateDietScreenContentState extends State { final GlobalKey _mdKey = GlobalKey(); bool _mdToolbarVisible = false; + final GlobalKey _nameKey = GlobalKey(); + final GlobalKey _durationKey = GlobalKey(); + final GlobalKey _statusKey = GlobalKey(); + final GlobalKey _typeKey = GlobalKey(); + final GlobalKey _imageKey = GlobalKey(); + final GlobalKey _planKey = GlobalKey(); + + void _scrollToFirstError(CreateDietState state) { + GlobalKey? key; + if (state.dietNameError != null) { + key = _nameKey; + } else if (state.dietDescriptionError != null) { + key = _mdKey; + } else if (state.dietDurationError != null) { + key = _durationKey; + } else if (state.dietStateError != null) { + key = _statusKey; + } else if (state.dietImageError != null) { + key = _imageKey; + } else if (state.dailyScheduleErrors.any((e) => e.isNotEmpty)) { + key = _planKey; + } + + if (key != null && key.currentContext != null) { + Scrollable.ensureVisible( + key.currentContext!, + duration: const Duration(milliseconds: 500), + curve: Curves.easeInOut, + alignment: 0.1, + ); + } + } + @override void initState() { super.initState(); @@ -118,6 +151,7 @@ class _CreateDietScreenContentState extends State { children: [ const SizedBox(height: 16), CustomTextFormField( + key: _nameKey, hintText: s.enter_diet_name, controller: nameController, hasError: state.dietNameError != null, @@ -143,19 +177,20 @@ class _CreateDietScreenContentState extends State { ), if (state.dietDescriptionError != null) ...[const SizedBox(height: 4), ValidationErrorWidget(error: state.dietDescriptionError ?? '')], const SizedBox(height: 12), - const DietDurationDropDownWidget(), + DietDurationDropDownWidget(key: _durationKey), const SizedBox(height: 12), - StatusDropDownWidget(dietState: widget.diet?.state), + StatusDropDownWidget(key: _statusKey, dietState: widget.diet?.state), const SizedBox(height: 16), - const DietTypeChoiceWidget(), + DietTypeChoiceWidget(key: _typeKey), const SizedBox(height: 16), - const DietImagePickerWidget(), + DietImagePickerWidget(key: _imageKey), const SizedBox(height: 16), ], ), ), const SizedBox(height: 16), MainBackgroundBouble( + key: _planKey, child: Padding( padding: const EdgeInsets.symmetric(vertical: 16.0), child: Center( @@ -185,7 +220,11 @@ class _CreateDietScreenContentState extends State { ? () {} : () async { final diet = await context.read().getDietForPreview(); - if (diet == null) return; + if (diet == null) { + // ignore: use_build_context_synchronously + _scrollToFirstError(context.read().state); + return; + } if (!context.mounted) return; GoRouter.of(context).pushNamed(DietPreviewScreen.name, extra: diet); }, @@ -204,7 +243,10 @@ class _CreateDietScreenContentState extends State { CustomOutlinedButton( onPressed: () async { final result = await context.read().createDiet(); - if (result == 'error') { + if (result == 'validation') { + // ignore: use_build_context_synchronously + _scrollToFirstError(context.read().state); + } else if (result == 'error') { if (!context.mounted) return; showErrorDialog(context, title: '${s.could_not_publish_diet} ${state.dietData.name}'); } 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 8ecf89ba..f8eb5a73 100644 --- a/lib/features/create_diet/presentation/cubit/create_diet_cubit.dart +++ b/lib/features/create_diet/presentation/cubit/create_diet_cubit.dart @@ -394,11 +394,11 @@ class CreateDietCubit extends Cubit { result = false; emit(state.copyWith(dietStateError: 'Статус диеты не выбран')); } - if (state.dietData.image == null) { + if (state.dietData.image == null && state.dietData.imageUrl == null) { result = false; emit(state.copyWith(dietImageError: 'Не добавлена обложка')); } - result = _isDailyScheduleValid(); + result = _isDailyScheduleValid() && result; return result; } From f81aebe9d8620afc56cec458def2aafe97b0c93c Mon Sep 17 00:00:00 2001 From: misha Date: Tue, 10 Feb 2026 08:02:50 +0300 Subject: [PATCH 10/58] feat(tests): added new tests and updated fixtures --- .gitea/workflows/test.yml | 126 +++++++++--------- .../presentation/cubit/create_diet_cubit.dart | 7 +- .../repositories/create_diet_repository.dart | 7 +- .../pages/blog_post_screen_smoke_test.dart | 46 +++++++ .../presentation/create_diet_screen_test.dart | 34 +++++ .../cubit/create_diet_cubit_test.dart | 70 ++++++++-- .../diet_preview_screen_smoke_test.dart | 6 +- .../create_diet_repository_test.dart | 6 +- .../my_diets_repository_test.dart | 2 +- .../pages/about_plan_screen_smoke_test.dart | 29 ++++ .../pages/about_recipe_screen_smoke_test.dart | 31 +++++ .../create_diet/nutrition_response.json | 53 +++++++- test/fixtures/diets/diet_response.json | 77 ++++++++++- .../fixtures/doubles/create_diet_doubles.dart | 22 +-- .../feed/diets_pagination_response.json | 65 ++++++++- .../my_diets_pagination_response.json | 112 ++++++++++++++++ 16 files changed, 591 insertions(+), 102 deletions(-) create mode 100644 test/fixtures/my_diets/my_diets_pagination_response.json diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml index 3ecff8b0..a26ff075 100644 --- a/.gitea/workflows/test.yml +++ b/.gitea/workflows/test.yml @@ -139,70 +139,70 @@ jobs: - name: Native Analyze run: flutter analyze - # - name: Test - # run: flutter test --coverage + - name: Test + run: flutter test --coverage - # - name: Test coverage - # run: | - # echo "📊 Analyzing test coverage..." - # echo "" - # awk -F: ' - # /^SF:/ { - # file = $0 - # sub(/^SF:/, "", file) - # sub(/^[[:space:]]+/, "", file) - # is_generated = 0 - # if (file ~ /\.(g|freezed|gr|mocks)\.dart$/) { - # is_generated = 1 - # generated_files++ - # } else if (file ~ /\/(build|\.dart_tool)\//) { - # is_generated = 1 - # generated_files++ - # } else { - # source_files++ - # } - # } - # /^LF:/ { - # if (!is_generated) { - # total += $2 - # } else { - # generated_lines += $2 - # } - # } - # /^LH:/ { - # if (!is_generated) { - # covered += $2 - # } else { - # generated_covered += $2 - # } - # } - # END { - # print "📁 Files:" - # printf " Source files: %d\n", source_files - # printf " Generated files: %d (excluded)\n", generated_files - # print "" - # print "📝 Lines:" - # printf " Total lines: %d\n", total - # printf " Covered lines: %d\n", covered - # printf " Uncovered lines: %d\n", total - covered - # print "" - # if (total == 0) { - # print "⚠️ Coverage: 0%" - # exit 1 - # } else { - # percentage = (covered / total) * 100 - # printf "✅ Coverage: %.2f%%\n", percentage - # print "" - # if (percentage < 60) { - # printf "⚠️ Warning: Coverage is below 60%%\n" - # } else if (percentage < 80) { - # printf "💛 Good: Coverage is above 60%%\n" - # } else { - # printf "💚 Excellent: Coverage is above 80%%\n" - # } - # } - # } - # ' coverage/lcov.info + - name: Test coverage + run: | + echo "📊 Analyzing test coverage..." + echo "" + awk -F: ' + /^SF:/ { + file = $0 + sub(/^SF:/, "", file) + sub(/^[[:space:]]+/, "", file) + is_generated = 0 + if (file ~ /\.(g|freezed|gr|mocks)\.dart$/) { + is_generated = 1 + generated_files++ + } else if (file ~ /\/(build|\.dart_tool)\//) { + is_generated = 1 + generated_files++ + } else { + source_files++ + } + } + /^LF:/ { + if (!is_generated) { + total += $2 + } else { + generated_lines += $2 + } + } + /^LH:/ { + if (!is_generated) { + covered += $2 + } else { + generated_covered += $2 + } + } + END { + print "📁 Files:" + printf " Source files: %d\n", source_files + printf " Generated files: %d (excluded)\n", generated_files + print "" + print "📝 Lines:" + printf " Total lines: %d\n", total + printf " Covered lines: %d\n", covered + printf " Uncovered lines: %d\n", total - covered + print "" + if (total == 0) { + print "⚠️ Coverage: 0%" + exit 1 + } else { + percentage = (covered / total) * 100 + printf "✅ Coverage: %.2f%%\n", percentage + print "" + if (percentage < 60) { + printf "⚠️ Warning: Coverage is below 60%%\n" + } else if (percentage < 80) { + printf "💛 Good: Coverage is above 60%%\n" + } else { + printf "💚 Excellent: Coverage is above 80%%\n" + } + } + } + ' coverage/lcov.info - name: Build apk run: | 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 22803a76..59be7492 100644 --- a/lib/features/create_diet/presentation/cubit/create_diet_cubit.dart +++ b/lib/features/create_diet/presentation/cubit/create_diet_cubit.dart @@ -201,6 +201,7 @@ class CreateDietCubit extends Cubit { } Future getDietForPreview() async { + if (!_isFormValidForDraft()) return null; final authorProfile = await profileRepository.getMyProfile(); final imageBytes = (await state.dietData.image?.readAsBytes()) ?? []; String image = base64.encode(imageBytes); @@ -317,6 +318,11 @@ class CreateDietCubit extends Cubit { return 'validation'; } try { + final dietState = state.dietData.state; + if (dietState == null) { + emit(state.copyWith(isDietPublishing: false, dietStateError: 'Статус диеты не выбран')); + return 'validation'; + } File? file; if (state.dietData.imageUrl != null) { file = await downloadImageAsFile(state.dietData.imageUrl!); @@ -325,7 +331,6 @@ class CreateDietCubit extends Cubit { if (state.dietData.image?.path != null) { image = await ImageCompressUtility.compressFile(File(state.dietData.image!.path)); } - final dietState = state.dietData.state!; final dailySchedule = state.dietData.dailySchedule; await createDietRepository.createDiet( name: state.dietData.name ?? '', diff --git a/lib/features/create_diet/repositories/create_diet_repository.dart b/lib/features/create_diet/repositories/create_diet_repository.dart index 546d975d..825e9424 100644 --- a/lib/features/create_diet/repositories/create_diet_repository.dart +++ b/lib/features/create_diet/repositories/create_diet_repository.dart @@ -82,7 +82,12 @@ class CreateDietRepository { return MapEntry( int.parse(key), (value as Map).map((key, value) { - return MapEntry(int.parse(key), DailySchedule.fromJson(value as Map)); + final portionMap = Map.from(value as Map); + final pnvid = portionMap['product_nutritional_value_id']; + if (pnvid is String) { + portionMap['product_nutritional_value_id'] = int.tryParse(pnvid); + } + return MapEntry(int.parse(key), DailySchedule.fromJson(portionMap)); }), ); }), diff --git a/test/features/blog_post/presentation/pages/blog_post_screen_smoke_test.dart b/test/features/blog_post/presentation/pages/blog_post_screen_smoke_test.dart index 0758ab9b..027ae523 100644 --- a/test/features/blog_post/presentation/pages/blog_post_screen_smoke_test.dart +++ b/test/features/blog_post/presentation/pages/blog_post_screen_smoke_test.dart @@ -7,6 +7,8 @@ import 'package:eatvio_app/core/theme/theme_helper.dart'; import 'package:eatvio_app/features/blog_post/presentation/cubit/blog_post_cubit.dart'; import 'package:eatvio_app/features/blog_post/presentation/cubit/blog_post_state.dart'; import 'package:eatvio_app/features/blog_post/presentation/pages/blog_post_screen.dart'; +import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart'; +import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart'; import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart'; import 'package:eatvio_app/generated/l10n.dart'; import 'package:flutter/material.dart'; @@ -159,5 +161,49 @@ void main() { expect(find.byType(BlogPostScreen), findsOneWidget); }); }); + + testWidgets('renders loaded state with blog post model', (tester) async { + await mockNetworkImagesFor(() async { + final blogPostCubit = MockBlogPostCubit(); + final feedCubit = MockFeedCubit(); + final commentsCubit = MockCommentsCubit(); + final blogPostModel = FeedBlogPostModel( + id: 1, + title: 'Test Post Title', + description: 'Test post description', + image: 'https://example.com/image.jpg', + state: 'published', + author: const Author(authorSlug: 'author-1', authorName: 'Author Name', authorProfileIcon: 'https://example.com/icon.jpg', authorIsMe: false), + videoUrl: '', + createdAt: DateTime(2024, 1, 15), + updatedAt: DateTime(2024, 1, 15), + totalLikes: 10, + totalViews: 100, + totalComments: 5, + wasLikedByMe: false, + ); + final state = BlogPostState(isLoading: false, blogPostModel: blogPostModel, error: null); + + when(blogPostCubit.state).thenReturn(state); + when(blogPostCubit.stream).thenAnswer((_) => Stream.value(state)); + when(blogPostCubit.close()).thenAnswer((_) async => Future.value()); + when(feedCubit.viewPost(any)).thenAnswer((_) async => Future.value()); + when(feedCubit.stream).thenAnswer((_) => const Stream.empty()); + when(feedCubit.close()).thenAnswer((_) async => Future.value()); + when(commentsCubit.state).thenReturn(const CommentsState()); + when(commentsCubit.stream).thenAnswer((_) => const Stream.empty()); + when(commentsCubit.close()).thenAnswer((_) async => Future.value()); + when(commentsCubit.getComments(any, any, any)).thenAnswer((_) async => Future.value()); + + registerMocks(blogPostCubit, commentsCubit); + + await tester.pumpWidget(wrap(blogPostCubit, feedCubit, commentsCubit, const BlogPostScreen(id: 1))); + await tester.pump(); + await tester.pumpAndSettle(); + + expect(find.byType(BlogPostScreen), findsOneWidget); + expect(find.text('Test Post Title'), findsOneWidget); + }); + }); }); } diff --git a/test/features/create_diet/presentation/create_diet_screen_test.dart b/test/features/create_diet/presentation/create_diet_screen_test.dart index a9d5f44a..6f3de99c 100644 --- a/test/features/create_diet/presentation/create_diet_screen_test.dart +++ b/test/features/create_diet/presentation/create_diet_screen_test.dart @@ -67,5 +67,39 @@ void main() { expect(find.byType(CreateDietScreenContent), findsOneWidget); }); + + testWidgets('renders create diet screen with loading state', (tester) async { + final cubit = MockCreateDietCubit(); + final state = CreateDietState.initial().copyWith(isLoading: true); + + when(cubit.state).thenReturn(state); + when(cubit.stream).thenAnswer((_) => Stream.value(state)); + when(cubit.close()).thenAnswer((_) async => Future.value()); + when(cubit.setInitialData(any)).thenAnswer((_) async => Future.value()); + + sl.registerLazySingleton(() => cubit); + + await tester.pumpWidget(wrap(const CreateDietScreenContent(), cubit)); + await tester.pump(); + + expect(find.byType(CreateDietScreenContent), findsOneWidget); + }); + + testWidgets('renders create diet screen with diet publishing state', (tester) async { + final cubit = MockCreateDietCubit(); + final state = CreateDietState.initial().copyWith(isDietPublishing: true); + + when(cubit.state).thenReturn(state); + when(cubit.stream).thenAnswer((_) => Stream.value(state)); + when(cubit.close()).thenAnswer((_) async => Future.value()); + when(cubit.setInitialData(any)).thenAnswer((_) async => Future.value()); + + sl.registerLazySingleton(() => cubit); + + await tester.pumpWidget(wrap(const CreateDietScreenContent(), cubit)); + await tester.pump(); + + expect(find.byType(CreateDietScreenContent), findsOneWidget); + }); }); } diff --git a/test/features/create_diet/presentation/cubit/create_diet_cubit_test.dart b/test/features/create_diet/presentation/cubit/create_diet_cubit_test.dart index c5266123..8cba4984 100644 --- a/test/features/create_diet/presentation/cubit/create_diet_cubit_test.dart +++ b/test/features/create_diet/presentation/cubit/create_diet_cubit_test.dart @@ -6,6 +6,7 @@ import 'package:eatvio_app/features/create_diet/repositories/create_diet_reposit import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart'; import 'package:eatvio_app/features/glossary/models/glossary_model.dart'; import 'package:eatvio_app/features/glossary/repositories/glossary_repository.dart'; +import 'package:eatvio_app/features/profile/models/profile_model.dart'; import 'package:eatvio_app/features/profile/repositories/profile_repository.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:flutter_image_compress/flutter_image_compress.dart'; @@ -236,6 +237,51 @@ void main() { expect(result, null); }); + test('getDietForPreview returns Diet when form is valid', () async { + const profile = ProfileModel( + id: 1, + name: 'Test User', + email: 'test@example.com', + phoneConfirmationState: 'confirmed', + slug: 'test-user', + isPublicProfile: true, + isBlockedByMe: false, + isBlockedByAuthor: false, + isBlockedByUser: false, + isEntitySubscribedByMe: false, + profilePhotoUrl: 'https://example.com/photo.jpg', + roles: [], + badges: [], + subscriptions: [], + subscribers: [], + ); + when(profileRepository.getMyProfile()).thenAnswer((_) async => profile); + + cubit.setName('Test Diet'); + cubit.setDetails('Test Description'); + cubit.setDuration(0); + cubit.setDietState(DietState.active); + const portion = DailySchedule( + name: 'Apple', + portion: Portion.grams, + quantity: 100, + dailyNutrition: DailyNutrition(energy: 52.0, proteins: 0.3, fats: 0.2, carbohydrates: 14.0), + productNutritionalValueId: 1, + ); + cubit.addPortion(dayIndex: 0, mealIndex: 0, portion: portion); + cubit.setImage(null, imageUrl: 'https://example.com/cover.jpg'); + + final result = await cubit.getDietForPreview(); + + expect(result, isNotNull); + expect(result!.name, 'Test Diet'); + expect(result.details, 'Test Description'); + expect(result.duration, 7); + expect(result.author.authorName, 'Test User'); + expect(result.author.authorSlug, 'test-user'); + verify(profileRepository.getMyProfile()).called(1); + }); + test('createDiet returns validation error when form is invalid', () async { final result = await cubit.createDiet(); expect(result, 'validation'); @@ -257,7 +303,7 @@ void main() { portion: Portion.grams, quantity: 100, dailyNutrition: DailyNutrition(energy: 52.0, proteins: 0.3, fats: 0.2, carbohydrates: 14.0), - productNutritionalValueId: '1', + productNutritionalValueId: 1, ); cubit.addPortion(dayIndex: 0, mealIndex: 0, portion: portion); // Set image with non-existent file to trigger error in compression @@ -280,7 +326,7 @@ void main() { portion: Portion.grams, quantity: 100, dailyNutrition: DailyNutrition(energy: 52.0, proteins: 0.3, fats: 0.2, carbohydrates: 14.0), - productNutritionalValueId: '1', + productNutritionalValueId: 1, ); cubit.addPortion(dayIndex: 0, mealIndex: 0, portion: portion); final image = XFile('test_image.jpg'); @@ -302,7 +348,7 @@ void main() { portion: Portion.grams, quantity: 100, dailyNutrition: DailyNutrition(energy: 52.0, proteins: 0.3, fats: 0.2, carbohydrates: 14.0), - productNutritionalValueId: '1', + productNutritionalValueId: 1, ); cubit.addPortion(dayIndex: 0, mealIndex: 0, portion: portion); final image = XFile('test_image.jpg'); @@ -321,7 +367,7 @@ void main() { portion: Portion.grams, quantity: 100, dailyNutrition: DailyNutrition(energy: 52.0, proteins: 0.3, fats: 0.2, carbohydrates: 14.0), - productNutritionalValueId: '1', + productNutritionalValueId: 1, ); cubit.addPortion(dayIndex: 0, mealIndex: 0, portion: portion); @@ -336,7 +382,7 @@ void main() { portion: Portion.grams, quantity: 100, dailyNutrition: DailyNutrition(energy: 52.0, proteins: 0.3, fats: 0.2, carbohydrates: 14.0), - productNutritionalValueId: '1', + productNutritionalValueId: 1, ); cubit.addPortion(dayIndex: 0, mealIndex: 0, portion: portion); @@ -405,7 +451,7 @@ void main() { portion: Portion.grams, quantity: 100, dailyNutrition: DailyNutrition(energy: 52.0, proteins: 0.3, fats: 0.2, carbohydrates: 14.0), - productNutritionalValueId: '1', + productNutritionalValueId: 1, ); cubit.addPortion(dayIndex: 0, mealIndex: 0, portion: portion); final image = XFile('test_image.jpg'); @@ -427,7 +473,7 @@ void main() { portion: Portion.grams, quantity: 100, dailyNutrition: DailyNutrition(energy: 52.0, proteins: 0.3, fats: 0.2, carbohydrates: 14.0), - productNutritionalValueId: '1', + productNutritionalValueId: 1, ); cubit.addPortion(dayIndex: 0, mealIndex: 0, portion: portion); final image = XFile('test_image.jpg'); @@ -448,7 +494,7 @@ void main() { portion: Portion.grams, quantity: 100, dailyNutrition: DailyNutrition(energy: 52.0, proteins: 0.3, fats: 0.2, carbohydrates: 14.0), - productNutritionalValueId: '1', + productNutritionalValueId: 1, ); cubit.addPortion(dayIndex: 0, mealIndex: 0, portion: portion); final image = XFile('test_image.jpg'); @@ -469,7 +515,7 @@ void main() { portion: Portion.grams, quantity: 100, dailyNutrition: DailyNutrition(energy: 52.0, proteins: 0.3, fats: 0.2, carbohydrates: 14.0), - productNutritionalValueId: '1', + productNutritionalValueId: 1, ); cubit.addPortion(dayIndex: 0, mealIndex: 0, portion: portion); final image = XFile('test_image.jpg'); @@ -491,7 +537,7 @@ void main() { portion: Portion.grams, quantity: 100, dailyNutrition: DailyNutrition(energy: 52.0, proteins: 0.3, fats: 0.2, carbohydrates: 14.0), - productNutritionalValueId: '1', + productNutritionalValueId: 1, ); cubit.addPortion(dayIndex: 0, mealIndex: 0, portion: portion); @@ -522,7 +568,7 @@ void main() { portion: Portion.grams, quantity: 100, dailyNutrition: DailyNutrition(energy: 52.0, proteins: 0.3, fats: 0.2, carbohydrates: 14.0), - productNutritionalValueId: '1', + productNutritionalValueId: 1, ); cubit.addPortion(dayIndex: 0, mealIndex: 0, portion: portion); @@ -546,7 +592,7 @@ void main() { portion: Portion.grams, quantity: 100, dailyNutrition: DailyNutrition(energy: 52.0, proteins: 0.3, fats: 0.2, carbohydrates: 14.0), - productNutritionalValueId: '1', + productNutritionalValueId: 1, ); cubit.addPortion(dayIndex: 0, mealIndex: 0, portion: portion); diff --git a/test/features/create_diet/presentation/diet_preview_screen_smoke_test.dart b/test/features/create_diet/presentation/diet_preview_screen_smoke_test.dart index 823b556c..391143c6 100644 --- a/test/features/create_diet/presentation/diet_preview_screen_smoke_test.dart +++ b/test/features/create_diet/presentation/diet_preview_screen_smoke_test.dart @@ -63,7 +63,7 @@ void main() { dailyNutrition: [], ); - await tester.pumpWidget(wrap(DietPreviewScreen(diet: testDiet))); + await tester.pumpWidget(wrap(DietPreviewScreen(diet: testDiet, isImageInBase64: true))); await tester.pumpAndSettle(); expect(find.byType(DietPreviewScreen), findsOneWidget); @@ -103,7 +103,7 @@ void main() { dailyNutrition: [const DailyNutrition(energy: 2000.0, proteins: 100.0, fats: 50.0, carbohydrates: 200.0)], ); - await tester.pumpWidget(wrap(DietPreviewScreen(diet: testDiet))); + await tester.pumpWidget(wrap(DietPreviewScreen(diet: testDiet, isImageInBase64: true))); await tester.pumpAndSettle(); expect(find.byType(DietPreviewScreen), findsOneWidget); @@ -131,7 +131,7 @@ void main() { dailyNutrition: [], ); - await tester.pumpWidget(wrap(DietPreviewScreen(diet: testDiet))); + await tester.pumpWidget(wrap(DietPreviewScreen(diet: testDiet, isImageInBase64: true))); await tester.pumpAndSettle(); expect(find.text('Test Diet Name'), findsOneWidget); diff --git a/test/features/create_diet/repositories/create_diet_repository_test.dart b/test/features/create_diet/repositories/create_diet_repository_test.dart index 1b881380..602097ad 100644 --- a/test/features/create_diet/repositories/create_diet_repository_test.dart +++ b/test/features/create_diet/repositories/create_diet_repository_test.dart @@ -196,7 +196,7 @@ void main() { portion: Portion.grams, quantity: 100, dailyNutrition: null, - productNutritionalValueId: '1', + productNutritionalValueId: 1, ), ], ], @@ -236,7 +236,7 @@ void main() { portion: Portion.grams, quantity: 100, dailyNutrition: null, - productNutritionalValueId: '1', + productNutritionalValueId: 1, ), ], ], @@ -299,7 +299,7 @@ void main() { portion: Portion.grams, quantity: 100, dailyNutrition: null, - productNutritionalValueId: '1', + productNutritionalValueId: 1, ), ], ], diff --git a/test/features/my_diets/repositories/my_diets_repository_test.dart b/test/features/my_diets/repositories/my_diets_repository_test.dart index c31bb6aa..b5a23bb5 100644 --- a/test/features/my_diets/repositories/my_diets_repository_test.dart +++ b/test/features/my_diets/repositories/my_diets_repository_test.dart @@ -103,7 +103,7 @@ void main() { dietData.first['name'] = 'Another Diet'; dietData.first['image'] = 'https://someurl.com/img2.jpg'; dietData.first['duration'] = duration; - dietData.first['state'] = 'draft'; + dietData.first['state'] = 'drafted'; dietData.first['type'] = {'light': false, 'veg': true, 'carb': false, 'meat': false}; dietData.first['description_md'] = ''; dietData.first['author'] = {'author_slug': 'author-slug', 'author_name': 'Author Name', 'author_profile_icon': '', 'author_is_me': true, 'is_entity_subscribed_by_me': null, 'author_place_in_rating': null}; diff --git a/test/features/plans/presentation/pages/about_plan_screen_smoke_test.dart b/test/features/plans/presentation/pages/about_plan_screen_smoke_test.dart index e57b7601..32f67e95 100644 --- a/test/features/plans/presentation/pages/about_plan_screen_smoke_test.dart +++ b/test/features/plans/presentation/pages/about_plan_screen_smoke_test.dart @@ -182,5 +182,34 @@ void main() { expect(find.byType(AboutPlanScreen), findsOneWidget); }); + + testWidgets('renders with navigateToComments true', (tester) async { + final planCubit = MockAboutPlanCubit(); + final feedCubit = MockFeedCubit(); + final commentsCubit = MockCommentsCubit(); + const state = AboutPlanState(isLoading: false, planModel: testPlan, chosenOptionIndex: 0, error: null); + + when(planCubit.state).thenReturn(state); + when(planCubit.stream).thenAnswer((_) => const Stream.empty()); + when(planCubit.close()).thenAnswer((_) async => Future.value()); + when(feedCubit.viewPlan(any)).thenAnswer((_) async => Future.value()); + when(feedCubit.stream).thenAnswer((_) => const Stream.empty()); + when(feedCubit.close()).thenAnswer((_) async => Future.value()); + when(commentsCubit.state).thenReturn(const CommentsState()); + when(commentsCubit.stream).thenAnswer((_) => const Stream.empty()); + when(commentsCubit.close()).thenAnswer((_) async => Future.value()); + when(commentsCubit.getComments(any, any, any)).thenAnswer((_) async => Future.value()); + + registerMocks(planCubit, commentsCubit); + + await mockNetworkImagesFor(() async { + await tester.pumpWidget(wrap(planCubit, feedCubit, commentsCubit, const AboutPlanScreen(slug: 'test', navigateToComments: true, index: 1))); + await tester.pump(); + await tester.pump(); + }); + + expect(find.byType(AboutPlanScreen), findsOneWidget); + expect(find.text('Test Plan'), findsOneWidget); + }); }); } diff --git a/test/features/recipe/presentation/pages/about_recipe_screen_smoke_test.dart b/test/features/recipe/presentation/pages/about_recipe_screen_smoke_test.dart index 3008d3b3..9bc22016 100644 --- a/test/features/recipe/presentation/pages/about_recipe_screen_smoke_test.dart +++ b/test/features/recipe/presentation/pages/about_recipe_screen_smoke_test.dart @@ -193,5 +193,36 @@ void main() { expect(find.byType(AboutRecipeScreen), findsOneWidget); }); + + testWidgets('renders with navigateToComments true', (tester) async { + final recipeCubit = MockAboutRecipeCubit(); + final feedCubit = MockFeedCubit(); + final commentsCubit = MockCommentsCubit(); + final state = AboutRecipeState.loaded(recipe: testRecipe); + + when(recipeCubit.state).thenReturn(state); + when(recipeCubit.stream).thenAnswer((_) => const Stream.empty()); + when(recipeCubit.close()).thenAnswer((_) async => Future.value()); + when(feedCubit.viewRecipe(any)).thenAnswer((_) async => Future.value()); + when(feedCubit.stream).thenAnswer((_) => const Stream.empty()); + when(feedCubit.close()).thenAnswer((_) async => Future.value()); + when(commentsCubit.state).thenReturn(const CommentsState()); + when(commentsCubit.stream).thenAnswer((_) => const Stream.empty()); + when(commentsCubit.close()).thenAnswer((_) async => Future.value()); + when(commentsCubit.getComments(any, any, any)).thenAnswer((_) async => Future.value()); + when(commentsCubit.setReplyId(any)).thenReturn(null); + + registerMocks(recipeCubit, commentsCubit); + + await mockNetworkImagesFor(() async { + await tester.pumpWidget( + wrap(recipeCubit, feedCubit, commentsCubit, const AboutRecipeScreen(recipeSlug: 'test', index: 2, navigateToComments: true)), + ); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 600)); + }); + + expect(find.byType(AboutRecipeScreen), findsOneWidget); + }); }); } diff --git a/test/fixtures/create_diet/nutrition_response.json b/test/fixtures/create_diet/nutrition_response.json index 6dd2119e..61bf7807 100644 --- a/test/fixtures/create_diet/nutrition_response.json +++ b/test/fixtures/create_diet/nutrition_response.json @@ -3,11 +3,58 @@ "1": { "0": { "name": "Авокадо", - "portion": "grams", + "portion": "gr", "quantity": 100, - "product_nutritional_value_id": "501" + "product_nutritional_value_id": 501, + "nutrition": { + "energy": 160.0, + "proteins": 2.0, + "fats": 14.0, + "carbohydrates": 9.0 + } + }, + "1": { + "name": "Лосось", + "portion": "gr", + "quantity": 150, + "product_nutritional_value_id": 502, + "nutrition": { + "energy": 312.0, + "proteins": 30.0, + "fats": 19.5, + "carbohydrates": 0.0 + } + } + }, + "2": { + "0": { + "name": "Овсянка", + "portion": "gr", + "quantity": 150, + "product_nutritional_value_id": 503, + "nutrition": { + "energy": 195.0, + "proteins": 6.75, + "fats": 3.0, + "carbohydrates": 33.0 + } + } + } + }, + "2": { + "1": { + "0": { + "name": "Творог", + "portion": "gr", + "quantity": 200, + "product_nutritional_value_id": 504, + "nutrition": { + "energy": 226.0, + "proteins": 31.0, + "fats": 9.0, + "carbohydrates": 3.4 + } } } } } - diff --git a/test/fixtures/diets/diet_response.json b/test/fixtures/diets/diet_response.json index d4d770a9..41b52101 100644 --- a/test/fixtures/diets/diet_response.json +++ b/test/fixtures/diets/diet_response.json @@ -12,7 +12,66 @@ "meat": false }, "description_md": "Test details", - "daily_schedule": {}, + "daily_schedule": { + "1": { + "1": { + "0": { + "name": "Овсянка", + "portion": "gr", + "quantity": 150, + "product_nutritional_value_id": 101, + "nutrition": { + "energy": 195.0, + "proteins": 6.75, + "fats": 3.0, + "carbohydrates": 33.0 + } + }, + "1": { + "name": "Бананы", + "portion": "gr", + "quantity": 120, + "product_nutritional_value_id": 102, + "nutrition": { + "energy": 107.0, + "proteins": 1.3, + "fats": 0.4, + "carbohydrates": 27.0 + } + } + }, + "2": { + "0": { + "name": "Куриная грудка", + "portion": "gr", + "quantity": 200, + "product_nutritional_value_id": 201, + "nutrition": { + "energy": 330.0, + "proteins": 62.0, + "fats": 7.2, + "carbohydrates": 0.0 + } + } + } + }, + "2": { + "1": { + "0": { + "name": "Творог", + "portion": "gr", + "quantity": 200, + "product_nutritional_value_id": 301, + "nutrition": { + "energy": 226.0, + "proteins": 31.0, + "fats": 9.0, + "carbohydrates": 3.4 + } + } + } + } + }, "author": { "author_slug": "author", "author_name": "Author Name", @@ -26,6 +85,18 @@ "total_comments": 5, "was_liked_by_me": false, "is_favorite": false, - "daily_nutrition": [] + "daily_nutrition": [ + { + "energy": 864.0, + "proteins": 101.05, + "fats": 19.6, + "carbohydrates": 63.4 + }, + { + "energy": 532.0, + "proteins": 93.3, + "fats": 16.2, + "carbohydrates": 30.4 + } + ] } - diff --git a/test/fixtures/doubles/create_diet_doubles.dart b/test/fixtures/doubles/create_diet_doubles.dart index 7cf7b3b9..08b16861 100644 --- a/test/fixtures/doubles/create_diet_doubles.dart +++ b/test/fixtures/doubles/create_diet_doubles.dart @@ -53,11 +53,15 @@ class FixtureCreateDietRepository extends CreateDietRepository { portion = Portion.grams; } + final pnvid = portionData['product_nutritional_value_id']; + final nutritionJson = portionData['nutrition'] as Map?; + final dailyNutrition = nutritionJson != null ? DailyNutrition.fromJson(nutritionJson) : null; mealMap[portionIndex] = DailySchedule( name: portionData['name'] as String, portion: portion, - quantity: portionData['quantity'] as int, - productNutritionalValueId: portionData['product_nutritional_value_id'] as String?, + quantity: portionData['quantity'] is int ? portionData['quantity'] as int : int.parse(portionData['quantity'].toString()), + productNutritionalValueId: pnvid == null ? null : (pnvid is int ? pnvid : int.tryParse(pnvid.toString())), + dailyNutrition: dailyNutrition, ); } @@ -75,13 +79,13 @@ class FixtureCreateDietRepository extends CreateDietRepository { 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, + required String? meatType, + required String? carbType, + required String? vegType, + required String? lightType, + required String? details, + required File? image, + required List>>? dailySchedule, }) async { createRequests.add({ 'name': name, diff --git a/test/fixtures/feed/diets_pagination_response.json b/test/fixtures/feed/diets_pagination_response.json index a15de8fb..8dd97a36 100644 --- a/test/fixtures/feed/diets_pagination_response.json +++ b/test/fixtures/feed/diets_pagination_response.json @@ -14,7 +14,54 @@ "meat": true }, "description_md": "Keto diet description", - "daily_schedule": {}, + "daily_schedule": { + "1": { + "1": { + "0": { + "name": "Яйца вареные", + "portion": "pcs", + "quantity": 2, + "product_nutritional_value_id": 401, + "nutrition": { + "energy": 155.0, + "proteins": 12.6, + "fats": 10.6, + "carbohydrates": 1.1 + } + } + }, + "2": { + "0": { + "name": "Авокадо", + "portion": "gr", + "quantity": 100, + "product_nutritional_value_id": 501, + "nutrition": { + "energy": 160.0, + "proteins": 2.0, + "fats": 14.0, + "carbohydrates": 9.0 + } + } + } + }, + "2": { + "1": { + "0": { + "name": "Лосось", + "portion": "gr", + "quantity": 150, + "product_nutritional_value_id": 502, + "nutrition": { + "energy": 312.0, + "proteins": 30.0, + "fats": 19.5, + "carbohydrates": 0.0 + } + } + } + } + }, "author": { "author_slug": "nutritionist-1", "author_name": "Nutritionist Name", @@ -28,7 +75,20 @@ "total_comments": 25, "was_liked_by_me": false, "is_favorite": false, - "daily_nutrition": [] + "daily_nutrition": [ + { + "energy": 627.0, + "proteins": 44.6, + "fats": 44.1, + "carbohydrates": 10.1 + }, + { + "energy": 312.0, + "proteins": 30.0, + "fats": 19.5, + "carbohydrates": 0.0 + } + ] } ], "links": { @@ -50,4 +110,3 @@ "total_max_price": null } } - diff --git a/test/fixtures/my_diets/my_diets_pagination_response.json b/test/fixtures/my_diets/my_diets_pagination_response.json new file mode 100644 index 00000000..a23af562 --- /dev/null +++ b/test/fixtures/my_diets/my_diets_pagination_response.json @@ -0,0 +1,112 @@ +{ + "data": [ + { + "id": 1, + "slug": "my-keto-diet", + "name": "My Keto Diet", + "image": "https://example.com/my-keto.jpg", + "duration": 14, + "state": "drafted", + "type": { + "light": true, + "veg": true, + "carb": false, + "meat": true + }, + "description_md": "My drafted keto diet for two weeks", + "daily_schedule": { + "1": { + "1": { + "0": { + "name": "Омлет", + "portion": "gr", + "quantity": 200, + "product_nutritional_value_id": 601, + "nutrition": { + "energy": 318.0, + "proteins": 21.2, + "fats": 24.2, + "carbohydrates": 2.0 + } + } + }, + "2": { + "0": { + "name": "Салат с курицей", + "portion": "gr", + "quantity": 300, + "product_nutritional_value_id": 602, + "nutrition": { + "energy": 285.0, + "proteins": 35.0, + "fats": 12.0, + "carbohydrates": 8.0 + } + } + } + }, + "2": { + "1": { + "0": { + "name": "Творог 5%", + "portion": "gr", + "quantity": 200, + "product_nutritional_value_id": 603, + "nutrition": { + "energy": 226.0, + "proteins": 31.0, + "fats": 9.0, + "carbohydrates": 3.4 + } + } + } + } + }, + "author": { + "author_slug": "me", + "author_name": "My Name", + "author_profile_icon": "https://example.com/me.jpg", + "author_is_me": true, + "is_entity_subscribed_by_me": null, + "author_place_in_rating": null + }, + "total_likes": 0, + "total_views": 0, + "total_comments": 0, + "was_liked_by_me": false, + "is_favorite": false, + "daily_nutrition": [ + { + "energy": 603.0, + "proteins": 56.2, + "fats": 36.2, + "carbohydrates": 10.0 + }, + { + "energy": 226.0, + "proteins": 31.0, + "fats": 9.0, + "carbohydrates": 3.4 + } + ] + } + ], + "links": { + "first": "http://localhost/api/my-diets?page=1", + "last": "http://localhost/api/my-diets?page=1", + "prev": null, + "next": null + }, + "meta": { + "current_page": 1, + "from": 1, + "last_page": 1, + "links": [], + "path": "http://localhost/api/my-diets", + "per_page": 10, + "to": 1, + "total": 1, + "total_min_price": null, + "total_max_price": null + } +} From 34eccb7db5c252cec282459f77402d779945ac99 Mon Sep 17 00:00:00 2001 From: mso7nueh Date: Tue, 10 Feb 2026 18:36:18 +0300 Subject: [PATCH 11/58] feat: Enhance OAuth error state to include specific registration validation errors and pass email in the verification request. --- .../bloc/oauth_cubit/oauth_cubit.dart | 29 +++++++++++++++---- .../bloc/oauth_cubit/oauth_state.dart | 7 ++++- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/lib/features/auth/presentation/bloc/oauth_cubit/oauth_cubit.dart b/lib/features/auth/presentation/bloc/oauth_cubit/oauth_cubit.dart index daf156a8..a066c959 100644 --- a/lib/features/auth/presentation/bloc/oauth_cubit/oauth_cubit.dart +++ b/lib/features/auth/presentation/bloc/oauth_cubit/oauth_cubit.dart @@ -36,9 +36,29 @@ class OAuthCubit extends Cubit with RegistrationDateCacheMixin { final emailErrors = errors?['email'] as List?; final phoneErrors = errors?['phone'] as List?; - if ((emailErrors != null && emailErrors.isNotEmpty) || (phoneErrors != null && phoneErrors.isNotEmpty)) { - // Это ошибка валидации - нужно показать экран подтверждения - emit(const OAuthState.error(errorMessage: 'validation_error')); + final errorsList = []; + if (emailErrors != null && emailErrors.isNotEmpty) { + for (final error in emailErrors) { + final errorStr = error.toString(); + if (errorStr.contains('Некорректный формат email')) { + errorsList.add(RegistrationValidationErrors.incorrectEmailFormat); + } + if (errorStr.contains('почта уже существует') || errorStr.contains('Эл. почта уже существует')) { + errorsList.add(RegistrationValidationErrors.emailExist); + } + } + } + if (phoneErrors != null && phoneErrors.isNotEmpty) { + for (final error in phoneErrors) { + final errorStr = error.toString(); + if (errorStr.contains('phone уже существует') || errorStr.contains('телефон уже существует')) { + errorsList.add(RegistrationValidationErrors.phoneExist); + } + } + } + + if (errorsList.isNotEmpty) { + emit(OAuthState.error(errorMessage: 'validation_error', registrationValidationErrors: errorsList)); } else { emit(OAuthState.error(errorMessage: e.toString())); } @@ -126,7 +146,7 @@ class OAuthCubit extends Cubit with RegistrationDateCacheMixin { try { emit(const OAuthState.loading()); log('OAuthCubit: Requesting verification code for email: $email, phone: $phone'); - final response = await authRepository.requestOAuthVerification(email: null, phone: phone); + final response = await authRepository.requestOAuthVerification(email: email, phone: phone); if (response != null) { log('OAuthCubit: Verification code requested successfully, masked email: ${response.email}'); } @@ -188,4 +208,3 @@ class OAuthCubit extends Cubit with RegistrationDateCacheMixin { } } -enum RegistrationValidationErrors { incorrectEmailFormat, emailExist, phoneExist, incorrectDateOfBirthFormat } diff --git a/lib/features/auth/presentation/bloc/oauth_cubit/oauth_state.dart b/lib/features/auth/presentation/bloc/oauth_cubit/oauth_state.dart index 7da9ab7c..517222de 100644 --- a/lib/features/auth/presentation/bloc/oauth_cubit/oauth_state.dart +++ b/lib/features/auth/presentation/bloc/oauth_cubit/oauth_state.dart @@ -10,5 +10,10 @@ abstract class OAuthState with _$OAuthState { const factory OAuthState.loaded({required String token}) = OAuthStateLoaded; - const factory OAuthState.error({required String errorMessage}) = OAuthStateError; + const factory OAuthState.error({ + required String errorMessage, + List? registrationValidationErrors, + }) = OAuthStateError; } + +enum RegistrationValidationErrors { incorrectEmailFormat, emailExist, phoneExist, incorrectDateOfBirthFormat } From ae07851396495e74072d60a380f542fcbb99fc7e Mon Sep 17 00:00:00 2001 From: mso7nueh Date: Tue, 10 Feb 2026 18:47:53 +0300 Subject: [PATCH 12/58] test: update OAuth verification mock to use `anyNamed` arguments for `email` and `phone`. --- .../auth/presentation/bloc/oauth_cubit/oauth_cubit_test.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/features/auth/presentation/bloc/oauth_cubit/oauth_cubit_test.dart b/test/features/auth/presentation/bloc/oauth_cubit/oauth_cubit_test.dart index 347ae773..8bf682e2 100644 --- a/test/features/auth/presentation/bloc/oauth_cubit/oauth_cubit_test.dart +++ b/test/features/auth/presentation/bloc/oauth_cubit/oauth_cubit_test.dart @@ -287,8 +287,7 @@ void main() { const verificationResponse = OAuthVerificationRequestResponse(email: 't***@example.com', status: 'sent'); - // OAuthCubit всегда передает email: null в репозиторий, независимо от переданного параметра - when(mockAuthRepository.requestOAuthVerification(email: null, phone: null)).thenAnswer((_) async => verificationResponse); + when(mockAuthRepository.requestOAuthVerification(email: anyNamed('email'), phone: anyNamed('phone'))).thenAnswer((_) async => verificationResponse); final result = await oauthCubit!.requestOAuthVerification(email: 'test@example.com'); From 5f30aaeb2ab243882013db0219b879123ff7b349 Mon Sep 17 00:00:00 2001 From: misha Date: Wed, 11 Feb 2026 16:23:48 +0300 Subject: [PATCH 13/58] feat(my_diets): merge conflict fixes --- .../presentation/create_diet_screen.dart | 87 ++++++++++++------- .../presentation/cubit/create_diet_cubit.dart | 8 +- .../presentation/cubit/create_diet_state.dart | 15 ++-- .../repositories/create_diet_repository.dart | 13 ++- .../services/create_diet_service.dart | 6 +- .../presentation/cubit/my_diets_cubit.dart | 8 +- .../presentation/my_diets_screen.dart | 3 +- .../presentation/widgets/my_diet_widget.dart | 74 ++++++---------- .../presentation/widgets/my_diets_tab.dart | 51 ++++++----- .../create_diet/integration_widget_test.dart | 2 +- .../cubit/create_diet_cubit_test.dart | 2 +- .../create_diet_repository_test.dart | 20 +++-- .../cubit/my_diets_cubit_test.dart | 8 +- .../widgets/basic_widget_test.dart | 6 +- .../fixtures/doubles/create_diet_doubles.dart | 9 +- 15 files changed, 173 insertions(+), 139 deletions(-) diff --git a/lib/features/create_diet/presentation/create_diet_screen.dart b/lib/features/create_diet/presentation/create_diet_screen.dart index 6596fd87..112e413d 100644 --- a/lib/features/create_diet/presentation/create_diet_screen.dart +++ b/lib/features/create_diet/presentation/create_diet_screen.dart @@ -180,41 +180,67 @@ class _CreateDietScreenContentState extends State { padding: const EdgeInsets.symmetric(horizontal: 16.0), child: Column( children: [ - CustomOutlinedButton( - onPressed: () async { - if (widget.diet != null && widget.isEditing == true) { - if (state.dietState == DietState.draft) { - context.read().setDietState(DietState.active); + if (state.isDietBeingEdited) + const Loader() + else + CustomOutlinedButton( + onPressed: () async { + if (state.isDietPublishing) return; + if (widget.diet != null && widget.isEditing == true) { + final dietState = state.dietState; + if (dietState == DietState.draft) { + context.read().setDietState(DietState.active); + } else { + context.read().setDietState(DietState.draft); + } + final result = await context.read().updateDiet(widget.diet!.id); + if (result == 'error') { + if (!context.mounted) return; + showErrorDialog(context, title: '${s.could_not_update_diet} ${state.dietData.name}'); + return; + } + if (dietState == DietState.draft) { + Future.delayed(const Duration(milliseconds: 500), () { + if (!context.mounted) return; + context.pop(); + showBottomSheetWithBlur( + userRootNavigator: true, + context: context, + child: SuccessPublishedDietOrPlanBottomSheet(title: state.dietData.name ?? 'Диета'), + ); + }); + } else { + Future.delayed(const Duration(milliseconds: 500), () { + if (!context.mounted) return; + context.pop(); + showBottomSheetWithBlur( + context: context, + child: SuccessPublishedDietOrPlanBottomSheet(title: state.dietData.name ?? 'Диета', isDrafted: true), + ); + }); + } } else { - context.read().setDietState(DietState.draft); + if (state.dietState != DietState.draft) { + context.read().setDietState(DietState.draft); + } + final result = await context.read().createDiet(); + if (result == 'error') { + if (!context.mounted) return; + showErrorDialog(context, title: '${s.could_not_publish_diet} ${state.dietData.name}'); + } } - final result = await context.read().updateDiet(widget.diet!.id); - if (result == 'error') { - if (!context.mounted) return; - showErrorDialog(context, title: '${s.could_not_update_diet} ${state.dietData.name}'); - } - } else { - if (state.dietState != DietState.draft) { - context.read().setDietState(DietState.draft); - } - final result = await context.read().createDiet(); - if (result == 'error') { - if (!context.mounted) return; - showErrorDialog(context, title: '${s.could_not_publish_diet} ${state.dietData.name}'); - } - } - }, - text: state.dietState == DietState.draft && widget.isEditing == true ? s.publish : "В черновик", - height: 52, - buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.gray), - buttonStyle: ButtonStyle( - side: const WidgetStatePropertyAll(BorderSide.none), - backgroundColor: WidgetStatePropertyAll(appTheme.dottedBorder), + }, + text: state.dietState == DietState.draft && widget.isEditing == true ? s.publish : "В черновик", + height: 52, + buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.gray), + buttonStyle: ButtonStyle( + side: const WidgetStatePropertyAll(BorderSide.none), + backgroundColor: WidgetStatePropertyAll(appTheme.dottedBorder), + ), ), - ), const SizedBox(height: 12), CustomOutlinedButton( - onPressed: state.isDietPublishing + onPressed: state.isDietPublishing || state.isDietBeingEdited ? () {} : () async { final diet = await context.read().getDietForPreview(); @@ -238,6 +264,7 @@ class _CreateDietScreenContentState extends State { else CustomOutlinedButton( onPressed: () async { + if (state.isDietBeingEdited) return; if (widget.diet != null && widget.isEditing == true) { final result = await context.read().updateDiet(widget.diet!.id); if (result == 'error') { 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 989ea170..9c460720 100644 --- a/lib/features/create_diet/presentation/cubit/create_diet_cubit.dart +++ b/lib/features/create_diet/presentation/cubit/create_diet_cubit.dart @@ -66,8 +66,7 @@ class CreateDietCubit extends Cubit { imageUrl: appConfig.buildType == AppBuildType.tests ? null : imageUrl, ), ); - emit(state.copyWith(dietState: diet.state, durationIndex: durationValues.indexOf(diet.duration))); - emit(state.copyWith(isLoading: true)); + emit(state.copyWith(isLoading: false, isDietBeingEdited: false, dietState: diet.state, durationIndex: durationValues.indexOf(diet.duration))); } void setDetails(String text) { @@ -356,9 +355,12 @@ class CreateDietCubit extends Cubit { } Future updateDiet(int dietId) async { + emit(state.copyWith(isDietBeingEdited: true)); if ([DietState.active, DietState.personal].contains(state.dietState) && !_isFromValid()) { + emit(state.copyWith(isDietBeingEdited: false)); return 'error'; } else if (!_isFormValidForDraft()) { + emit(state.copyWith(isDietBeingEdited: false)); return 'error'; } @@ -380,10 +382,12 @@ class CreateDietCubit extends Cubit { details: state.dietData.details?.isEmpty ?? true ? null : state.dietData.details, dailySchedule: state.dietData.dailySchedule, ); + emit(state.copyWith(isDietBeingEdited: false)); return null; } catch (e, st) { await Sentry.captureException(e, stackTrace: st); log(e.toString(), stackTrace: st); + emit(state.copyWith(isDietBeingEdited: false)); return 'error'; } } 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 dc280baa..c55f6b91 100644 --- a/lib/features/create_diet/presentation/cubit/create_diet_state.dart +++ b/lib/features/create_diet/presentation/cubit/create_diet_state.dart @@ -27,12 +27,11 @@ abstract class CreateDietState with _$CreateDietState { }) = _CreateDietState; factory CreateDietState.initial() => CreateDietState( - dailyScheduleErrors: List.filled(21, ''), - isDietPublishing: false, - isLoading: false, - errorMessage: null, - dietData: CreateDietModel( - type: Map.fromIterable(dietTypeValues, value: (element) => false), - ), - ); + dailyScheduleErrors: List.filled(21, ''), + isDietPublishing: false, + isLoading: false, + errorMessage: null, + isDietBeingEdited: false, + dietData: CreateDietModel(type: Map.fromIterable(dietTypeValues, value: (element) => false)), + ); } diff --git a/lib/features/create_diet/repositories/create_diet_repository.dart b/lib/features/create_diet/repositories/create_diet_repository.dart index d63be30f..feb37496 100644 --- a/lib/features/create_diet/repositories/create_diet_repository.dart +++ b/lib/features/create_diet/repositories/create_diet_repository.dart @@ -106,7 +106,7 @@ class CreateDietRepository { } } - Future updateDiet({ + Future updateDiet({ required int dietId, required DietState state, String? name, @@ -164,12 +164,17 @@ class CreateDietRepository { try { switch (state) { case DietState.active: - return await createDietService.updatePublished(diet: dietId, formData: formData); + final response = await createDietService.updatePublished(diet: dietId, formData: formData); + return response.response.statusCode ?? 0; case DietState.draft: - return await createDietService.updateDrafted(diet: dietId, formData: formData); + final response = await createDietService.updateDrafted(diet: dietId, formData: formData); + return response.response.statusCode ?? 0; case DietState.personal: - return await createDietService.updatePersonal(diet: dietId, formData: formData); + final response = await createDietService.updatePersonal(diet: dietId, formData: formData); + return response.response.statusCode ?? 0; } + } on DioException catch (e) { + return e.response?.statusCode ?? 0; } catch (e, s) { await Sentry.captureException(e, stackTrace: s); rethrow; diff --git a/lib/features/create_diet/services/create_diet_service.dart b/lib/features/create_diet/services/create_diet_service.dart index 3c0d8923..cc010293 100644 --- a/lib/features/create_diet/services/create_diet_service.dart +++ b/lib/features/create_diet/services/create_diet_service.dart @@ -28,21 +28,21 @@ abstract class CreateDietService { @POST('/api/diets/update/published/{diet}') @MultiPart() - Future updatePublished({ + Future updatePublished({ @Path() required int diet, @Body() required FormData formData, }); @POST('/api/diets/update/drafted/{diet}') @MultiPart() - Future updateDrafted({ + Future updateDrafted({ @Path() required int diet, @Body() required FormData formData, }); @POST('/api/diets/update/personalized/{diet}') @MultiPart() - Future updatePersonal({ + Future updatePersonal({ @Path() required int diet, @Body() required FormData formData, }); 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 8a93ec17..398fdb63 100644 --- a/lib/features/my_diets/presentation/cubit/my_diets_cubit.dart +++ b/lib/features/my_diets/presentation/cubit/my_diets_cubit.dart @@ -291,16 +291,16 @@ class MyDietsCubit extends Cubit { } } - Future publishDiet(int dietId) async { + Future publishDiet(int dietId) async { try { - await createDietRepository.updateDiet( + final statusCode = await createDietRepository.updateDiet( dietId: dietId, state: DietState.active, ); - return true; + return statusCode; } catch (e, st) { log(e.toString(), stackTrace: st); - return false; + return 0; } } diff --git a/lib/features/my_diets/presentation/my_diets_screen.dart b/lib/features/my_diets/presentation/my_diets_screen.dart index 51f7ee39..1c5bad54 100644 --- a/lib/features/my_diets/presentation/my_diets_screen.dart +++ b/lib/features/my_diets/presentation/my_diets_screen.dart @@ -142,7 +142,8 @@ class MyDietsScreenContent extends StatelessWidget { } final diets = switch (state.selectedStateIndex) { - 1 => state.personalDiets, + 2 => state.personalDiets, + 1 => state.draftDiets, _ => state.activeDiets, }; 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 e2dec663..019cc487 100644 --- a/lib/features/my_diets/presentation/widgets/my_diet_widget.dart +++ b/lib/features/my_diets/presentation/widgets/my_diet_widget.dart @@ -36,8 +36,11 @@ class MyDietWidget extends StatefulWidget { } class _MyDietWidgetState extends State { - void _editDiet(Diet diet) { - GoRouter.of(context).pushNamed(CreateDietScreen.name, extra: {'diet': diet, 'isEditing': true}); + + Future _editDiet(Diet diet, BuildContext context) async{ + await GoRouter.of(context).pushNamed(CreateDietScreen.name, extra: {'diet': diet, 'isEditing': true}); + if (!context.mounted) return; + await context.read().loadAllDiets(); } void _suggestDiet(Diet diet) { @@ -49,16 +52,19 @@ class _MyDietWidgetState extends State { } Future _publishDiet(Diet diet, BuildContext context) async { - final result = await context.read().publishDiet(widget.diet.id); - if (result) { - if (!context.mounted) return; - showBottomSheetWithBlur( - context: context, - child: SuccessPublishedDietOrPlanBottomSheet(title: widget.diet.name), - ); - await context.read().loadAllDiets(); - } else { - showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время'); + final statusCode = await context.read().publishDiet(widget.diet.id); + switch (statusCode) { + case 201: + if (!context.mounted) return; + showBottomSheetWithBlur( + context: context, + child: SuccessPublishedDietOrPlanBottomSheet(title: widget.diet.name), + ); + await context.read().loadAllDiets(); + case 422: + showErrorToast('Пожалуйста, заполните все обязательные поля для публикации'); + default: + showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время'); } } @@ -169,35 +175,8 @@ class _MyDietWidgetState extends State { padding: const EdgeInsets.symmetric(horizontal: 16.0), child: Column( children: [ - // Верхний ряд: Удалить + Редактировать (или + Поделиться для опубликованных) Row( children: [ - // Кнопка удаления (слева) - CustomOutlinedButton( - onPressed: () async { - final result = await context.read().deleteDiet(widget.diet.id); - if (result) { - if (!context.mounted) return; - showBottomSheetWithBlur( - context: context, - child: DietDeletedBottomSheet(title: widget.diet.name), - ); - await context.read().loadAllDiets(); - } - }, - height: 52, - width: 52, - leftIcon: SvgPicture.asset('assets/icons/trash_can.svg', width: 20, height: 20), - buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.red), - buttonStyle: ButtonStyle( - shape: WidgetStatePropertyAll(RoundedRectangleBorder(borderRadius: BorderRadius.circular(20))), - backgroundColor: WidgetStatePropertyAll(appTheme.lightGray0), - side: const WidgetStatePropertyAll(BorderSide.none), - ), - text: '', - ), - const SizedBox(width: 6), - // Кнопка редактирования (центр) Expanded( child: CustomOutlinedButton( onPressed: () async { @@ -206,20 +185,23 @@ class _MyDietWidgetState extends State { } else if (state.selectedStateIndex == 2) { _suggestDiet(widget.diet); } else { - _editDiet(widget.diet); - } + _editDiet(widget.diet, context); + } + }, + text: switch (state.selectedStateIndex) { + 1 => s.publish, + 2 => s.toOffer, + _ => s.edit, }, - text: s.edit, height: 52, - buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.black), + buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700), buttonStyle: ButtonStyle( shape: WidgetStatePropertyAll(RoundedRectangleBorder(borderRadius: BorderRadius.circular(20))), - backgroundColor: WidgetStatePropertyAll(appTheme.lightGray0), + backgroundColor: WidgetStatePropertyAll(appTheme.blueA400), side: const WidgetStatePropertyAll(BorderSide.none), ), ), ), - // Кнопка поделиться (справа) для опубликованных if (state.selectedStateIndex == 0) ...[ const SizedBox(width: 6), CustomOutlinedButton( @@ -315,7 +297,7 @@ class _MyDietWidgetState extends State { if (state.selectedStateIndex == 0) { _unpublishDiet(widget.diet, context); } else { - _editDiet(widget.diet); + _editDiet(widget.diet, context); } }, text: switch (state.selectedStateIndex) { diff --git a/lib/features/my_diets/presentation/widgets/my_diets_tab.dart b/lib/features/my_diets/presentation/widgets/my_diets_tab.dart index be6a0d95..a653441c 100644 --- a/lib/features/my_diets/presentation/widgets/my_diets_tab.dart +++ b/lib/features/my_diets/presentation/widgets/my_diets_tab.dart @@ -21,6 +21,7 @@ class _MyDietsTabState extends State { final s = S.of(context); if (tabTitles.isEmpty) { tabTitles.add(s.published); + tabTitles.add(s.drafted); tabTitles.add(s.personal); } super.didChangeDependencies(); @@ -30,29 +31,37 @@ class _MyDietsTabState extends State { Widget build(BuildContext context) { return BlocBuilder( builder: (context, state) { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 8), - child: Container( - height: 48, - decoration: BoxDecoration( - color: appTheme.whiteA700, - borderRadius: BorderRadius.circular(100), - ), - padding: const EdgeInsets.all(4), + return Align( + alignment: Alignment.centerLeft, + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, child: Row( children: [ - ...List.generate( - 2, - (index) => Expanded( - child: AnimatedOptionButton( - textStyle: TextStyles.textSMedium, - isSelected: state.selectedStateIndex == index, - text: tabTitles[index], - onTap: () { - widget.additionalOnTap?.call(); - context.read().changeSelectedStateIndex(index); - }, - ), + Container( + width: 484, + height: 48, + decoration: BoxDecoration( + color: appTheme.whiteA700, + borderRadius: BorderRadius.circular(100), + ), + padding: const EdgeInsets.all(4), + child: Row( + children: [ + ...List.generate( + 3, + (index) => Expanded( + child: AnimatedOptionButton( + textStyle: TextStyles.textSMedium, + isSelected: state.selectedStateIndex == index, + text: tabTitles[index], + onTap: () { + widget.additionalOnTap?.call(); + context.read().changeSelectedStateIndex(index); + }, + ), + ), + ), + ], ), ), ], diff --git a/test/features/create_diet/integration_widget_test.dart b/test/features/create_diet/integration_widget_test.dart index 541056f6..0d8c7b31 100644 --- a/test/features/create_diet/integration_widget_test.dart +++ b/test/features/create_diet/integration_widget_test.dart @@ -326,7 +326,7 @@ void main() { expect(find.byType(DietTypeChoiceWidget), findsOneWidget); expect(find.byType(DietImagePickerWidget), findsOneWidget); // Action buttons - expect(find.byType(CustomOutlinedButton), findsNWidgets(2)); + expect(find.byType(CustomOutlinedButton), findsNWidgets(3)); }); }); } diff --git a/test/features/create_diet/presentation/cubit/create_diet_cubit_test.dart b/test/features/create_diet/presentation/cubit/create_diet_cubit_test.dart index 8cba4984..5feac64f 100644 --- a/test/features/create_diet/presentation/cubit/create_diet_cubit_test.dart +++ b/test/features/create_diet/presentation/cubit/create_diet_cubit_test.dart @@ -432,7 +432,7 @@ void main() { await cubit.setInitialData(testDiet); expect(cubit.dietId, 1); - expect(cubit.state.isDietBeingEdited, true); + expect(cubit.state.isDietBeingEdited, false); expect(cubit.state.dietData.name, 'Test Diet'); expect(cubit.state.dietData.details, 'Test details'); expect(cubit.state.dietData.duration, 7); diff --git a/test/features/create_diet/repositories/create_diet_repository_test.dart b/test/features/create_diet/repositories/create_diet_repository_test.dart index 602097ad..f80d4eb5 100644 --- a/test/features/create_diet/repositories/create_diet_repository_test.dart +++ b/test/features/create_diet/repositories/create_diet_repository_test.dart @@ -6,10 +6,16 @@ import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; +import 'package:retrofit/dio.dart'; import 'package:retrofit/retrofit.dart'; import 'create_diet_repository_test.mocks.dart'; +HttpResponse _okResponse() => HttpResponse( + null, + Response(requestOptions: RequestOptions(path: ''), statusCode: 200), + ); + @GenerateNiceMocks([MockSpec()]) void main() { group('CreateDietRepository', () { @@ -152,7 +158,7 @@ void main() { final testFile = File('${Directory.systemTemp.path}/test_file.jpg'); await testFile.writeAsString('test content'); - when(mockService.updatePublished(diet: dietId, formData: anyNamed('formData'))).thenAnswer((_) async => Future.value()); + when(mockService.updatePublished(diet: dietId, formData: anyNamed('formData'))).thenAnswer((_) async => _okResponse()); await repository.updateDiet(dietId: dietId, state: DietState.active, name: 'Updated Diet', duration: 14, details: 'Updated details', image: testFile); @@ -167,7 +173,7 @@ void main() { test('updateDiet - обновляет черновик диеты (draft)', () async { const dietId = 1; - when(mockService.updateDrafted(diet: dietId, formData: anyNamed('formData'))).thenAnswer((_) async => Future.value()); + when(mockService.updateDrafted(diet: dietId, formData: anyNamed('formData'))).thenAnswer((_) async => _okResponse()); await repository.updateDiet(dietId: dietId, state: DietState.draft, name: 'Updated Diet'); @@ -177,7 +183,7 @@ void main() { test('updateDiet - обновляет персональную диету (personal)', () async { const dietId = 1; - when(mockService.updatePersonal(diet: dietId, formData: anyNamed('formData'))).thenAnswer((_) async => Future.value()); + when(mockService.updatePersonal(diet: dietId, formData: anyNamed('formData'))).thenAnswer((_) async => _okResponse()); await repository.updateDiet(dietId: dietId, state: DietState.personal, name: 'Updated Diet'); @@ -242,7 +248,7 @@ void main() { ], ]; - when(mockService.updatePublished(diet: dietId, formData: anyNamed('formData'))).thenAnswer((_) async => Future.value()); + when(mockService.updatePublished(diet: dietId, formData: anyNamed('formData'))).thenAnswer((_) async => _okResponse()); await repository.updateDiet( dietId: dietId, @@ -257,7 +263,7 @@ void main() { test('updateDiet - обновляет диету без image', () async { const dietId = 1; - when(mockService.updatePublished(diet: dietId, formData: anyNamed('formData'))).thenAnswer((_) async => Future.value()); + when(mockService.updatePublished(diet: dietId, formData: anyNamed('formData'))).thenAnswer((_) async => _okResponse()); await repository.updateDiet( dietId: dietId, @@ -273,7 +279,7 @@ void main() { test('updateDiet - обновляет диету только с типами', () async { const dietId = 1; - when(mockService.updatePublished(diet: dietId, formData: anyNamed('formData'))).thenAnswer((_) async => Future.value()); + when(mockService.updatePublished(diet: dietId, formData: anyNamed('formData'))).thenAnswer((_) async => _okResponse()); await repository.updateDiet( dietId: dietId, @@ -305,7 +311,7 @@ void main() { ], ]; - when(mockService.updatePublished(diet: dietId, formData: anyNamed('formData'))).thenAnswer((_) async => Future.value()); + when(mockService.updatePublished(diet: dietId, formData: anyNamed('formData'))).thenAnswer((_) async => _okResponse()); await repository.updateDiet( dietId: dietId, diff --git a/test/features/my_diets/presentation/cubit/my_diets_cubit_test.dart b/test/features/my_diets/presentation/cubit/my_diets_cubit_test.dart index 66526ea4..bb990eec 100644 --- a/test/features/my_diets/presentation/cubit/my_diets_cubit_test.dart +++ b/test/features/my_diets/presentation/cubit/my_diets_cubit_test.dart @@ -269,11 +269,11 @@ void main() { when(mockCreateDietRepository.updateDiet( dietId: 1, state: DietState.active, - )).thenAnswer((_) async => {}); + )).thenAnswer((_) async => 200); final result = await myDietsCubit!.publishDiet(1); - expect(result, true); + expect(result, 200); verify(mockCreateDietRepository.updateDiet(dietId: 1, state: DietState.active)).called(1); }); @@ -290,7 +290,7 @@ void main() { final result = await myDietsCubit!.publishDiet(1); - expect(result, false); + expect(result, 0); }); test('unpublishDiet - снимает диету с публикации', () async { @@ -302,7 +302,7 @@ void main() { when(mockCreateDietRepository.updateDiet( dietId: 1, state: DietState.draft, - )).thenAnswer((_) async => {}); + )).thenAnswer((_) async => 200); final result = await myDietsCubit!.unpublishDiet(1); diff --git a/test/features/my_diets/presentation/widgets/basic_widget_test.dart b/test/features/my_diets/presentation/widgets/basic_widget_test.dart index ef674d0f..9afb6818 100644 --- a/test/features/my_diets/presentation/widgets/basic_widget_test.dart +++ b/test/features/my_diets/presentation/widgets/basic_widget_test.dart @@ -106,7 +106,7 @@ void main() { // Assert expect(find.byType(MyDietsTab), findsOneWidget); - expect(find.byType(AnimatedOptionButton), findsNWidgets(2)); + expect(find.byType(AnimatedOptionButton), findsNWidgets(3)); }); testWidgets('should handle tab selection in MyDietsTab', (WidgetTester tester) async { @@ -119,7 +119,7 @@ void main() { // Act - Find and tap the second tab final tabButtons = find.byType(AnimatedOptionButton); - expect(tabButtons, findsNWidgets(2)); + expect(tabButtons, findsNWidgets(3)); // Tap the second tab (index 1) await tester.tap(tabButtons.at(1)); @@ -221,7 +221,7 @@ void main() { // Assert expect(find.byType(MyDietsTab), findsOneWidget); - expect(find.byType(AnimatedOptionButton), findsNWidgets(2)); + expect(find.byType(AnimatedOptionButton), findsNWidgets(3)); }); testWidgets('should handle MyDietsTab additionalOnTap callback', (WidgetTester tester) async { diff --git a/test/fixtures/doubles/create_diet_doubles.dart b/test/fixtures/doubles/create_diet_doubles.dart index 08b16861..52645ae5 100644 --- a/test/fixtures/doubles/create_diet_doubles.dart +++ b/test/fixtures/doubles/create_diet_doubles.dart @@ -104,7 +104,7 @@ class FixtureCreateDietRepository extends CreateDietRepository { } @override - Future updateDiet({ + Future updateDiet({ required int dietId, required DietState state, String? name, @@ -131,6 +131,7 @@ class FixtureCreateDietRepository extends CreateDietRepository { if (lightType != null) 'light': lightType == '1', }, }); + return 201; } } @@ -166,17 +167,17 @@ class _NoopCreateDietService implements CreateDietService { } @override - Future updateDrafted({required int diet, required FormData formData}) { + Future updateDrafted({required int diet, required FormData formData}) { throw UnimplementedError(); } @override - Future updatePersonal({required int diet, required FormData formData}) { + Future updatePersonal({required int diet, required FormData formData}) { throw UnimplementedError(); } @override - Future updatePublished({required int diet, required FormData formData}) { + Future updatePublished({required int diet, required FormData formData}) { throw UnimplementedError(); } } From b7804db49de268351f0feb9ae58f9789213bfe71 Mon Sep 17 00:00:00 2001 From: misha Date: Wed, 11 Feb 2026 16:54:08 +0300 Subject: [PATCH 14/58] feat(my_diets): merge conflict fix --- .../presentation/create_diet_screen.dart | 254 +++++++++--------- 1 file changed, 123 insertions(+), 131 deletions(-) diff --git a/lib/features/create_diet/presentation/create_diet_screen.dart b/lib/features/create_diet/presentation/create_diet_screen.dart index bc1d1445..4678f989 100644 --- a/lib/features/create_diet/presentation/create_diet_screen.dart +++ b/lib/features/create_diet/presentation/create_diet_screen.dart @@ -82,12 +82,7 @@ class _CreateDietScreenContentState extends State { } if (key != null && key.currentContext != null) { - Scrollable.ensureVisible( - key.currentContext!, - duration: const Duration(milliseconds: 500), - curve: Curves.easeInOut, - alignment: 0.1, - ); + Scrollable.ensureVisible(key.currentContext!, duration: const Duration(milliseconds: 500), curve: Curves.easeInOut, alignment: 0.1); } } @@ -213,134 +208,131 @@ class _CreateDietScreenContentState extends State { const SizedBox(height: 12), Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0), - child: Column( - children: [ - if (state.isDietBeingEdited) - const Loader() - else - CustomOutlinedButton( - onPressed: () async { - if (state.isDietPublishing) return; - if (widget.diet != null && widget.isEditing == true) { - final dietState = state.dietState; - if (dietState == DietState.draft) { - context.read().setDietState(DietState.active); - } else { - context.read().setDietState(DietState.draft); - } - final result = await context.read().updateDiet(widget.diet!.id); - if (result == 'error') { - if (!context.mounted) return; - showErrorDialog(context, title: '${s.could_not_update_diet} ${state.dietData.name}'); - return; - } - if (dietState == DietState.draft) { - Future.delayed(const Duration(milliseconds: 500), () { - if (!context.mounted) return; - context.pop(); - showBottomSheetWithBlur( - userRootNavigator: true, - context: context, - child: SuccessPublishedDietOrPlanBottomSheet(title: state.dietData.name ?? 'Диета'), - ); - }); - } else { - Future.delayed(const Duration(milliseconds: 500), () { - if (!context.mounted) return; - context.pop(); - showBottomSheetWithBlur( - context: context, - child: SuccessPublishedDietOrPlanBottomSheet(title: state.dietData.name ?? 'Диета', isDrafted: true), - ); - }); - } - } else { - if (state.dietState != DietState.draft) { - context.read().setDietState(DietState.draft); - } - final result = await context.read().createDiet(); - if (result == 'error') { - if (!context.mounted) return; - showErrorDialog(context, title: '${s.could_not_publish_diet} ${state.dietData.name}'); - } - } - }, - text: state.dietState == DietState.draft && widget.isEditing == true ? s.publish : "В черновик", - height: 52, - buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.gray), - buttonStyle: ButtonStyle( - side: const WidgetStatePropertyAll(BorderSide.none), - backgroundColor: WidgetStatePropertyAll(appTheme.dottedBorder), - ), - ), - const SizedBox(height: 12), - CustomOutlinedButton( - onPressed: state.isDietPublishing || state.isDietBeingEdited - ? () {} - : () async { - final diet = await context.read().getDietForPreview(); - if (diet == null) { - // ignore: use_build_context_synchronously - _scrollToFirstError(context.read().state); - return; + child: state.isDietBeingEdited || state.isDietPublishing + ? const Loader() + : Column( + children: [ + CustomOutlinedButton( + onPressed: () async { + if (state.isDietPublishing) return; + if (widget.diet != null && widget.isEditing == true) { + final dietState = state.dietState; + if (dietState == DietState.draft) { + context.read().setDietState(DietState.active); + } else { + context.read().setDietState(DietState.draft); + } + final result = await context.read().updateDiet(widget.diet!.id); + if (result == 'error') { + if (!context.mounted) return; + showErrorDialog(context, title: '${s.could_not_update_diet} ${state.dietData.name}'); + return; + } + if (dietState == DietState.draft) { + Future.delayed(const Duration(milliseconds: 500), () { + if (!context.mounted) return; + context.pop(); + showBottomSheetWithBlur( + userRootNavigator: true, + context: context, + child: SuccessPublishedDietOrPlanBottomSheet(title: state.dietData.name ?? 'Диета'), + ); + }); + } else { + Future.delayed(const Duration(milliseconds: 500), () { + if (!context.mounted) return; + context.pop(); + showBottomSheetWithBlur( + context: context, + child: SuccessPublishedDietOrPlanBottomSheet(title: state.dietData.name ?? 'Диета', isDrafted: true), + ); + }); + } + } else { + if (state.dietState != DietState.draft) { + context.read().setDietState(DietState.draft); + } + final result = await context.read().createDiet(); + if (result == 'error') { + if (!context.mounted) return; + showErrorDialog(context, title: '${s.could_not_publish_diet} ${state.dietData.name}'); + } } - if (!context.mounted) return; - GoRouter.of( - context, - ).pushNamed(DietPreviewScreen.name, extra: {'diet': diet, 'isImageInBase64': state.dietData.imageUrl == null}); }, - text: s.preview, - height: 52, - buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.gray), - buttonStyle: ButtonStyle( - side: const WidgetStatePropertyAll(BorderSide.none), - backgroundColor: WidgetStatePropertyAll(appTheme.dottedBorder), + text: state.dietState == DietState.draft && widget.isEditing == true ? s.publish : "В черновик", + height: 52, + buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.gray), + buttonStyle: ButtonStyle( + side: const WidgetStatePropertyAll(BorderSide.none), + backgroundColor: WidgetStatePropertyAll(appTheme.dottedBorder), + ), + ), + const SizedBox(height: 12), + CustomOutlinedButton( + onPressed: state.isDietPublishing || state.isDietBeingEdited + ? () {} + : () async { + final diet = await context.read().getDietForPreview(); + if (diet == null) { + // ignore: use_build_context_synchronously + _scrollToFirstError(context.read().state); + return; + } + if (!context.mounted) return; + GoRouter.of( + context, + ).pushNamed(DietPreviewScreen.name, extra: {'diet': diet, 'isImageInBase64': state.dietData.imageUrl == null}); + }, + text: s.preview, + height: 52, + buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.gray), + buttonStyle: ButtonStyle( + side: const WidgetStatePropertyAll(BorderSide.none), + backgroundColor: WidgetStatePropertyAll(appTheme.dottedBorder), + ), + ), + const SizedBox(height: 12), + + CustomOutlinedButton( + onPressed: () async { + if (state.isDietBeingEdited) return; + if (widget.diet != null && widget.isEditing == true) { + final result = await context.read().updateDiet(widget.diet!.id); + if (result == 'error') { + if (!context.mounted) return; + showErrorDialog(context, title: '${s.could_not_update_diet} ${state.dietData.name}'); + } else { + Future.delayed(const Duration(milliseconds: 500), () { + if (!context.mounted) return; + context.pop(); + showBottomSheetWithBlur( + userRootNavigator: true, + context: context, + child: SuccessPublishedDietOrPlanBottomSheet(title: state.dietData.name ?? 'Диета', isUpdated: true), + ); + }); + } + } else { + final result = await context.read().createDiet(); + if (result == 'validation') { + // ignore: use_build_context_synchronously + _scrollToFirstError(context.read().state); + } else if (result == 'error') { + if (!context.mounted) return; + showErrorDialog(context, title: '${s.could_not_publish_diet} ${state.dietData.name}'); + } + } + }, + text: widget.diet != null && widget.isEditing == true ? s.save : s.publish, + height: 52, + buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700), + buttonStyle: ButtonStyle( + side: const WidgetStatePropertyAll(BorderSide.none), + backgroundColor: WidgetStatePropertyAll(appTheme.blueA400), + ), + ), + ], ), - ), - const SizedBox(height: 12), - if (state.isDietPublishing) - const Loader() - else - CustomOutlinedButton( - onPressed: () async { - if (state.isDietBeingEdited) return; - if (widget.diet != null && widget.isEditing == true) { - final result = await context.read().updateDiet(widget.diet!.id); - if (result == 'error') { - if (!context.mounted) return; - showErrorDialog(context, title: '${s.could_not_update_diet} ${state.dietData.name}'); - } else { - Future.delayed(const Duration(milliseconds: 500), () { - if (!context.mounted) return; - context.pop(); - showBottomSheetWithBlur( - userRootNavigator: true, - context: context, - child: SuccessPublishedDietOrPlanBottomSheet(title: state.dietData.name ?? 'Диета', isUpdated: true), - ); - }); - } - } else { - final result = await context.read().createDiet(); - if (result == 'validation') { - // ignore: use_build_context_synchronously - _scrollToFirstError(context.read().state); - } else if (result == 'error') { - if (!context.mounted) return; - showErrorDialog(context, title: '${s.could_not_publish_diet} ${state.dietData.name}'); - } - } - }, - text: widget.diet != null && widget.isEditing == true ? s.save : s.publish, - height: 52, - buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700), - buttonStyle: ButtonStyle( - side: const WidgetStatePropertyAll(BorderSide.none), - backgroundColor: WidgetStatePropertyAll(appTheme.blueA400), - ), - ), - ], - ), ), const SizedBox(height: 20), ], From bb97c395dded9828966fa49c74fb41f696eb9349 Mon Sep 17 00:00:00 2001 From: mso7nueh Date: Thu, 12 Feb 2026 11:25:06 +0300 Subject: [PATCH 15/58] feat: Implement comprehensive navigation logic for various notification types within the NotificationWidget. --- .gitignore | 3 +- lib/core/routing/content_routes.dart | 66 +- .../presentation/pages/blog_post_screen.dart | 11 +- .../pages/blog_post_create_screen.dart | 1 + .../presentation/pages/about_diet_screen.dart | 4 +- .../presentation/widgets/comments_widget.dart | 52 +- .../models/notification_model.dart | 392 +++++++++++- .../notifications_cubit.dart | 76 ++- .../widgets/notification_widget.dart | 589 ++++++++++++++++-- .../service/notifications_service.dart | 72 +++ .../presentation/pages/about_plan_screen.dart | 25 +- .../pages/about_recipe_screen.dart | 4 +- .../pages/about_diet_screen_smoke_test.dart | 8 +- .../models/notification_model_test.dart | 2 +- .../widgets/notification_widget_test.dart | 6 + .../pages/about_plan_screen_smoke_test.dart | 6 +- 16 files changed, 1211 insertions(+), 106 deletions(-) diff --git a/.gitignore b/.gitignore index 530ea713..1503b5ce 100644 --- a/.gitignore +++ b/.gitignore @@ -20,4 +20,5 @@ env/.env .env android/key.properties app/upload-keystore.jks -coverage/ \ No newline at end of file +coverage/ +backend/ \ No newline at end of file diff --git a/lib/core/routing/content_routes.dart b/lib/core/routing/content_routes.dart index 0556fab6..5a93013c 100644 --- a/lib/core/routing/content_routes.dart +++ b/lib/core/routing/content_routes.dart @@ -17,12 +17,25 @@ class ContentRoutes { name: 'aboutPlanScreen', builder: (context, state) { final slug = state.uri.queryParameters['slug']; + final commentIdStr = state.uri.queryParameters['commentId']; + final commentId = int.tryParse(commentIdStr ?? ''); + if (slug != null) { - return AboutPlanScreen(slug: slug, navigateToComments: false, index: null); + return AboutPlanScreen( + slug: slug, + navigateToComments: commentId != null, + index: null, + highlightedCommentId: commentId, + ); } final data = state.extra as Map; - return AboutPlanScreen(slug: data['slug'] as String, index: data['index'] as int?, navigateToComments: data['navigateToComments'] as bool); + return AboutPlanScreen( + slug: data['slug'] as String, + index: data['index'] as int?, + navigateToComments: data['navigateToComments'] as bool? ?? (data['commentId'] != null), + highlightedCommentId: data['commentId'] as int?, + ); }, ), GoRoute( @@ -30,12 +43,25 @@ class ContentRoutes { name: 'aboutRecipesScreen', builder: (context, state) { final slug = state.uri.queryParameters['slug']; + final commentIdStr = state.uri.queryParameters['commentId']; + final commentId = int.tryParse(commentIdStr ?? ''); + if (slug != null) { - return AboutRecipeScreen(recipeSlug: slug, navigateToComments: false, index: null); + return AboutRecipeScreen( + recipeSlug: slug, + navigateToComments: commentId != null, + index: null, + highlightedCommentId: commentId, + ); } final data = state.extra as Map; - return AboutRecipeScreen(recipeSlug: data['recipe'] as String, index: data['index'] as int?, navigateToComments: data['navigateToComments'] as bool); + return AboutRecipeScreen( + recipeSlug: data['recipe'] as String, + index: data['index'] as int?, + navigateToComments: data['navigateToComments'] as bool? ?? (data['commentId'] != null), + highlightedCommentId: data['commentId'] as int?, + ); }, ), GoRoute( @@ -43,15 +69,25 @@ class ContentRoutes { name: 'aboutDietScreen', builder: (context, state) { final slug = state.uri.queryParameters['slug']; + final commentIdStr = state.uri.queryParameters['commentId']; + final commentId = int.tryParse(commentIdStr ?? ''); + if (slug != null) { - return AboutDietScreen(id: null, slug: slug, index: null, navigateToComments: false); + return AboutDietScreen( + id: null, + slug: slug, + index: null, + navigateToComments: commentId != null, + highlightedCommentId: commentId, + ); } final data = state.extra as Map; return AboutDietScreen( id: data['id'] as int?, slug: data['slug'] as String?, index: data['index'] as int?, - navigateToComments: data['navigateToComments'] as bool, + navigateToComments: data['navigateToComments'] as bool? ?? (data['commentId'] != null), + highlightedCommentId: data['commentId'] as int?, ); }, ), @@ -65,12 +101,26 @@ class ContentRoutes { name: 'blogPostScreen', builder: (context, state) { final id = state.uri.queryParameters['id']; + final commentIdStr = state.uri.queryParameters['commentId']; + final commentId = int.tryParse(commentIdStr ?? ''); final parsedId = int.tryParse(id ?? ''); + if (parsedId != null) { - return BlogPostScreen(id: parsedId); + return BlogPostScreen( + id: parsedId, + highlightedCommentId: commentId, + ); } - return BlogPostScreen(id: state.extra as int); + if (state.extra is int) { + return BlogPostScreen(id: state.extra as int); + } + + final data = state.extra as Map; + return BlogPostScreen( + id: data['id'] as int, + highlightedCommentId: data['commentId'] as int?, + ); }, ), ]; diff --git a/lib/features/blog_post/presentation/pages/blog_post_screen.dart b/lib/features/blog_post/presentation/pages/blog_post_screen.dart index 70593f8c..bf50518a 100644 --- a/lib/features/blog_post/presentation/pages/blog_post_screen.dart +++ b/lib/features/blog_post/presentation/pages/blog_post_screen.dart @@ -21,8 +21,9 @@ import 'package:share_plus/share_plus.dart'; class BlogPostScreen extends StatefulWidget { final int id; + final int? highlightedCommentId; - const BlogPostScreen({super.key, required this.id}); + const BlogPostScreen({super.key, required this.id, this.highlightedCommentId}); @override State createState() => _BlogPostScreenState(); @@ -303,7 +304,13 @@ class _BlogPostScreenState extends State { const SizedBox(height: 16), if (state is CommentsStateLoading) const Loader(), if (state is CommentsStateLoaded) - CommentsWidget(id: widget.id.toString(), commentSourceType: CommentSourceType.blogPost, comment: state.comment, replyId: replyId), + CommentsWidget( + id: widget.id.toString(), + commentSourceType: CommentSourceType.blogPost, + comment: state.comment, + replyId: replyId, + highlightedCommentId: widget.highlightedCommentId, + ), ], ), ), diff --git a/lib/features/blog_post_create/presentation/pages/blog_post_create_screen.dart b/lib/features/blog_post_create/presentation/pages/blog_post_create_screen.dart index 0254d3c7..6da09b9e 100644 --- a/lib/features/blog_post_create/presentation/pages/blog_post_create_screen.dart +++ b/lib/features/blog_post_create/presentation/pages/blog_post_create_screen.dart @@ -568,6 +568,7 @@ class _BlogPostCreateScreenState extends State { CustomTextFormField(key: AppKeys.blogPostCreateScreenVideoLinkTextField, controller: videoUrlController), const SizedBox(height: 80), _createBlogPostButton(), + const SizedBox(height: 80), ], ), ), diff --git a/lib/features/diets/presentation/pages/about_diet_screen.dart b/lib/features/diets/presentation/pages/about_diet_screen.dart index 54d74215..3823b471 100644 --- a/lib/features/diets/presentation/pages/about_diet_screen.dart +++ b/lib/features/diets/presentation/pages/about_diet_screen.dart @@ -37,8 +37,9 @@ class AboutDietScreen extends StatefulWidget { final int? id; final int? index; final bool navigateToComments; + final int? highlightedCommentId; - const AboutDietScreen({super.key, required this.index, required this.navigateToComments, required this.slug, required this.id}); + const AboutDietScreen({super.key, required this.index, required this.navigateToComments, required this.slug, required this.id, this.highlightedCommentId}); @override State createState() => _AboutDietScreenState(); @@ -433,6 +434,7 @@ class _AboutDietScreenState extends State { commentSourceType: CommentSourceType.diet, comment: state.comment, replyId: replyId, + highlightedCommentId: widget.highlightedCommentId, ), ], ), diff --git a/lib/features/feed/presentation/widgets/comments_widget.dart b/lib/features/feed/presentation/widgets/comments_widget.dart index f8b3ecba..61ba39d2 100644 --- a/lib/features/feed/presentation/widgets/comments_widget.dart +++ b/lib/features/feed/presentation/widgets/comments_widget.dart @@ -23,14 +23,60 @@ class CommentsWidget extends StatefulWidget { final CommentSourceType commentSourceType; final CommentModel comment; final int? replyId; + final int? highlightedCommentId; - const CommentsWidget({super.key, required this.id, required this.commentSourceType, required this.comment, this.replyId}); + const CommentsWidget({ + super.key, + required this.id, + required this.commentSourceType, + required this.comment, + this.replyId, + this.highlightedCommentId, + }); @override State createState() => _CommentsWidgetState(); } class _CommentsWidgetState extends State { + final Map commentKeys = {}; + + @override + void initState() { + super.initState(); + if (widget.highlightedCommentId != null) { + WidgetsBinding.instance.addPostFrameCallback((_) { + _scrollToHighlightedComment(); + }); + } + } + + @override + void didUpdateWidget(CommentsWidget oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.highlightedCommentId != null && widget.highlightedCommentId != oldWidget.highlightedCommentId) { + WidgetsBinding.instance.addPostFrameCallback((_) { + _scrollToHighlightedComment(); + }); + } else if (widget.comment != oldWidget.comment && widget.highlightedCommentId != null) { + // Comments might have just loaded + WidgetsBinding.instance.addPostFrameCallback((_) { + _scrollToHighlightedComment(); + }); + } + } + + void _scrollToHighlightedComment() { + final key = commentKeys[widget.highlightedCommentId]; + if (key != null && key.currentContext != null) { + Scrollable.ensureVisible( + key.currentContext!, + duration: const Duration(milliseconds: 500), + curve: Curves.easeInOut, + ); + } + } + @override Widget build(BuildContext context) { final s = S.of(context); @@ -49,8 +95,10 @@ class _CommentsWidgetState extends State { } Widget _comment(S s, Comment comment, [double radius = 18, bool isChild = false]) { + final key = commentKeys.putIfAbsent(comment.id, () => GlobalKey()); final isMe = (sl().state as ProfileAvatarStateLoaded).profile.slug == comment.authorSlug; return GestureDetector( + key: key, onTap: () { showCommentsActionsBottomSheet( context, @@ -83,7 +131,7 @@ class _CommentsWidgetState extends State { child: Container( width: MediaQuery.of(context).size.width, decoration: BoxDecoration( - color: widget.replyId == comment.id ? appTheme.blueA400.withValues(alpha: 0.1) : Colors.white, + color: (widget.replyId == comment.id || widget.highlightedCommentId == comment.id) ? appTheme.blueA400.withValues(alpha: 0.1) : Colors.white, borderRadius: BorderRadius.circular(16), ), child: Padding( diff --git a/lib/features/notifications/models/notification_model.dart b/lib/features/notifications/models/notification_model.dart index cc77616e..3375a312 100644 --- a/lib/features/notifications/models/notification_model.dart +++ b/lib/features/notifications/models/notification_model.dart @@ -215,6 +215,8 @@ class ChosenPlanStartedToConsultantNotification implements BaseNotificationModel final bool isRead; final String planName; final String customerName; + final int planId; + final int customerId; @override final DateTime createdAt; ChosenPlanStartedToConsultantNotification({ @@ -222,6 +224,8 @@ class ChosenPlanStartedToConsultantNotification implements BaseNotificationModel required this.isRead, required this.planName, required this.customerName, + required this.planId, + required this.customerId, required this.createdAt, }); @@ -231,6 +235,8 @@ class ChosenPlanStartedToConsultantNotification implements BaseNotificationModel isRead: isRead ?? false, planName: json['plan_name'], customerName: json['client_name'], + planId: json['plan_id'] ?? 0, + customerId: json['client_id'] ?? 0, createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()), ); } @@ -420,6 +426,7 @@ class CommentReplied implements BaseNotificationModel { final String author; final int authorId; final int parentCommentId; + final int? commentId; @override final DateTime createdAt; @@ -431,6 +438,7 @@ class CommentReplied implements BaseNotificationModel { required this.author, required this.authorId, required this.parentCommentId, + this.commentId, required this.isRead, required this.createdAt, }); @@ -444,6 +452,7 @@ class CommentReplied implements BaseNotificationModel { author: json['author'], authorId: json['author_id'], parentCommentId: json['parrent_comment_id'], + commentId: json['comment_id'], isRead: isRead ?? false, createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()), ); @@ -504,6 +513,7 @@ class EntityCommented implements BaseNotificationModel { final String authorName; final int authorId; final String authorSlug; + final int? commentId; @override final DateTime createdAt; @@ -517,6 +527,7 @@ class EntityCommented implements BaseNotificationModel { required this.authorName, required this.authorId, required this.authorSlug, + this.commentId, required this.isRead, required this.createdAt, }); @@ -532,6 +543,7 @@ class EntityCommented implements BaseNotificationModel { authorName: json['author_name'], authorId: json['author_id'], authorSlug: json['author_slug'], + commentId: json['comment_id'], isRead: isRead ?? false, createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()), ); @@ -831,7 +843,15 @@ class UnknownNotification implements BaseNotificationModel { @override final DateTime createdAt; - UnknownNotification({required this.type, this.isRead = true, DateTime? createdAt}) : createdAt = createdAt ?? DateTime.fromMillisecondsSinceEpoch(0); + UnknownNotification({required this.type, this.isRead = true, required this.createdAt}); + + factory UnknownNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) { + return UnknownNotification( + type: type, + isRead: isRead ?? false, + createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()), + ); + } } class DietCancelledNotification implements BaseNotificationModel { @@ -1015,16 +1035,28 @@ class StayOnTrackToAuthorNotification implements BaseNotificationModel { final bool isRead; final String planName; final String clientName; + final int planId; + final int clientId; @override final DateTime createdAt; - StayOnTrackToAuthorNotification({required this.type, required this.planName, required this.clientName, required this.isRead, required this.createdAt}); + StayOnTrackToAuthorNotification({ + required this.type, + required this.planName, + required this.clientName, + required this.planId, + required this.clientId, + required this.isRead, + required this.createdAt, + }); factory StayOnTrackToAuthorNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) { return StayOnTrackToAuthorNotification( type: type, planName: json['plan_name'] ?? '', clientName: json['client_name'] ?? '', + planId: json['plan_id'] ?? 0, + clientId: json['client_id'] ?? 0, isRead: isRead ?? false, createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()), ); @@ -1326,16 +1358,28 @@ class StayOnTrackToCustomerNotification implements BaseNotificationModel { final bool isRead; final String planName; final String consultantName; + final int planId; + final int consultantId; @override final DateTime createdAt; - StayOnTrackToCustomerNotification({required this.type, required this.planName, required this.consultantName, required this.isRead, required this.createdAt}); + StayOnTrackToCustomerNotification({ + required this.type, + required this.planName, + required this.consultantName, + required this.planId, + required this.consultantId, + required this.isRead, + required this.createdAt, + }); factory StayOnTrackToCustomerNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) { return StayOnTrackToCustomerNotification( type: type, planName: json['plan_name'] ?? '', consultantName: json['consultant_name'] ?? '', + planId: json['plan_id'] ?? 0, + consultantId: json['consultant_id'] ?? 0, isRead: isRead ?? false, createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()), ); @@ -1652,14 +1696,16 @@ class ProductStatusUpdatedForAuthorNotification implements BaseNotificationModel final String productName; final String oldStatus; final String newStatus; + final String status; // Added to capture single status if old/new aren't provided @override final DateTime createdAt; ProductStatusUpdatedForAuthorNotification({ required this.type, required this.productName, - required this.oldStatus, - required this.newStatus, + this.oldStatus = '', + this.newStatus = '', + this.status = '', required this.isRead, required this.createdAt, }); @@ -1670,6 +1716,7 @@ class ProductStatusUpdatedForAuthorNotification implements BaseNotificationModel productName: json['product_name'] ?? '', oldStatus: json['old_status'] ?? '', newStatus: json['new_status'] ?? '', + status: json['status'] ?? '', isRead: isRead ?? false, createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()), ); @@ -1724,6 +1771,309 @@ class PersonalDietIsDefinedToCustomerNotification implements BaseNotificationMod } } +// New Model Classes for Comprehensive Support + +class ConsultantEducationUpdatedNotification implements BaseNotificationModel { + @override + final NotificationType type; + @override + final bool isRead; + @override + final DateTime createdAt; + final String consultantName; + + ConsultantEducationUpdatedNotification({ + required this.type, + required this.isRead, + required this.createdAt, + required this.consultantName, + }); + + factory ConsultantEducationUpdatedNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) { + return ConsultantEducationUpdatedNotification( + type: type, + isRead: isRead ?? false, + createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()), + consultantName: json['consultant_name'] ?? '', + ); + } +} + +class ComplaintRegisteredNotification implements BaseNotificationModel { + @override + final NotificationType type; + @override + final bool isRead; + @override + final DateTime createdAt; + final String complaintType; + final String entityName; + + ComplaintRegisteredNotification({ + required this.type, + required this.isRead, + required this.createdAt, + required this.complaintType, + required this.entityName, + }); + + factory ComplaintRegisteredNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) { + return ComplaintRegisteredNotification( + type: type, + isRead: isRead ?? false, + createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()), + complaintType: json['complaint_type'] ?? '', + entityName: json['entity_name'] ?? '', + ); + } +} + +class ContentUnpublishedNotification implements BaseNotificationModel { + @override + final NotificationType type; + @override + final bool isRead; + @override + final DateTime createdAt; + final String reason; + final String entityName; + + ContentUnpublishedNotification({ + required this.type, + required this.isRead, + required this.createdAt, + required this.reason, + required this.entityName, + }); + + factory ContentUnpublishedNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) { + return ContentUnpublishedNotification( + type: type, + isRead: isRead ?? false, + createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()), + reason: json['reason'] ?? '', + entityName: json['entity_name'] ?? '', + ); + } +} + +class FinanceQuestionCreatedNotification implements BaseNotificationModel { + @override + final NotificationType type; + @override + final bool isRead; + @override + final DateTime createdAt; + final String questionText; + + FinanceQuestionCreatedNotification({ + required this.type, + required this.isRead, + required this.createdAt, + required this.questionText, + }); + + factory FinanceQuestionCreatedNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) { + return FinanceQuestionCreatedNotification( + type: type, + isRead: isRead ?? false, + createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()), + questionText: json['question_text'] ?? '', + ); + } +} + +class NewChatMessageNotification implements BaseNotificationModel { + @override + final NotificationType type; + @override + final bool isRead; + @override + final DateTime createdAt; + final String senderName; + final String messagePreview; + final int chatId; + + NewChatMessageNotification({ + required this.type, + required this.isRead, + required this.createdAt, + required this.senderName, + required this.messagePreview, + required this.chatId, + }); + + factory NewChatMessageNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) { + return NewChatMessageNotification( + type: type, + isRead: isRead ?? false, + createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()), + senderName: json['sender_name'] ?? '', + messagePreview: json['message_preview'] ?? '', + chatId: json['chat_id'] ?? 0, + ); + } +} + +class CompleteRegistrationNotification implements BaseNotificationModel { + @override + final NotificationType type; + @override + final bool isRead; + @override + final DateTime createdAt; + + CompleteRegistrationNotification({ + required this.type, + required this.isRead, + required this.createdAt, + }); + + factory CompleteRegistrationNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) { + return CompleteRegistrationNotification( + type: type, + isRead: isRead ?? false, + createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()), + ); + } +} + +class ChosenDietExpiredNotification implements BaseNotificationModel { + @override + final NotificationType type; + @override + final bool isRead; + @override + final DateTime createdAt; + final String dietName; + + ChosenDietExpiredNotification({ + required this.type, + required this.isRead, + required this.createdAt, + required this.dietName, + }); + + factory ChosenDietExpiredNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) { + return ChosenDietExpiredNotification( + type: type, + isRead: isRead ?? false, + createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()), + dietName: json['diet_name'] ?? '', + ); + } +} + +class MotivateUserNotification implements BaseNotificationModel { + @override + final NotificationType type; + @override + final bool isRead; + @override + final DateTime createdAt; + final String message; + + MotivateUserNotification({ + required this.type, + required this.isRead, + required this.createdAt, + required this.message, + }); + + factory MotivateUserNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) { + return MotivateUserNotification( + type: type, + isRead: isRead ?? false, + createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()), + message: json['message'] ?? '', + ); + } +} + +class CommentComplaintNotification implements BaseNotificationModel { + @override + final NotificationType type; + @override + final bool isRead; + @override + final DateTime createdAt; + final String commentPreview; + final String reason; + + CommentComplaintNotification({ + required this.type, + required this.isRead, + required this.createdAt, + required this.commentPreview, + required this.reason, + }); + + factory CommentComplaintNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) { + return CommentComplaintNotification( + type: type, + isRead: isRead ?? false, + createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()), + commentPreview: json['comment_preview'] ?? '', + reason: json['reason'] ?? '', + ); + } +} + +class CommentDeletedNotification implements BaseNotificationModel { + @override + final NotificationType type; + @override + final bool isRead; + @override + final DateTime createdAt; + final String commentPreview; + + CommentDeletedNotification({ + required this.type, + required this.isRead, + required this.createdAt, + required this.commentPreview, + }); + + factory CommentDeletedNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) { + return CommentDeletedNotification( + type: type, + isRead: isRead ?? false, + createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()), + commentPreview: json['comment_preview'] ?? '', + ); + } +} + +class UserSubscribedNotification implements BaseNotificationModel { + @override + final NotificationType type; + @override + final bool isRead; + @override + final DateTime createdAt; + final String subscriberName; + final int subscriberId; + + UserSubscribedNotification({ + required this.type, + required this.isRead, + required this.createdAt, + required this.subscriberName, + required this.subscriberId, + }); + + factory UserSubscribedNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) { + return UserSubscribedNotification( + type: type, + isRead: isRead ?? false, + createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()), + subscriberName: json['subscriber_name'] ?? '', + subscriberId: json['subscriber_id'] ?? 0, + ); + } +} + enum NotificationType { // Diet related dietCancelled, @@ -1731,6 +2081,7 @@ enum NotificationType { dietExpired, dietWillStartedIn, dietCanNotBeStarted, + chosenDietExpired, // New // Plan related personalDietCreationNeeded, @@ -1760,12 +2111,17 @@ enum NotificationType { // Comments entityCommented, commentReplied, + commentComplaint, // New + commentDeleted, // New // Profile phoneChanged, + consultantEducationUpdated, // New + completeRegistration, // New // Subscription subscription, + userSubscribed, // New // Badges badgeAwarded, @@ -1773,16 +2129,21 @@ enum NotificationType { // Consultant consultantRequestStarted, + motivateUser, // New // Complaints clientCloseComplaintSuccess, complaintToModerationForUser, + complaintRegistered, // New + contentUnpublished, // New + contentUnpublishedComplaint, // New // Finance clientRefundSuccess, consultantRefundSuccess, clientRejectSuccess, consultantRejectSuccess, + financeQuestionCreated, // New // Score scoreAdded, @@ -1794,6 +2155,9 @@ enum NotificationType { // Diary diaryReminderForUsers, + // Chat + newChatMessage, // New + // Legacy types (keeping for backward compatibility) planSubscriptionStartedToAuthor, planSubscriptionCanceledToAuthor, @@ -1865,7 +2229,23 @@ enum NotificationType { 'stay-on-track-to-customer' => NotificationType.stayOnTrackToCustomer, 'diary-reminder-for-users' => NotificationType.diaryReminderForUsers, 'personal-diet-is-defined-to-customer' => NotificationType.personalDietIsDefinedToCustomer, - + + // New additions + 'consultant-education-updated' => NotificationType.consultantEducationUpdated, + 'complaint-registered' => NotificationType.complaintRegistered, + 'content-unpublished' => NotificationType.contentUnpublished, + 'content-unpublished-complaint' => NotificationType.contentUnpublishedComplaint, + 'finance-question-created' => NotificationType.financeQuestionCreated, + 'new-chat-message' => NotificationType.newChatMessage, + 'complete-registration' => NotificationType.completeRegistration, + 'chosen-diet-expired' => NotificationType.chosenDietExpired, + 'motivate-user' => NotificationType.motivateUser, + 'comment-complaint' => NotificationType.commentComplaint, + 'comment-deleted' => NotificationType.commentDeleted, + 'user-subscribed' => NotificationType.userSubscribed, + // 'diary-reminder' => NotificationType.diaryReminder, // Already legacy? Check if it maps to diaryReminderNotification + 'meal-reminder' => NotificationType.mealReminder, // Maps to legacy if string matches? No, legacy was App\\... + // Legacy mapping 'App\\Notifications\\Plan\\PlanSubscriptionStartedToAuthor' => NotificationType.planSubscriptionStartedToAuthor, 'App\\Notifications\\Plan\\PlanSubscriptionCanceledToAuthor' => NotificationType.planSubscriptionCanceledToAuthor, diff --git a/lib/features/notifications/presentation/notifications_cubit/notifications_cubit.dart b/lib/features/notifications/presentation/notifications_cubit/notifications_cubit.dart index 110607c0..54c84507 100644 --- a/lib/features/notifications/presentation/notifications_cubit/notifications_cubit.dart +++ b/lib/features/notifications/presentation/notifications_cubit/notifications_cubit.dart @@ -267,10 +267,82 @@ class NotificationsCubit extends Cubit { isRead: isRead, createdAt: createdAt, ), - NotificationType.unknown => UnknownNotification(type: type), + NotificationType.consultantEducationUpdated => ConsultantEducationUpdatedNotification.fromJson( + notificationJson, + type, + isRead: isRead, + createdAt: createdAt, + ), + NotificationType.complaintRegistered => ComplaintRegisteredNotification.fromJson( + notificationJson, + type, + isRead: isRead, + createdAt: createdAt, + ), + NotificationType.contentUnpublished => ContentUnpublishedNotification.fromJson( + notificationJson, + type, + isRead: isRead, + createdAt: createdAt, + ), + NotificationType.contentUnpublishedComplaint => ContentUnpublishedNotification.fromJson( + notificationJson, + type, + isRead: isRead, + createdAt: createdAt, + ), + NotificationType.financeQuestionCreated => FinanceQuestionCreatedNotification.fromJson( + notificationJson, + type, + isRead: isRead, + createdAt: createdAt, + ), + NotificationType.newChatMessage => NewChatMessageNotification.fromJson( + notificationJson, + type, + isRead: isRead, + createdAt: createdAt, + ), + NotificationType.completeRegistration => CompleteRegistrationNotification.fromJson( + notificationJson, + type, + isRead: isRead, + createdAt: createdAt, + ), + NotificationType.chosenDietExpired => ChosenDietExpiredNotification.fromJson( + notificationJson, + type, + isRead: isRead, + createdAt: createdAt, + ), + NotificationType.motivateUser => MotivateUserNotification.fromJson( + notificationJson, + type, + isRead: isRead, + createdAt: createdAt, + ), + NotificationType.commentComplaint => CommentComplaintNotification.fromJson( + notificationJson, + type, + isRead: isRead, + createdAt: createdAt, + ), + NotificationType.commentDeleted => CommentDeletedNotification.fromJson( + notificationJson, + type, + isRead: isRead, + createdAt: createdAt, + ), + NotificationType.userSubscribed => UserSubscribedNotification.fromJson( + notificationJson, + type, + isRead: isRead, + createdAt: createdAt, + ), + NotificationType.unknown => UnknownNotification(type: type, createdAt: parseDate(createdAt ?? jsonEncode(notificationJson))), }; } catch (e) { - return UnknownNotification(type: NotificationType.unknown); + return UnknownNotification(type: NotificationType.unknown, createdAt: DateTime.now()); } } diff --git a/lib/features/notifications/presentation/widgets/notification_widget.dart b/lib/features/notifications/presentation/widgets/notification_widget.dart index 0169f96d..1c2d1c58 100644 --- a/lib/features/notifications/presentation/widgets/notification_widget.dart +++ b/lib/features/notifications/presentation/widgets/notification_widget.dart @@ -9,9 +9,194 @@ import 'package:eatvio_app/generated/l10n.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; +import 'package:eatvio_app/features/notifications/presentation/notifications_cubit/notifications_cubit.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + class NotificationWidget extends StatelessWidget { const NotificationWidget({super.key, required this.notification}); final BaseNotificationModel notification; + + void _onTap(BuildContext context) { + // Mark as read first + context.read().markNotificationsAsRead(); + + switch (notification) { + case CommentReplied n: + _navigateToEntity(context, n.commentedEntityType, n.commentedEntityId, n.commentId); + case EntityCommented n: + _navigateToEntity(context, n.entityType, n.entityId, n.commentId); + case PersonalDietIsDefinedToCustomerNotification n: + context.push(RouteConstants.aboutDietScreen, extra: {'id': n.dietId}); + case PlanSubscriptionStartedToAuthorNotification n: + if (n.chatId.isNotEmpty && n.chatId != '0') { + context.push(RouteConstants.chatScreen, extra: int.tryParse(n.chatId) ?? 0); + } else { + // Fallback if no chat ID + context.push(RouteConstants.profileScreen); + } + case ConsultantAssignedNotification n: + context.push(RouteConstants.aboutPlanScreen, extra: {'id': n.planId}); + case StayOnTrackToAuthorNotification n: + context.push(RouteConstants.aboutPlanScreen, extra: {'id': n.planId}); + case StayOnTrackToCustomerNotification n: + context.push(RouteConstants.aboutPlanScreen, extra: {'id': n.planId}); + case ChosenPlanStartedToClientNotification n: + context.push(RouteConstants.aboutPlanScreen, extra: {'id': n.planId}); + case ChosenPlanStartedToConsultantNotification n: + context.push(RouteConstants.aboutPlanScreen, extra: {'id': n.planId}); + case ProductStatusUpdatedForAuthorNotification(): + context.push(RouteConstants.profileScreen); + // New Cases with Safety Checks + case NewChatMessageNotification n: + if (n.chatId != 0) context.push(RouteConstants.chatScreen, extra: n.chatId); + case FinanceQuestionCreatedNotification(): + context.push(RouteConstants.userFinanceScreen); + case ChosenDietExpiredNotification(): + context.push(RouteConstants.myDietsScreen); + case ContentUnpublishedNotification(): + context.push(RouteConstants.profileScreen); + case CompleteRegistrationNotification(): + context.push(RouteConstants.profileScreen); // Or edit profile + case ConsultantEducationUpdatedNotification(): + context.push(RouteConstants.profileScreen); + case ComplaintRegisteredNotification(): + context.push(RouteConstants.profileScreen); + case ConsultantRequestStartedNotification(): + // Navigate to where requests are shown + context.push(RouteConstants.profileScreen); + case ClientCloseComplaintSuccessNotification(): + context.push(RouteConstants.profileScreen); + case ClientRefundSuccessNotification(): + context.push(RouteConstants.userFinanceScreen); + case ConsultantRefundSuccessNotification(): + context.push(RouteConstants.userFinanceScreen); + case ClientRejectSuccessNotification(): + context.push(RouteConstants.aboutPlanScreen); // Best guess + case ConsultantRejectSuccessNotification(): + context.push(RouteConstants.aboutPlanScreen); + case ScoreSubstractedNotification(): + context.push(RouteConstants.profileScreen); + case ScoreAdded(): + context.push(RouteConstants.profileScreen); + case DiaryReminderForUsersNotification(): + context.push(RouteConstants.myDietsScreen); // Likely where diary is + case MotivateUserNotification(): + context.push(RouteConstants.myDietsScreen); + case CommentComplaintNotification(): + context.push(RouteConstants.profileScreen); + case CommentDeletedNotification(): + context.push(RouteConstants.profileScreen); + case UserSubscribedNotification(): + context.push(RouteConstants.profileScreen); + case BadgeAwardedNotification(): + context.push(RouteConstants.profileScreen); + case BadgeRevokedNotification(): + context.push(RouteConstants.profileScreen); + case SubscriptionNotification(): + context.push(RouteConstants.userFinanceScreen); + case PhoneChangedNotification(): + context.push(RouteConstants.profileScreen); + case UserDailyReportCompletedNotification(): + // Attempt to go to plan if we had ID, but we don't in this model. Go to plans list. + context.push(RouteConstants.myPlansScreen); + case ClientCancelPlanSubscriptionToSubscriberNotification(): + context.push(RouteConstants.myPlansScreen); + case ClientCancelPlanSubscriptionToAuthorNotification(): + context.push(RouteConstants.myPlansScreen); + case ClientStartPlanSubscriptionToAuthorNotification(): + context.push(RouteConstants.myPlansScreen); + case ClientPaidPlanSubscriptionToSubscriberNotification(): + context.push(RouteConstants.userFinanceScreen); + case ConsultantConfirmPlanSubscriptionToAuthorNotification(): + context.push(RouteConstants.myPlansScreen); + case ConsultantLeftDiaryCommentNotification(): + // Needs planID to go to specific plan, model has planName. + // Optimally we'd go to the diary. For now plans screen. + context.push(RouteConstants.myPlansScreen); + case ClientPaidPlanSubscriptionToAuthorNotification(): + context.push(RouteConstants.userFinanceScreen); + case ConsultantConfirmPlanSubscriptionToSubscriberNotification(): + context.push(RouteConstants.myPlansScreen); + case ClientStartPlanSubscriptionToSubscriberNotification(): + context.push(RouteConstants.myPlansScreen); + case ChosenPlanClientLastDayStartedNotification(): + context.push(RouteConstants.myPlansScreen); + case ChosenPlanClientLastDayFinishedNotification(): + context.push(RouteConstants.myPlansScreen); + case PersonalDietCreationNeededNotification(): + context.push(RouteConstants.myPlansScreen); + case DietCanNotBeStartedNotification(): + context.push(RouteConstants.myDietsScreen); + case DietWillStartedInNotification(): + context.push(RouteConstants.myDietsScreen); + case DietExpiredNotification(): + context.push(RouteConstants.myDietsScreen); + case DietStartedNotification(): + context.push(RouteConstants.myDietsScreen); + case DietCancelledNotification(): + context.push(RouteConstants.myDietsScreen); + case MealReminderNotification(): + context.push(RouteConstants.myDietsScreen); + case WorkoutReminderNotification(): + context.push(RouteConstants.myDietsScreen); + case GoalAchievedNotification(): + context.push(RouteConstants.profileScreen); + case PaymentSuccessfulNotification(): + context.push(RouteConstants.userFinanceScreen); + case PaymentFailedNotification(): + context.push(RouteConstants.userFinanceScreen); + case ConsultantUnassignedNotification n: + context.push(RouteConstants.aboutPlanScreen, extra: {'id': n.planId}); + case PlanSubscriptionRenewedNotification(): + context.push(RouteConstants.userFinanceScreen); + case PlanSubscriptionExpiredNotification(): + context.push(RouteConstants.userFinanceScreen); + case ComplaintToModerationForUser(): + context.push(RouteConstants.profileScreen); + case ChosenPlanConsultantLastDayStarted n: + context.push(RouteConstants.aboutPlanScreen, extra: {'id': n.planId}); + case ChosenPlanLastDayFinishedForUser(): + context.push(RouteConstants.myPlansScreen); + case ChosenPlanFinishedToConsultantNotification n: + context.push(RouteConstants.aboutPlanScreen, extra: {'id': n.planId}); + case PlanSubscriptionPaidToAuthorNotification(): + context.push(RouteConstants.userFinanceScreen); + case ChosenPlanTomorrowReminderToConsultantNotification(): + context.push(RouteConstants.myPlansScreen); + case ChosenPlanTomorrowReminderToClientNotification(): + context.push(RouteConstants.myPlansScreen); + case PlanSubscriptionCanceledToCustomerNotification(): + context.push(RouteConstants.myPlansScreen); + case ChosenPlanFinishedToClientNotification(): + context.push(RouteConstants.myPlansScreen); + case PersonalizedDietIsDefinedNotification(): + context.push(RouteConstants.myDietsScreen); + case ConsultantLeftCommentNotification(): + context.push(RouteConstants.myPlansScreen); + case DiaryReminderNotification(): + context.push(RouteConstants.myDietsScreen); + case PlanSubscriptionCanceledToAuthorNotification(): + context.push(RouteConstants.myPlansScreen); + default: + // Do nothing if no logical destination + break; + } + } + + void _navigateToEntity(BuildContext context, String type, int id, int? commentId) { + if (type.contains('BlogPost')) { + context.push(RouteConstants.blogPostScreen, extra: {'id': id, 'highlightedCommentId': commentId}); + } else if (type.contains('Recipe')) { + context.push(RouteConstants.aboutRecipesScreen, extra: {'id': id, 'highlightedCommentId': commentId}); + } else if (type.contains('Plan')) { + context.push(RouteConstants.aboutPlanScreen, extra: {'id': id, 'highlightedCommentId': commentId}); + } else if (type.contains('Diet')) { + context.push(RouteConstants.aboutDietScreen, extra: {'id': id, 'highlightedCommentId': commentId}); + } + } + +// }; +// } @override Widget build(BuildContext context) { return switch (notification) { @@ -117,6 +302,20 @@ class NotificationWidget extends StatelessWidget { PersonalDietIsDefinedToCustomerNotification() => _PersonalDietIsDefinedToCustomerWidget( notification: notification as PersonalDietIsDefinedToCustomerNotification, ), + // New Widgets + ConsultantEducationUpdatedNotification() => _ConsultantEducationUpdatedWidget( + notification: notification as ConsultantEducationUpdatedNotification, + ), + ComplaintRegisteredNotification() => _ComplaintRegisteredWidget(notification: notification as ComplaintRegisteredNotification), + ContentUnpublishedNotification() => _ContentUnpublishedWidget(notification: notification as ContentUnpublishedNotification), + FinanceQuestionCreatedNotification() => _FinanceQuestionCreatedWidget(notification: notification as FinanceQuestionCreatedNotification), + NewChatMessageNotification() => _NewChatMessageWidget(notification: notification as NewChatMessageNotification), + CompleteRegistrationNotification() => _CompleteRegistrationWidget(notification: notification as CompleteRegistrationNotification), + ChosenDietExpiredNotification() => _ChosenDietExpiredWidget(notification: notification as ChosenDietExpiredNotification), + MotivateUserNotification() => _MotivateUserWidget(notification: notification as MotivateUserNotification), + CommentComplaintNotification() => _CommentComplaintWidget(notification: notification as CommentComplaintNotification), + CommentDeletedNotification() => _CommentDeletedWidget(notification: notification as CommentDeletedNotification), + UserSubscribedNotification() => _UserSubscribedWidget(notification: notification as UserSubscribedNotification), UnknownNotification() => _UnknownNotificationWidget(notification: notification as UnknownNotification), }; } @@ -131,8 +330,9 @@ class _PlanSubscriptionStartedToAuthorWidget extends StatelessWidget { return _BaseNotificationWidget( isRead: notification.isRead, - notificationsText: s.userSubscribedToPlan(notification.customerName, notification.planName), + notificationsText: '${s.userSubscribedToPlan(notification.customerName, notification.planName)}! 🎉', createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), ); } } @@ -146,8 +346,9 @@ class _PlanSubscriptionCanceledToAuthorWidget extends StatelessWidget { return _BaseNotificationWidget( isRead: notification.isRead, - notificationsText: s.userCanceldThePlanSubscription(notification.customerName, notification.planName), + notificationsText: 'Отмена подписки. Клиент ${notification.customerName} отказался от плана "${notification.planName}"', createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), ); } } @@ -159,7 +360,12 @@ class _ChosenPlanStartedToClientWidget extends StatelessWidget { Widget build(BuildContext context) { final s = S.of(context); - return _BaseNotificationWidget(isRead: notification.isRead, notificationsText: s.planStarted(notification.planName), createdAt: notification.createdAt); + return _BaseNotificationWidget( + isRead: notification.isRead, + notificationsText: 'Ваш план "${notification.planName}" начался! Удачи! 🚀', + createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), + ); } } @@ -172,8 +378,9 @@ class _ChosenPlanTomorrowReminderToClientWidget extends StatelessWidget { return _BaseNotificationWidget( isRead: notification.isRead, - notificationsText: s.tomorrowYourPlanWillStart(notification.planName), + notificationsText: 'Напоминание: Завтра старт плана "${notification.planName}"', createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), ); } } @@ -184,7 +391,12 @@ class _DiaryReminderWidget extends StatelessWidget { @override Widget build(BuildContext context) { final s = S.of(context); - return _BaseNotificationWidget(isRead: notification.isRead, notificationsText: s.fillYasterday, createdAt: notification.createdAt); + return _BaseNotificationWidget( + isRead: notification.isRead, + notificationsText: 'Не забудьте заполнить дневник за вчера! 📝', + createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), + ); } } @@ -197,8 +409,9 @@ class _ConsultantLeftCommentWidget extends StatelessWidget { return _BaseNotificationWidget( isRead: notification.isRead, - notificationsText: s.consultantLeftComment(notification.date.format('dd.MM.yyyy')), + notificationsText: 'Новый комментарий от консультанта (за ${notification.date.format('dd.MM.yyyy')})', createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), ); } } @@ -208,7 +421,12 @@ class _PersonalizedDietIsDefinedWidget extends StatelessWidget { final PersonalizedDietIsDefinedNotification notification; @override Widget build(BuildContext context) { - return _BaseNotificationWidget(isRead: notification.isRead, notificationsText: 'Персональная диета определена', createdAt: notification.createdAt); + return _BaseNotificationWidget( + isRead: notification.isRead, + notificationsText: 'Персональная диета определена', + createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), + ); } } @@ -221,8 +439,9 @@ class _ChosenPlanFinishedToClientWidget extends StatelessWidget { return _BaseNotificationWidget( isRead: notification.isRead, - notificationsText: '${s.congratsYouHaveFinishedPlan} «${notification.planName}»', + notificationsText: 'Поздравляем! План «${notification.planName}» успешно завершен! 🏆', createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), ); } } @@ -236,8 +455,9 @@ class _PlanSubscriptionCanceledToCustomerWidget extends StatelessWidget { return _BaseNotificationWidget( isRead: notification.isRead, - notificationsText: s.youCanceledSubscriptionToPlan(notification.planName), + notificationsText: 'Подписка на план "${notification.planName}" отменена.', createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), ); } } @@ -251,8 +471,9 @@ class _ChosenPlanStartedToConsultantWidget extends StatelessWidget { return _BaseNotificationWidget( isRead: notification.isRead, - notificationsText: s.planWithUserStarted(notification.planName, notification.customerName), + notificationsText: 'Начало работы: План "${notification.planName}" с клиентом ${notification.customerName}', createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), ); } } @@ -266,8 +487,9 @@ class _ChosenPlanTomorrowReminderToConsultantWidget extends StatelessWidget { return _BaseNotificationWidget( isRead: notification.isRead, - notificationsText: s.tommorowPlanWithUserWillStart(notification.planName, notification.customerName), + notificationsText: 'Напоминание: Завтра старт работы с ${notification.customerName} по плану "${notification.planName}"', createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), ); } } @@ -281,8 +503,9 @@ class _PlanSubscriptionPaidToAuthorWidget extends StatelessWidget { return _BaseNotificationWidget( isRead: notification.isRead, - notificationsText: s.userPaidThePlan(notification.customerName, notification.planName), + notificationsText: 'Оплата получена! Клиент ${notification.customerName} оплатил план "${notification.planName}" 💰', createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), ); } } @@ -293,16 +516,19 @@ class _ChosenPlanFinishedToConsultantWidget extends StatelessWidget { @override Widget build(BuildContext context) { final s = S.of(context); - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ + return GestureDetector( + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), + behavior: HitTestBehavior.opaque, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ Row( children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('${s.congratsYouHaveFinishedPlan} «${notification.planName}»', style: TextStyles.textMSemibold.copyWith(color: appTheme.black)), + Text('План «${notification.planName}» завершен! Поздравляем! 🎉', style: TextStyles.textMSemibold.copyWith(color: appTheme.black)), const SizedBox(height: 12), Text(s.userConfirmedPlanCompletionWithoutSum, style: TextStyles.textSMedium.copyWith(color: appTheme.black)), const SizedBox(height: 12), @@ -331,6 +557,7 @@ class _ChosenPlanFinishedToConsultantWidget extends StatelessWidget { style: TextStyles.textSMedium.copyWith(color: appTheme.gray600), ), ], + ), ); } @@ -377,6 +604,7 @@ class _BaseNotificationWidget extends StatelessWidget { Widget build(BuildContext context) { return GestureDetector( onTap: onTap, + behavior: HitTestBehavior.opaque, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -385,8 +613,14 @@ class _BaseNotificationWidget extends StatelessWidget { Expanded( child: Text(notificationsText, style: TextStyles.textMMedium.copyWith(color: appTheme.black)), ), + if (onTap != null) + Padding( + padding: const EdgeInsets.only(left: 8), + child: Icon(Icons.chevron_right, color: appTheme.gray500, size: 20), + ), if (!isRead) Container( + margin: const EdgeInsets.only(left: 8), decoration: BoxDecoration(color: appTheme.blueA400, shape: BoxShape.circle), width: 8, height: 8, @@ -440,7 +674,7 @@ class _ChosenPlanLastDayFinishedForUserWidget extends StatelessWidget { final ChosenPlanLastDayFinishedForUser notification; @override Widget build(BuildContext context) { - return _BaseNotificationWidget(isRead: notification.isRead, notificationsText: 'Последний день плана завершен', createdAt: notification.createdAt); + return _BaseNotificationWidget(isRead: notification.isRead, notificationsText: 'Финишная прямая! Последний день плана завершен.', createdAt: notification.createdAt); } } @@ -451,8 +685,9 @@ class _ChosenPlanConsultantLastDayStartedWidget extends StatelessWidget { Widget build(BuildContext context) { return _BaseNotificationWidget( isRead: notification.isRead, - notificationsText: 'Последний день плана начался: ${notification.planName}', + notificationsText: 'Внимание: Начался последний день плана "${notification.planName}"', createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), ); } } @@ -462,7 +697,12 @@ class _ComplaintToModerationForUserWidget extends StatelessWidget { final ComplaintToModerationForUser notification; @override Widget build(BuildContext context) { - return _BaseNotificationWidget(isRead: notification.isRead, notificationsText: 'Жалоба отправлена на модерацию', createdAt: notification.createdAt); + return _BaseNotificationWidget( + isRead: notification.isRead, + notificationsText: 'Ваша жалоба принята и находится на модерации.', + createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), + ); } } @@ -473,8 +713,9 @@ class _CommentRepliedWidget extends StatelessWidget { Widget build(BuildContext context) { return _BaseNotificationWidget( isRead: notification.isRead, - notificationsText: 'Ответ на комментарий: ${notification.entityName}', + notificationsText: 'Вам ответили в комментариях к: ${notification.entityName}', createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), ); } } @@ -486,8 +727,9 @@ class _ScoreAddedWidget extends StatelessWidget { Widget build(BuildContext context) { return _BaseNotificationWidget( isRead: notification.isRead, - notificationsText: 'Добавлены баллы: ${notification.scoreValue}', + notificationsText: 'Поздравляем! Вам начислены баллы: ${notification.scoreValue} ⭐', createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), ); } } @@ -499,8 +741,9 @@ class _EntityCommentedWidget extends StatelessWidget { Widget build(BuildContext context) { return _BaseNotificationWidget( isRead: notification.isRead, - notificationsText: 'Комментарий к ${notification.entityTranslatedType}: ${notification.entityName}', + notificationsText: 'Новый комментарий к вашей публикации "${notification.entityTranslatedType}": ${notification.entityName}', createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), ); } } @@ -512,8 +755,9 @@ class _UnknownNotificationWidget extends StatelessWidget { Widget build(BuildContext context) { return _BaseNotificationWidget( isRead: notification.isRead, - notificationsText: 'Неизвестное уведомление: ${notification.type.toString()}', + notificationsText: 'У вас новое уведомление! Пожалуйста, обновите приложение, чтобы увидеть детали.', createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), ); } } @@ -526,8 +770,9 @@ class _PlanSubscriptionExpiredWidget extends StatelessWidget { Widget build(BuildContext context) { return _BaseNotificationWidget( isRead: notification.isRead, - notificationsText: 'Подписка на план "${notification.planName}" истекла', + notificationsText: 'Срок действия подписки на план "${notification.planName}" истек. Продлите, чтобы продолжить! ⏳', createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), ); } } @@ -539,8 +784,9 @@ class _PlanSubscriptionRenewedWidget extends StatelessWidget { Widget build(BuildContext context) { return _BaseNotificationWidget( isRead: notification.isRead, - notificationsText: 'Подписка на план "${notification.planName}" продлена до ${notification.renewalDate}', + notificationsText: 'Успешное продление! Подписка на план "${notification.planName}" активна до ${notification.renewalDate} ✅', createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), ); } } @@ -552,8 +798,9 @@ class _ConsultantAssignedWidget extends StatelessWidget { Widget build(BuildContext context) { return _BaseNotificationWidget( isRead: notification.isRead, - notificationsText: 'К плану "${notification.planName}" назначен консультант ${notification.consultantName}', + notificationsText: 'Вам назначен консультант ${notification.consultantName} по плану "${notification.planName}". Приветствуйте! 👋', createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), ); } } @@ -565,8 +812,9 @@ class _ConsultantUnassignedWidget extends StatelessWidget { Widget build(BuildContext context) { return _BaseNotificationWidget( isRead: notification.isRead, - notificationsText: 'Консультант ${notification.consultantName} отстранен от плана "${notification.planName}"', + notificationsText: 'Изменение в плане "${notification.planName}": консультант ${notification.consultantName} отстранен.', createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), ); } } @@ -578,8 +826,9 @@ class _PaymentFailedWidget extends StatelessWidget { Widget build(BuildContext context) { return _BaseNotificationWidget( isRead: notification.isRead, - notificationsText: 'Ошибка оплаты плана "${notification.planName}": ${notification.errorMessage}', + notificationsText: 'Не удалось провести оплату за план "${notification.planName}". Причина: ${notification.errorMessage} ❌', createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), ); } } @@ -591,8 +840,9 @@ class _PaymentSuccessfulWidget extends StatelessWidget { Widget build(BuildContext context) { return _BaseNotificationWidget( isRead: notification.isRead, - notificationsText: 'Успешная оплата плана "${notification.planName}" на сумму ${notification.amount} ${notification.currency}', + notificationsText: 'Оплата прошла успешно! План "${notification.planName}" оплачен на сумму ${notification.amount} ${notification.currency} 💳', createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), ); } } @@ -604,8 +854,9 @@ class _GoalAchievedWidget extends StatelessWidget { Widget build(BuildContext context) { return _BaseNotificationWidget( isRead: notification.isRead, - notificationsText: 'Достигнута цель "${notification.goalName}": ${notification.achievedValue}/${notification.targetValue}', + notificationsText: 'Цель достигнута! "${notification.goalName}": ${notification.achievedValue}/${notification.targetValue} 🎯', createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), ); } } @@ -617,8 +868,9 @@ class _WorkoutReminderWidget extends StatelessWidget { Widget build(BuildContext context) { return _BaseNotificationWidget( isRead: notification.isRead, - notificationsText: 'Напоминание о тренировке "${notification.workoutName}" (${notification.duration} мин)', + notificationsText: 'Время тренировки! "${notification.workoutName}" ждет вас (${notification.duration} мин) 💪', createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), ); } } @@ -630,8 +882,9 @@ class _MealReminderWidget extends StatelessWidget { Widget build(BuildContext context) { return _BaseNotificationWidget( isRead: notification.isRead, - notificationsText: 'Напоминание о приеме пищи "${notification.mealName}" (${notification.calories} ккал)', + notificationsText: 'Пора подкрепиться! "${notification.mealName}" (${notification.calories} ккал) 🍎', createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), ); } } @@ -644,8 +897,9 @@ class _DietCancelledWidget extends StatelessWidget { Widget build(BuildContext context) { return _BaseNotificationWidget( isRead: notification.isRead, - notificationsText: 'Диета "${notification.dietName}" отменена', + notificationsText: 'Диета "${notification.dietName}" была отменена.', createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), ); } } @@ -657,8 +911,9 @@ class _DietStartedWidget extends StatelessWidget { Widget build(BuildContext context) { return _BaseNotificationWidget( isRead: notification.isRead, - notificationsText: 'Диета "${notification.dietName}" началась', + notificationsText: 'Старт диеты "${notification.dietName}"! Успехов! 🥗', createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), ); } } @@ -670,8 +925,9 @@ class _DietExpiredWidget extends StatelessWidget { Widget build(BuildContext context) { return _BaseNotificationWidget( isRead: notification.isRead, - notificationsText: 'Диета "${notification.dietName}" истекла', + notificationsText: 'Срок действия диеты "${notification.dietName}" подошел к концу.', createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), ); } } @@ -683,8 +939,9 @@ class _DietWillStartedInWidget extends StatelessWidget { Widget build(BuildContext context) { return _BaseNotificationWidget( isRead: notification.isRead, - notificationsText: 'Диета "${notification.dietName}" начнется ${notification.startDate}', + notificationsText: 'Скоро старт! Диета "${notification.dietName}" начнется ${notification.startDate} ⏰', createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), ); } } @@ -696,8 +953,9 @@ class _DietCanNotBeStartedWidget extends StatelessWidget { Widget build(BuildContext context) { return _BaseNotificationWidget( isRead: notification.isRead, - notificationsText: 'Диета "${notification.dietName}" не может быть начата: ${notification.reason}', + notificationsText: 'Внимание: Не удалось начать диету "${notification.dietName}". Причина: ${notification.reason}', createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), ); } } @@ -709,8 +967,9 @@ class _PersonalDietCreationNeededWidget extends StatelessWidget { Widget build(BuildContext context) { return _BaseNotificationWidget( isRead: notification.isRead, - notificationsText: 'Требуется создание персональной диеты для клиента ${notification.clientName} по плану "${notification.planName}"', + notificationsText: 'Задача: Создать персональную диету для клиента ${notification.clientName} (План "${notification.planName}")', createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), ); } } @@ -722,8 +981,9 @@ class _ChosenPlanClientLastDayFinishedWidget extends StatelessWidget { Widget build(BuildContext context) { return _BaseNotificationWidget( isRead: notification.isRead, - notificationsText: 'Последний день плана "${notification.planName}" завершен', + notificationsText: 'Поздравляем с завершением плана "${notification.planName}"! 🎉', createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), ); } } @@ -735,8 +995,9 @@ class _ChosenPlanClientLastDayStartedWidget extends StatelessWidget { Widget build(BuildContext context) { return _BaseNotificationWidget( isRead: notification.isRead, - notificationsText: 'Последний день плана "${notification.planName}" начался', + notificationsText: 'Сегодня последний день плана "${notification.planName}"! Финиш близко! 🏁', createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), ); } } @@ -748,8 +1009,9 @@ class _StayOnTrackToAuthorWidget extends StatelessWidget { Widget build(BuildContext context) { return _BaseNotificationWidget( isRead: notification.isRead, - notificationsText: 'Клиент ${notification.clientName} остается на плане "${notification.planName}"', + notificationsText: 'Клиент ${notification.clientName} продолжает уверенно идти по плану "${notification.planName}" 🔥', createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), ); } } @@ -761,8 +1023,9 @@ class _ClientStartPlanSubscriptionToSubscriberWidget extends StatelessWidget { Widget build(BuildContext context) { return _BaseNotificationWidget( isRead: notification.isRead, - notificationsText: 'Подписка на план "${notification.planName}" началась', + notificationsText: 'У вас новая подписка на план "${notification.planName}"! 🌟', createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), ); } } @@ -774,8 +1037,9 @@ class _ConsultantConfirmPlanSubscriptionToSubscriberWidget extends StatelessWidg Widget build(BuildContext context) { return _BaseNotificationWidget( isRead: notification.isRead, - notificationsText: 'Консультант ${notification.consultantName} подтвердил подписку на план "${notification.planName}"', + notificationsText: 'Ваша подписка на план "${notification.planName}" подтверждена консультантом ${notification.consultantName} ✅', createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), ); } } @@ -787,8 +1051,9 @@ class _ClientPaidPlanSubscriptionToAuthorWidget extends StatelessWidget { Widget build(BuildContext context) { return _BaseNotificationWidget( isRead: notification.isRead, - notificationsText: 'Клиент ${notification.clientName} оплатил подписку на план "${notification.planName}" на сумму ${notification.amount}', + notificationsText: 'Поступление средств: Клиент ${notification.clientName} оплатил подписку на план "${notification.planName}" (${notification.amount}) 💸', createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), ); } } @@ -800,8 +1065,9 @@ class _ConsultantLeftDiaryCommentWidget extends StatelessWidget { Widget build(BuildContext context) { return _BaseNotificationWidget( isRead: notification.isRead, - notificationsText: 'Консультант ${notification.consultantName} оставил комментарий к дневнику по плану "${notification.planName}"', + notificationsText: 'Ваш консультант ${notification.consultantName} прокомментировал дневник по плану "${notification.planName}" 💬', createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), ); } } @@ -813,8 +1079,9 @@ class _ConsultantConfirmPlanSubscriptionToAuthorWidget extends StatelessWidget { Widget build(BuildContext context) { return _BaseNotificationWidget( isRead: notification.isRead, - notificationsText: 'Консультант ${notification.consultantName} подтвердил подписку на план "${notification.planName}"', + notificationsText: 'Консультант ${notification.consultantName} подтвердил подписку на план "${notification.planName}" 🤝', createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), ); } } @@ -826,8 +1093,9 @@ class _ClientPaidPlanSubscriptionToSubscriberWidget extends StatelessWidget { Widget build(BuildContext context) { return _BaseNotificationWidget( isRead: notification.isRead, - notificationsText: 'Оплачена подписка на план "${notification.planName}" на сумму ${notification.amount}', + notificationsText: 'Оплата подписки на план "${notification.planName}" прошла успешно (${notification.amount}) ✅', createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), ); } } @@ -839,8 +1107,9 @@ class _ClientStartPlanSubscriptionToAuthorWidget extends StatelessWidget { Widget build(BuildContext context) { return _BaseNotificationWidget( isRead: notification.isRead, - notificationsText: 'Клиент ${notification.clientName} начал подписку на план "${notification.planName}"', + notificationsText: 'Новый подписчик! Клиент ${notification.clientName} начал подписку на план "${notification.planName}" 🎈', createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), ); } } @@ -852,8 +1121,9 @@ class _ClientCancelPlanSubscriptionToAuthorWidget extends StatelessWidget { Widget build(BuildContext context) { return _BaseNotificationWidget( isRead: notification.isRead, - notificationsText: 'Клиент ${notification.clientName} отменил подписку на план "${notification.planName}"', + notificationsText: 'Отмена подписки: Клиент ${notification.clientName} отказался от плана "${notification.planName}"', createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), ); } } @@ -865,8 +1135,9 @@ class _ClientCancelPlanSubscriptionToSubscriberWidget extends StatelessWidget { Widget build(BuildContext context) { return _BaseNotificationWidget( isRead: notification.isRead, - notificationsText: 'Подписка на план "${notification.planName}" отменена', + notificationsText: 'Ваша подписка на план "${notification.planName}" была отменена.', createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), ); } } @@ -878,8 +1149,9 @@ class _UserDailyReportCompletedWidget extends StatelessWidget { Widget build(BuildContext context) { return _BaseNotificationWidget( isRead: notification.isRead, - notificationsText: 'Клиент ${notification.clientName} завершил ежедневный отчет по плану "${notification.planName}"', + notificationsText: 'Отчет получен: Клиент ${notification.clientName} заполнил дневник по плану "${notification.planName}" 📝', createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), ); } } @@ -891,8 +1163,9 @@ class _StayOnTrackToCustomerWidget extends StatelessWidget { Widget build(BuildContext context) { return _BaseNotificationWidget( isRead: notification.isRead, - notificationsText: 'Консультант ${notification.consultantName} остается на плане "${notification.planName}"', + notificationsText: 'Консультант ${notification.consultantName} продолжает работу с вами по плану "${notification.planName}" 💪', createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), ); } } @@ -904,8 +1177,9 @@ class _PhoneChangedWidget extends StatelessWidget { Widget build(BuildContext context) { return _BaseNotificationWidget( isRead: notification.isRead, - notificationsText: 'Номер телефона изменен с ${notification.oldPhone} на ${notification.newPhone}', + notificationsText: 'Ваш номер телефона был успешно изменен на ${notification.newPhone} 📱', createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), ); } } @@ -917,8 +1191,9 @@ class _SubscriptionWidget extends StatelessWidget { Widget build(BuildContext context) { return _BaseNotificationWidget( isRead: notification.isRead, - notificationsText: 'Подписка ${notification.subscriptionType} на план "${notification.planName}"', + notificationsText: 'Оформлена подписка ${notification.subscriptionType} на план "${notification.planName}" 🎉', createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), ); } } @@ -930,8 +1205,9 @@ class _BadgeAwardedWidget extends StatelessWidget { Widget build(BuildContext context) { return _BaseNotificationWidget( isRead: notification.isRead, - notificationsText: 'Получен значок "${notification.badgeName}": ${notification.badgeDescription}', + notificationsText: 'Новая награда! Вы получили значок "${notification.badgeName}": ${notification.badgeDescription} 🏅', createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), ); } } @@ -943,8 +1219,9 @@ class _BadgeRevokedWidget extends StatelessWidget { Widget build(BuildContext context) { return _BaseNotificationWidget( isRead: notification.isRead, - notificationsText: 'Отозван значок "${notification.badgeName}": ${notification.reason}', + notificationsText: 'Значок "${notification.badgeName}" был отозван. Причина: ${notification.reason}', createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), ); } } @@ -956,8 +1233,9 @@ class _ConsultantRequestStartedWidget extends StatelessWidget { Widget build(BuildContext context) { return _BaseNotificationWidget( isRead: notification.isRead, - notificationsText: 'Запрос к консультанту ${notification.consultantName} начат', + notificationsText: 'Запрос отправлен консультанту ${notification.consultantName} 📩', createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), ); } } @@ -969,8 +1247,9 @@ class _ClientCloseComplaintSuccessWidget extends StatelessWidget { Widget build(BuildContext context) { return _BaseNotificationWidget( isRead: notification.isRead, - notificationsText: 'Жалоба на ${notification.complaintType} "${notification.entityName}" успешно закрыта', + notificationsText: 'Ваша жалоба на ${notification.complaintType} "${notification.entityName}" была рассмотрена и закрыта.', createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), ); } } @@ -982,8 +1261,9 @@ class _ClientRefundSuccessWidget extends StatelessWidget { Widget build(BuildContext context) { return _BaseNotificationWidget( isRead: notification.isRead, - notificationsText: 'Возврат средств по плану "${notification.planName}" на сумму ${notification.amount}: ${notification.reason}', + notificationsText: 'Вам одобрен возврат средств по плану "${notification.planName}" (${notification.amount}). Причина: ${notification.reason}', createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), ); } } @@ -995,8 +1275,9 @@ class _ConsultantRefundSuccessWidget extends StatelessWidget { Widget build(BuildContext context) { return _BaseNotificationWidget( isRead: notification.isRead, - notificationsText: 'Возврат средств клиенту ${notification.clientName} по плану "${notification.planName}" на сумму ${notification.amount}', + notificationsText: 'Выполнен возврат средств клиенту ${notification.clientName} по плану "${notification.planName}" (${notification.amount})', createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), ); } } @@ -1008,8 +1289,9 @@ class _ClientRejectSuccessWidget extends StatelessWidget { Widget build(BuildContext context) { return _BaseNotificationWidget( isRead: notification.isRead, - notificationsText: 'Отклонение по плану "${notification.planName}": ${notification.reason}', + notificationsText: 'Отказ по плану "${notification.planName}" принят: ${notification.reason}', createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), ); } } @@ -1021,8 +1303,9 @@ class _ConsultantRejectSuccessWidget extends StatelessWidget { Widget build(BuildContext context) { return _BaseNotificationWidget( isRead: notification.isRead, - notificationsText: 'Отклонение клиентом ${notification.clientName} по плану "${notification.planName}": ${notification.reason}', + notificationsText: 'Клиент ${notification.clientName} отказался от плана "${notification.planName}": ${notification.reason}', createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), ); } } @@ -1034,8 +1317,9 @@ class _ScoreSubstractedWidget extends StatelessWidget { Widget build(BuildContext context) { return _BaseNotificationWidget( isRead: notification.isRead, - notificationsText: 'Вычтено ${notification.scoreValue} баллов за ${notification.scorableName}', + notificationsText: 'Списание баллов: -${notification.scoreValue} за ${notification.scorableName}', createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), ); } } @@ -1045,10 +1329,22 @@ class _ProductStatusUpdatedForAuthorWidget extends StatelessWidget { final ProductStatusUpdatedForAuthorNotification notification; @override Widget build(BuildContext context) { + String text; + if (notification.oldStatus.isNotEmpty && notification.newStatus.isNotEmpty) { + text = 'Статус продукта "${notification.productName}" изменен с ${notification.oldStatus} на ${notification.newStatus}'; + } else if (notification.newStatus.isNotEmpty) { + text = 'Статус продукта "${notification.productName}" изменен на ${notification.newStatus}'; + } else if (notification.status.isNotEmpty) { + text = 'Статус продукта "${notification.productName}": ${notification.status}'; + } else { + text = 'Статус продукта "${notification.productName}" обновлен'; + } + return _BaseNotificationWidget( isRead: notification.isRead, - notificationsText: 'Статус продукта "${notification.productName}" изменен с ${notification.oldStatus} на ${notification.newStatus}', + notificationsText: text, createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), ); } } @@ -1058,7 +1354,12 @@ class _DiaryReminderForUsersWidget extends StatelessWidget { final DiaryReminderForUsersNotification notification; @override Widget build(BuildContext context) { - return _BaseNotificationWidget(isRead: notification.isRead, notificationsText: 'Напоминание о заполнении дневника', createdAt: notification.createdAt); + return _BaseNotificationWidget( + isRead: notification.isRead, + notificationsText: 'Не забудьте заполнить дневник сегодня! 📓', + createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), + ); } } @@ -1069,9 +1370,163 @@ class _PersonalDietIsDefinedToCustomerWidget extends StatelessWidget { Widget build(BuildContext context) { return _BaseNotificationWidget( isRead: notification.isRead, - onTap: () => GoRouter.of(context).push(RouteConstants.aboutDietScreen, extra: {'id': notification.dietId, 'navigateToComments': false}), - notificationsText: 'Персональная диета "${notification.dietName}" установлена', + notificationsText: 'Для вас составлена персональная диета "${notification.dietName}"! 🥗', createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), + ); + } +} + +class _ConsultantEducationUpdatedWidget extends StatelessWidget { + const _ConsultantEducationUpdatedWidget({required this.notification}); + final ConsultantEducationUpdatedNotification notification; + @override + Widget build(BuildContext context) { + return _BaseNotificationWidget( + isRead: notification.isRead, + notificationsText: 'Консультант ${notification.consultantName} обновил данные об образовании.', + createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), + ); + } +} + +class _ComplaintRegisteredWidget extends StatelessWidget { + const _ComplaintRegisteredWidget({required this.notification}); + final ComplaintRegisteredNotification notification; + @override + Widget build(BuildContext context) { + return _BaseNotificationWidget( + isRead: notification.isRead, + notificationsText: 'Мы получили вашу жалобу на ${notification.entityName}. Спасибо за сигнал!', + createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), + ); + } +} + +class _ContentUnpublishedWidget extends StatelessWidget { + const _ContentUnpublishedWidget({required this.notification}); + final ContentUnpublishedNotification notification; + @override + Widget build(BuildContext context) { + return _BaseNotificationWidget( + isRead: notification.isRead, + notificationsText: 'Ваша публикация ${notification.entityName} была скрыта. Причина: ${notification.reason}', + createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), + ); + } +} + +class _FinanceQuestionCreatedWidget extends StatelessWidget { + const _FinanceQuestionCreatedWidget({required this.notification}); + final FinanceQuestionCreatedNotification notification; + @override + Widget build(BuildContext context) { + return _BaseNotificationWidget( + isRead: notification.isRead, + notificationsText: 'Служба поддержки: Новый вопрос по финансам: ${notification.questionText}', + createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), + ); + } +} + +class _NewChatMessageWidget extends StatelessWidget { + const _NewChatMessageWidget({required this.notification}); + final NewChatMessageNotification notification; + @override + Widget build(BuildContext context) { + return _BaseNotificationWidget( + isRead: notification.isRead, + notificationsText: 'Входящее сообщение от ${notification.senderName}: "${notification.messagePreview}"', + createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), + ); + } +} + +class _CompleteRegistrationWidget extends StatelessWidget { + const _CompleteRegistrationWidget({required this.notification}); + final CompleteRegistrationNotification notification; + @override + Widget build(BuildContext context) { + return _BaseNotificationWidget( + isRead: notification.isRead, + notificationsText: 'Завершите регистрацию, чтобы получить доступ ко всем функциям! 🚀', + createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), + ); + } +} + +class _ChosenDietExpiredWidget extends StatelessWidget { + const _ChosenDietExpiredWidget({required this.notification}); + final ChosenDietExpiredNotification notification; + @override + Widget build(BuildContext context) { + return _BaseNotificationWidget( + isRead: notification.isRead, + notificationsText: 'Диета "${notification.dietName}" завершена. Время подвести итоги!', + createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), + ); + } +} + +class _MotivateUserWidget extends StatelessWidget { + const _MotivateUserWidget({required this.notification}); + final MotivateUserNotification notification; + @override + Widget build(BuildContext context) { + return _BaseNotificationWidget( + isRead: notification.isRead, + notificationsText: notification.message, + createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), + ); + } +} + +class _CommentComplaintWidget extends StatelessWidget { + const _CommentComplaintWidget({required this.notification}); + final CommentComplaintNotification notification; + @override + Widget build(BuildContext context) { + return _BaseNotificationWidget( + isRead: notification.isRead, + notificationsText: 'Поступила жалоба на ваш комментарий: "${notification.commentPreview}". Причина: ${notification.reason}', + createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), + ); + } +} + +class _CommentDeletedWidget extends StatelessWidget { + const _CommentDeletedWidget({required this.notification}); + final CommentDeletedNotification notification; + @override + Widget build(BuildContext context) { + return _BaseNotificationWidget( + isRead: notification.isRead, + notificationsText: 'Ваш комментарий был удален по правилам сообщества: "${notification.commentPreview}"', + createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), + ); + } +} + +class _UserSubscribedWidget extends StatelessWidget { + const _UserSubscribedWidget({required this.notification}); + final UserSubscribedNotification notification; + @override + Widget build(BuildContext context) { + return _BaseNotificationWidget( + isRead: notification.isRead, + notificationsText: 'У вас новый подписчик: ${notification.subscriberName}! 👋', + createdAt: notification.createdAt, + onTap: () => (context.findAncestorWidgetOfExactType() as NotificationWidget)._onTap(context), ); } } diff --git a/lib/features/notifications/service/notifications_service.dart b/lib/features/notifications/service/notifications_service.dart index 1fd5e4d6..c934fb53 100644 --- a/lib/features/notifications/service/notifications_service.dart +++ b/lib/features/notifications/service/notifications_service.dart @@ -413,6 +413,78 @@ class NotificationsService { isRead: readDate != null, createdAt: createdAt, ), + NotificationType.consultantEducationUpdated => ConsultantEducationUpdatedNotification.fromJson( + notificationJson['data'], + type, + isRead: readDate != null, + createdAt: createdAt, + ), + NotificationType.complaintRegistered => ComplaintRegisteredNotification.fromJson( + notificationJson['data'], + type, + isRead: readDate != null, + createdAt: createdAt, + ), + NotificationType.contentUnpublished => ContentUnpublishedNotification.fromJson( + notificationJson['data'], + type, + isRead: readDate != null, + createdAt: createdAt, + ), + NotificationType.contentUnpublishedComplaint => ContentUnpublishedNotification.fromJson( // Reusing ContentUnpublishedNotification if structure is same, or create specific if different. Assuming similar for now or check backend. + notificationJson['data'], + type, + isRead: readDate != null, + createdAt: createdAt, + ), + NotificationType.financeQuestionCreated => FinanceQuestionCreatedNotification.fromJson( + notificationJson['data'], + type, + isRead: readDate != null, + createdAt: createdAt, + ), + NotificationType.newChatMessage => NewChatMessageNotification.fromJson( + notificationJson['data'], + type, + isRead: readDate != null, + createdAt: createdAt, + ), + NotificationType.completeRegistration => CompleteRegistrationNotification.fromJson( + notificationJson['data'], + type, + isRead: readDate != null, + createdAt: createdAt, + ), + NotificationType.chosenDietExpired => ChosenDietExpiredNotification.fromJson( + notificationJson['data'], + type, + isRead: readDate != null, + createdAt: createdAt, + ), + NotificationType.motivateUser => MotivateUserNotification.fromJson( + notificationJson['data'], + type, + isRead: readDate != null, + createdAt: createdAt, + ), + NotificationType.commentComplaint => CommentComplaintNotification.fromJson( + notificationJson['data'], + type, + isRead: readDate != null, + createdAt: createdAt, + ), + NotificationType.commentDeleted => CommentDeletedNotification.fromJson( + notificationJson['data'], + type, + isRead: readDate != null, + createdAt: createdAt, + ), + NotificationType.userSubscribed => UserSubscribedNotification.fromJson( + notificationJson['data'], + type, + isRead: readDate != null, + createdAt: createdAt, + ), NotificationType.unknown => UnknownNotification(type: type, createdAt: parseDate(createdAt)), }; } catch (e) { diff --git a/lib/features/plans/presentation/pages/about_plan_screen.dart b/lib/features/plans/presentation/pages/about_plan_screen.dart index 03e94814..283ef0fc 100644 --- a/lib/features/plans/presentation/pages/about_plan_screen.dart +++ b/lib/features/plans/presentation/pages/about_plan_screen.dart @@ -32,10 +32,18 @@ import 'package:go_router/go_router.dart'; import '../../../../core/widgets/option_widget.dart'; class AboutPlanScreen extends StatefulWidget { - const AboutPlanScreen({super.key, required this.slug, required this.navigateToComments, required this.index}); final String slug; final bool navigateToComments; final int? index; + final int? highlightedCommentId; + + const AboutPlanScreen({ + super.key, + required this.slug, + required this.navigateToComments, + required this.index, + this.highlightedCommentId, + }); @override State createState() => _AboutPlanScreenState(); } @@ -468,13 +476,14 @@ class _AboutPlanScreenState extends State { ), const SizedBox(height: 16), if (commentsState is CommentsStateLoading) const Loader(), - if (commentsState is CommentsStateLoaded) - CommentsWidget( - id: state.planModel?.id.toString() ?? '', - commentSourceType: CommentSourceType.plan, - comment: commentsState.comment, - replyId: replyId, - ), + if (commentsState is CommentsStateLoaded) + CommentsWidget( + id: state.planModel?.id.toString() ?? '', + commentSourceType: CommentSourceType.plan, + comment: commentsState.comment, + replyId: replyId, + highlightedCommentId: widget.highlightedCommentId, + ), ], ), ), diff --git a/lib/features/recipe/presentation/pages/about_recipe_screen.dart b/lib/features/recipe/presentation/pages/about_recipe_screen.dart index a5448a86..ff9e4f0a 100644 --- a/lib/features/recipe/presentation/pages/about_recipe_screen.dart +++ b/lib/features/recipe/presentation/pages/about_recipe_screen.dart @@ -36,8 +36,9 @@ class AboutRecipeScreen extends StatefulWidget { final String recipeSlug; final int? index; final bool navigateToComments; + final int? highlightedCommentId; - const AboutRecipeScreen({super.key, required this.recipeSlug, required this.index, required this.navigateToComments}); + const AboutRecipeScreen({super.key, required this.recipeSlug, required this.index, required this.navigateToComments, this.highlightedCommentId}); @override State createState() => _AboutRecipeScreenState(); @@ -391,6 +392,7 @@ class _AboutRecipeScreenState extends State { commentSourceType: CommentSourceType.recipe, comment: state.comment, replyId: replyId, + highlightedCommentId: widget.highlightedCommentId, ), ], ), diff --git a/test/features/diets/presentation/pages/about_diet_screen_smoke_test.dart b/test/features/diets/presentation/pages/about_diet_screen_smoke_test.dart index a09bfe1d..18546734 100644 --- a/test/features/diets/presentation/pages/about_diet_screen_smoke_test.dart +++ b/test/features/diets/presentation/pages/about_diet_screen_smoke_test.dart @@ -101,7 +101,7 @@ void main() { registerMocks(cubit, commentsCubit); - await tester.pumpWidget(wrap(cubit, feedCubit, commentsCubit, const AboutDietScreen(slug: 'test', id: null, index: null, navigateToComments: false))); + await tester.pumpWidget(wrap(cubit, feedCubit, commentsCubit, const AboutDietScreen(slug: 'test', id: null, index: null, navigateToComments: false, highlightedCommentId: null))); await tester.pump(); expect(find.byType(AboutDietScreen), findsOneWidget); @@ -127,7 +127,7 @@ void main() { registerMocks(cubit, commentsCubit); await mockNetworkImagesFor(() async { - await tester.pumpWidget(wrap(cubit, feedCubit, commentsCubit, const AboutDietScreen(slug: 'test', id: null, index: null, navigateToComments: false))); + await tester.pumpWidget(wrap(cubit, feedCubit, commentsCubit, const AboutDietScreen(slug: 'test', id: null, index: null, navigateToComments: false, highlightedCommentId: null))); await tester.pump(); await tester.pump(const Duration(milliseconds: 600)); }); @@ -157,7 +157,7 @@ void main() { registerMocks(cubit, commentsCubit); await mockNetworkImagesFor(() async { - await tester.pumpWidget(wrap(cubit, feedCubit, commentsCubit, const AboutDietScreen(slug: 'test', id: null, index: null, navigateToComments: false))); + await tester.pumpWidget(wrap(cubit, feedCubit, commentsCubit, const AboutDietScreen(slug: 'test', id: null, index: null, navigateToComments: false, highlightedCommentId: null))); await tester.pump(); await tester.pump(const Duration(milliseconds: 600)); }); @@ -183,7 +183,7 @@ void main() { registerMocks(cubit, commentsCubit); - await tester.pumpWidget(wrap(cubit, feedCubit, commentsCubit, const AboutDietScreen(slug: 'test', id: null, index: null, navigateToComments: false))); + await tester.pumpWidget(wrap(cubit, feedCubit, commentsCubit, const AboutDietScreen(slug: 'test', id: null, index: null, navigateToComments: false, highlightedCommentId: null))); await tester.pump(); expect(find.byType(AboutDietScreen), findsOneWidget); diff --git a/test/features/notifications/models/notification_model_test.dart b/test/features/notifications/models/notification_model_test.dart index 28b21566..cb7504ec 100644 --- a/test/features/notifications/models/notification_model_test.dart +++ b/test/features/notifications/models/notification_model_test.dart @@ -124,7 +124,7 @@ void main() { }); test('UnknownNotification uses default createdAt when not provided', () { - final notification = UnknownNotification(type: NotificationType.unknown); + final notification = UnknownNotification(type: NotificationType.unknown, createdAt: DateTime.now()); expect(notification.type, NotificationType.unknown); expect(notification.isRead, true); diff --git a/test/features/notifications/presentation/widgets/notification_widget_test.dart b/test/features/notifications/presentation/widgets/notification_widget_test.dart index f624993a..d6a8025d 100644 --- a/test/features/notifications/presentation/widgets/notification_widget_test.dart +++ b/test/features/notifications/presentation/widgets/notification_widget_test.dart @@ -160,6 +160,8 @@ void main() { type: NotificationType.chosenPlanStartedToConsultant, planName: 'Test Plan', customerName: 'Test Customer', + planId: 1, + customerId: 1, isRead: false, createdAt: testDate, ); @@ -586,6 +588,8 @@ void main() { type: NotificationType.stayOnTrackToAuthor, planName: 'Test Plan', clientName: 'Test Client', + planId: 1, + clientId: 1, isRead: false, createdAt: testDate, ); @@ -752,6 +756,8 @@ void main() { type: NotificationType.stayOnTrackToCustomer, planName: 'Test Plan', consultantName: 'Test Consultant', + planId: 1, + consultantId: 1, isRead: false, createdAt: testDate, ); diff --git a/test/features/plans/presentation/pages/about_plan_screen_smoke_test.dart b/test/features/plans/presentation/pages/about_plan_screen_smoke_test.dart index 32f67e95..4fa46bb3 100644 --- a/test/features/plans/presentation/pages/about_plan_screen_smoke_test.dart +++ b/test/features/plans/presentation/pages/about_plan_screen_smoke_test.dart @@ -123,7 +123,7 @@ void main() { registerMocks(planCubit, commentsCubit); - await tester.pumpWidget(wrap(planCubit, feedCubit, commentsCubit, const AboutPlanScreen(slug: 'test', navigateToComments: false, index: null))); + await tester.pumpWidget(wrap(planCubit, feedCubit, commentsCubit, const AboutPlanScreen(slug: 'test', navigateToComments: false, index: null, highlightedCommentId: null))); await tester.pump(); expect(find.byType(AboutPlanScreen), findsOneWidget); @@ -149,7 +149,7 @@ void main() { registerMocks(planCubit, commentsCubit); await mockNetworkImagesFor(() async { - await tester.pumpWidget(wrap(planCubit, feedCubit, commentsCubit, const AboutPlanScreen(slug: 'test', navigateToComments: false, index: null))); + await tester.pumpWidget(wrap(planCubit, feedCubit, commentsCubit, const AboutPlanScreen(slug: 'test', navigateToComments: false, index: null, highlightedCommentId: null))); await tester.pump(); await tester.pump(); }); @@ -177,7 +177,7 @@ void main() { registerMocks(planCubit, commentsCubit); - await tester.pumpWidget(wrap(planCubit, feedCubit, commentsCubit, const AboutPlanScreen(slug: 'test', navigateToComments: false, index: null))); + await tester.pumpWidget(wrap(planCubit, feedCubit, commentsCubit, const AboutPlanScreen(slug: 'test', navigateToComments: false, index: null, highlightedCommentId: null))); await tester.pump(); expect(find.byType(AboutPlanScreen), findsOneWidget); From d4f41fe6cfac4c78bfc7cf385e2e7403a0ed5bbd Mon Sep 17 00:00:00 2001 From: mso7nueh Date: Mon, 16 Feb 2026 12:46:55 +0300 Subject: [PATCH 16/58] feat: implement recipe drafting functionality and refactor MyRecipesTab to be stateless --- .gitignore | 3 +- .../create_recipe_cubit.dart | 33 +++++ .../create_recipe_state.dart | 2 + .../pages/recipe_create_screen.dart | 104 ++++++++++---- .../widgets/ingredients_widget.dart | 8 ++ .../reporitories/recipe_repository.dart | 127 +++++++++++------- .../services/recipe_service.dart | 10 +- .../my_recipes_cubit/my_recieps_cubit.dart | 12 ++ .../presentation/pages/my_recipes_screen.dart | 45 +++++-- .../widgets/my_recipe_widget.dart | 106 +++++++++++---- .../presentation/widgets/my_recipes_tab.dart | 51 +++---- .../reporitories/recipe_repository_test.dart | 29 ++-- .../widgets/my_recipes_tab_test.dart | 11 +- 13 files changed, 388 insertions(+), 153 deletions(-) diff --git a/.gitignore b/.gitignore index 530ea713..0d94aecf 100644 --- a/.gitignore +++ b/.gitignore @@ -20,4 +20,5 @@ env/.env .env android/key.properties app/upload-keystore.jks -coverage/ \ No newline at end of file +coverage/ +eatvio/ \ No newline at end of file diff --git a/lib/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit.dart b/lib/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit.dart index 8f7bca23..482d7d56 100644 --- a/lib/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit.dart +++ b/lib/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit.dart @@ -172,6 +172,23 @@ class CreateRecipeCubit extends Cubit { } } + Future saveAsDraft([bool update = false, String? slug]) async { + try { + final model = state.entity.toModel(); + await recipeRepository.saveAsDraft(model, update, slug); + emit(state.copyWith(drafted: true)); + return true; + } catch (e, st) { + await Sentry.captureException(e, stackTrace: st); + log(e.toString(), stackTrace: st); + return false; + } + } + + bool validateForDraft() { + return validateAll(); + } + Future downloadImageAsFile(String imageUrl) async { final response = await http.get(Uri.parse(imageUrl)); @@ -239,6 +256,7 @@ class CreateRecipeCubit extends Cubit { final isActiveCookTimeValid = validateActiveCookTime(); final isEnergyPerServingsValid = validateEnergyPerServing(); + final isIngredientsValid = validateIngredients(); final isStepsValid = validateSteps(); return isTitleValid && @@ -248,8 +266,19 @@ class CreateRecipeCubit extends Cubit { isCookTimeValid && isActiveCookTimeValid && isEnergyPerServingsValid && + isIngredientsValid && isStepsValid; } + + bool validateIngredients() { + if (state.entity.ingredients.isEmpty) { + emit(state.copyWith(ingredientsValidationError: 'Добавьте хотя бы один ингредиент')); + return false; + } else { + emit(state.copyWith(ingredientsValidationError: null)); + return true; + } + } bool validateTitle() { if (state.entity.title == null || (state.entity.title?.isEmpty ?? true)) { @@ -343,6 +372,10 @@ class CreateRecipeCubit extends Cubit { bool validateSteps() { bool isValid = true; + if (state.entity.steps.isEmpty) { + emit(state.copyWith(stepsValidationError: 'Добавьте хотя бы один шаг')); + return false; + } for (var step in state.entity.steps) { if (step.description == null || step.description!.trim().isEmpty) { isValid = false; diff --git a/lib/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_state.dart b/lib/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_state.dart index ef0efe78..23cb9895 100644 --- a/lib/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_state.dart +++ b/lib/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_state.dart @@ -8,6 +8,7 @@ abstract class CreateRecipeState with _$CreateRecipeState { const factory CreateRecipeState({ required RecipeCreateEntity entity, required bool loading, + @Default(false) bool drafted, String? titleValidationError, String? descriptionValidationError, String? cookingTimeValidationError, @@ -16,6 +17,7 @@ abstract class CreateRecipeState with _$CreateRecipeState { String? energyPerServingValidationError, String? imageValidationError, String? stepsValidationError, + String? ingredientsValidationError, }) = _CreateRecipeState; } diff --git a/lib/features/create_recipe/presentation/pages/recipe_create_screen.dart b/lib/features/create_recipe/presentation/pages/recipe_create_screen.dart index 932852ac..1538dc57 100644 --- a/lib/features/create_recipe/presentation/pages/recipe_create_screen.dart +++ b/lib/features/create_recipe/presentation/pages/recipe_create_screen.dart @@ -81,55 +81,107 @@ class _RecipeCreateScreenState extends State { builder: (context, state) { return Builder( builder: (context) { - return _isRecipeBeingPublished - ? const Center(child: Loader()) - : InkWell( - key: AppKeys.recipeCreateScreenPublishButton, + if (_isRecipeBeingPublished) { + return const Center(child: Loader()); + } + return Column( + children: [ + InkWell( + key: AppKeys.recipeCreateScreenPublishButton, + onTap: () async { + setState(() { + _isRecipeBeingPublished = true; + }); + if (context.read().validateAll()) { + final isSuccess = await context.read().publishRecipe( + widget.recipe == null ? false : true, + widget.recipe?.slug, + ); + setState(() { + _isRecipeBeingPublished = false; + }); + if (isSuccess) { + Future.delayed(const Duration(milliseconds: 500), () { + if (!context.mounted) return; + context.pop(); + showBottomSheetWithBlur( + userRootNavigator: true, + context: context, + child: SuccessPublishedRecipeBottomSheet(title: state.entity.title ?? 'Рецепт'), + ); + }); + } else { + if (!context.mounted) return; + showErrorDialog(context, title: '${s.could_not_publish_recipe} ${state.entity.title}'); + } + } else { + setState(() { + _isRecipeBeingPublished = false; + }); + } + }, + borderRadius: BorderRadius.circular(20), + 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( + widget.recipe != null ? 'Обновить' : 'Опубликовать', + style: const TextStyle(fontSize: 16, color: Colors.white, fontWeight: FontWeight.w600), + ), + ), + ), + ), + const SizedBox(height: 12), + if (widget.recipe == null || widget.recipe?.state == 'drafted') + InkWell( onTap: () async { setState(() { _isRecipeBeingPublished = true; }); - if (context.read().validateAll()) { - final isSuccess = await context.read().publishRecipe( - widget.recipe == null ? false : true, - widget.recipe?.slug, - ); + if (context.read().validateForDraft()) { + final isSuccess = await context.read().saveAsDraft( + widget.recipe == null ? false : true, + widget.recipe?.slug, + ); setState(() { _isRecipeBeingPublished = false; }); if (isSuccess) { - Future.delayed(const Duration(milliseconds: 500), () { - if (!context.mounted) return; - context.pop(); - showBottomSheetWithBlur( - userRootNavigator: true, - context: context, - child: SuccessPublishedRecipeBottomSheet(title: state.entity.title ?? 'Рецепт'), - ); - }); + if (!context.mounted) return; + context.pop(); } else { if (!context.mounted) return; - showErrorDialog(context, title: '${s.could_not_publish_recipe} ${state.entity.title}'); + showErrorDialog(context, title: 'Не удалось сохранить черновик'); } + } else { + setState(() { + _isRecipeBeingPublished = false; + }); } - setState(() { - _isRecipeBeingPublished = false; - }); }, borderRadius: BorderRadius.circular(20), child: Ink( width: MediaQuery.of(context).size.width - 32, - decoration: BoxDecoration(color: appTheme.blueA400, borderRadius: BorderRadius.circular(20)), + decoration: BoxDecoration( + color: Colors.transparent, + borderRadius: BorderRadius.circular(20), + border: Border.all(color: appTheme.blueA400), + ), child: Container( padding: const EdgeInsets.all(16), alignment: Alignment.center, child: Text( - widget.recipe != null ? 'Обновить' : 'Опубликовать', - style: const TextStyle(fontSize: 16, color: Colors.white, fontWeight: FontWeight.w600), + 'Сохранить как черновик', + style: TextStyle(fontSize: 16, color: appTheme.blueA400, fontWeight: FontWeight.w600), ), ), ), - ); + ), + ], + ); }, ); }, diff --git a/lib/features/create_recipe/presentation/widgets/ingredients_widget.dart b/lib/features/create_recipe/presentation/widgets/ingredients_widget.dart index 4b3261ab..6894c162 100644 --- a/lib/features/create_recipe/presentation/widgets/ingredients_widget.dart +++ b/lib/features/create_recipe/presentation/widgets/ingredients_widget.dart @@ -85,6 +85,14 @@ class _IngredientsWidgetState extends State { title: Text('Добавить ингредиент', style: TextStyle(color: appTheme.blueA400)), leading: Icon(Icons.add_rounded, color: appTheme.blueA400), ), + if (state.ingredientsValidationError != null) + Padding( + padding: const EdgeInsets.only(left: 16, top: 4), + child: Text( + state.ingredientsValidationError!, + style: const TextStyle(color: Colors.red, fontSize: 12), + ), + ), ], ), ), diff --git a/lib/features/create_recipe/reporitories/recipe_repository.dart b/lib/features/create_recipe/reporitories/recipe_repository.dart index b1b5fce9..f079d810 100644 --- a/lib/features/create_recipe/reporitories/recipe_repository.dart +++ b/lib/features/create_recipe/reporitories/recipe_repository.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:developer'; import 'dart:io'; import 'package:dio/dio.dart'; import 'package:eatvio_app/core/image_compressing/image_compress_utility.dart'; @@ -23,55 +24,59 @@ class RecipeRepository { } } - Future publishRecipe(RecipeModel recipe, [bool update = false, String? slug]) async { - try { - if (recipe.image == null || (recipe.image?.isEmpty ?? true)) { - return; - } - final image = await ImageCompressUtility.compressFile(File(recipe.image ?? '')); - final multiPartFile = await MultipartFile.fromFile(image.path); + Future _buildFormData(RecipeModel recipe, {String state = 'active'}) async { + if (recipe.image == null || (recipe.image?.isEmpty ?? true)) { + throw Exception('Recipe image is required'); + } + final image = await ImageCompressUtility.compressFile(File(recipe.image ?? '')); + final multiPartFile = await MultipartFile.fromFile(image.path); - final ingredientsMap = recipe.ingredients?.map((ingredient) { - if (ingredient != null) { - return ingredient.toJson(); - } - }).toList() ?? - []; - - 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_md': 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_md': step.description ?? "", - 'image': multipartFile, - }); + final ingredientsMap = recipe.ingredients?.map((ingredient) { + if (ingredient != null) { + return ingredient.toJson(); } + }).toList() ?? + []; + + 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_md': 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_md': step.description ?? "", + 'image': multipartFile, + }); } } - final formMap = { - 'type': recipe.type, - 'state': 'active', - 'title': recipe.title, - 'tags[]': recipe.tags ?? [], - 'description_md': recipe.description, - 'video_url': recipe.videoUrl, - '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); + } + final formMap = { + 'type': recipe.type, + 'state': state, + 'title': recipe.title, + 'tags[]': recipe.tags ?? [], + 'description_md': recipe.description, + 'video_url': recipe.videoUrl, + '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)); + return FormData.fromMap(formMap); + } + + Future publishRecipe(RecipeModel recipe, [bool update = false, String? slug]) async { + try { + final formData = await _buildFormData(recipe, state: 'active'); if (update) { await recipeService.updateRecipe(slug: slug!, formData: formData); } else { @@ -83,6 +88,38 @@ class RecipeRepository { } } + Future saveAsDraft(RecipeModel recipe, [bool update = false, String? slug]) async { + try { + final formData = await _buildFormData(recipe, state: 'archived'); + log(formData.fields.toString()); + if (update) { + final response = await recipeService.updateRecipe(slug: slug!, formData: formData); + final recipeId = response.data['data']?['id'] ?? response.data['id']; + if (recipeId != null) { + await recipeService.draftRecipe(id: recipeId as int); + } + } else { + final response = await recipeService.publishRecipe(formData: formData); + final recipeId = response.data['data']?['id'] ?? response.data['id']; + if (recipeId != null) { + await recipeService.draftRecipe(id: recipeId as int); + } + } + } catch (e, s) { + await Sentry.captureException(e, stackTrace: s); + rethrow; + } + } + + Future draftExistingRecipe(int id) async { + try { + await recipeService.draftRecipe(id: id); + } catch (e, s) { + await Sentry.captureException(e, stackTrace: s); + rethrow; + } + } + Future getRecipe(String recipeID) async { try { return await recipeService.getRecipe(recipeID); diff --git a/lib/features/create_recipe/services/recipe_service.dart b/lib/features/create_recipe/services/recipe_service.dart index 41ee957a..199c7d54 100644 --- a/lib/features/create_recipe/services/recipe_service.dart +++ b/lib/features/create_recipe/services/recipe_service.dart @@ -2,6 +2,7 @@ import 'package:dio/dio.dart'; import 'package:eatvio_app/features/create_recipe/models/recipe_model.dart'; import 'package:eatvio_app/features/create_recipe/models/recipe_tag_model.dart'; import 'package:retrofit/retrofit.dart'; +import 'package:retrofit/http.dart'; import 'package:eatvio_app/features/create_recipe/models/product.dart'; part 'recipe_service.g.dart'; @@ -33,7 +34,7 @@ abstract class CreateRecipeService { @POST('/api/recipes/create') @MultiPart() - Future publishRecipe({ + Future> publishRecipe({ @Body() required FormData formData, }); @@ -47,8 +48,13 @@ abstract class CreateRecipeService { @POST('/api/recipes/update/{slug}') @MultiPart() - Future updateRecipe({ + Future> updateRecipe({ @Path() required String slug, @Body() required FormData formData, }); + + @POST('/api/recipes/draft/{id}') + Future draftRecipe({ + @Path() required int id, + }); } diff --git a/lib/features/recipe/presentation/cubit/my_recipes_cubit/my_recieps_cubit.dart b/lib/features/recipe/presentation/cubit/my_recipes_cubit/my_recieps_cubit.dart index 44481c3a..2ab45971 100644 --- a/lib/features/recipe/presentation/cubit/my_recipes_cubit/my_recieps_cubit.dart +++ b/lib/features/recipe/presentation/cubit/my_recipes_cubit/my_recieps_cubit.dart @@ -11,6 +11,7 @@ class MyRecipesCubit extends Cubit { static const String active = 'active'; static const String archived = 'archived'; + static const String drafted = 'drafted'; MyRecipesCubit({ required this.myRecipesRepository, @@ -50,6 +51,17 @@ class MyRecipesCubit extends Cubit { } } + Future setDraftedState() async { + emit(const MyRecipesState.loading()); + try { + final myRecipes = await myRecipesRepository.getMyRecipes(); + final List draftedRecipes = myRecipes.where((recipe) => recipe.state == drafted).toList(); + emit(MyRecipesState.loaded(recipes: draftedRecipes, selectedState: drafted)); + } catch (e) { + emit(const MyRecipesState.error()); + } + } + Future getRecipeById(String slug) async { return await aboutRecipeRepository.getRecipe(slug); } diff --git a/lib/features/recipe/presentation/pages/my_recipes_screen.dart b/lib/features/recipe/presentation/pages/my_recipes_screen.dart index c3e29db5..1f276cf6 100644 --- a/lib/features/recipe/presentation/pages/my_recipes_screen.dart +++ b/lib/features/recipe/presentation/pages/my_recipes_screen.dart @@ -4,6 +4,7 @@ import 'package:eatvio_app/core/theme/theme_helper.dart'; import 'package:eatvio_app/features/recipe/presentation/cubit/my_recipes_cubit/my_recieps_cubit.dart'; import 'package:eatvio_app/features/recipe/presentation/cubit/my_recipes_cubit/my_recipes_state.dart'; import 'package:eatvio_app/features/recipe/presentation/widgets/my_recipe_widget.dart'; +import 'package:eatvio_app/features/recipe/presentation/widgets/my_recipes_tab.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -32,15 +33,41 @@ class MyRecipesScreen extends StatelessWidget { create: (BuildContext context) => sl()..getMyRecipes(), child: BlocBuilder( builder: (BuildContext context, MyRecipesState state) { - if (state is MyRecipesStateLoaded) { - return ListView.separated( - padding: const EdgeInsets.symmetric(vertical: 16), - itemBuilder: (context, index) => MyRecipeWidget(recipe: state.recipes[index]), - separatorBuilder: (context, index) => const SizedBox(height: 12), - itemCount: state.recipes.length, - ); - } - return Container(); + return Column( + children: [ + MyRecipesTab( + selectedIndex: state is MyRecipesStateLoaded + ? (state.selectedState == MyRecipesCubit.drafted ? 1 : 0) + : 0, + onTabChanged: (index) { + if (index == 0) { + context.read().setActiveState(); + } else { + context.read().setDraftedState(); + } + }, + ), + Expanded( + child: state is MyRecipesStateLoaded + ? state.recipes.isEmpty + ? Center( + child: Text( + state.selectedState == MyRecipesCubit.drafted + ? 'Нет черновиков' + : 'Нет опубликованных рецептов', + style: TextStyles.textLMedium.copyWith(color: appTheme.gray500), + ), + ) + : ListView.separated( + padding: const EdgeInsets.symmetric(vertical: 16), + itemBuilder: (context, index) => MyRecipeWidget(recipe: state.recipes[index]), + separatorBuilder: (context, index) => const SizedBox(height: 12), + itemCount: state.recipes.length, + ) + : const Center(child: CircularProgressIndicator()), + ), + ], + ); }, ), ), diff --git a/lib/features/recipe/presentation/widgets/my_recipe_widget.dart b/lib/features/recipe/presentation/widgets/my_recipe_widget.dart index c30aebc1..a47b6161 100644 --- a/lib/features/recipe/presentation/widgets/my_recipe_widget.dart +++ b/lib/features/recipe/presentation/widgets/my_recipe_widget.dart @@ -12,6 +12,8 @@ import 'package:eatvio_app/features/recipe/presentation/cubit/my_recipes_cubit/m import 'package:eatvio_app/generated/l10n.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:eatvio_app/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit.dart'; +import 'package:eatvio_app/features/create_recipe/presentation/pages/recipe_create_screen.dart'; import 'package:eatvio_app/core/widgets/markdown_html_widget.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:go_router/go_router.dart'; @@ -99,11 +101,7 @@ class _MyRecipeWidgetState extends State { ? '${widget.recipe.description.substring(0, 200)}...' : widget.recipe.description; - return MarkdownHtmlWidget( - markdownText: description, - fontSize: TextStyles.textSMedium.fontSize ?? 14, - textColor: appTheme.black, - ); + return MarkdownHtmlWidget(markdownText: description, fontSize: TextStyles.textSMedium.fontSize ?? 14, textColor: appTheme.black); }, ), ), @@ -126,7 +124,13 @@ class _MyRecipeWidgetState extends State { return; } showSuccessToast('Рецепт удален'); - if (context.mounted) context.read().getMyRecipes(); + if (context.mounted) { + if (isDraft) { + context.read().setDraftedState(); + } else { + context.read().getMyRecipes(); + } + } }, text: 'Удалить', height: 52, @@ -140,30 +144,83 @@ class _MyRecipeWidgetState extends State { ), const SizedBox(width: 8), Expanded( - child: CustomOutlinedButton( - onPressed: () async { - final box = context.findRenderObject() as RenderBox?; - await SharePlus.instance.share( - ShareParams( - uri: Uri.parse('${sl().baseUrl}/ru/recipe/${widget.recipe.slug}'), - sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size, + child: PopupMenuButton( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + offset: const Offset(0, 50), + itemBuilder: (context) => [ + if (!isDraft) + PopupMenuItem( + onTap: () async { + final box = context.findRenderObject() as RenderBox?; + await SharePlus.instance.share( + ShareParams( + uri: Uri.parse('${sl().baseUrl}/ru/recipe/${widget.recipe.slug}'), + sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size, + ), + ); + }, + child: Row( + children: [ + Icon(Icons.share, size: 20, color: appTheme.black), + const SizedBox(width: 8), + Text(S.of(context).share, style: TextStyles.textSMedium), + ], + ), ), - ); - }, - height: 52, - width: 52, - buttonStyle: ButtonStyle( - shape: WidgetStatePropertyAll(RoundedRectangleBorder(borderRadius: BorderRadius.circular(20))), - backgroundColor: WidgetStatePropertyAll(appTheme.blueA400), - side: const WidgetStatePropertyAll(BorderSide.none), + if (widget.recipe.state == 'active' || isDraft) + PopupMenuItem( + onTap: () { + context.push(RecipeCreateScreen.path, extra: widget.recipe); + }, + child: Row( + children: [ + Icon(Icons.edit, size: 20, color: appTheme.black), + const SizedBox(width: 8), + Text(S.of(context).edit, style: TextStyles.textSMedium), + ], + ), + ), + ], + child: CustomOutlinedButton( + onPressed: () {}, + height: 52, + width: 52, + buttonStyle: ButtonStyle( + shape: WidgetStatePropertyAll(RoundedRectangleBorder(borderRadius: BorderRadius.circular(20))), + backgroundColor: WidgetStatePropertyAll(appTheme.blueA400), + side: const WidgetStatePropertyAll(BorderSide.none), + ), + text: isDraft ? '...' : s.share, + buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700), ), - text: s.share, - buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700), ), ), ], ), const SizedBox(height: 8), + if (isDraft) + Padding( + padding: const EdgeInsets.only(top: 12), + child: SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: () { + // Publish draft + context.read().publishRecipe(true, widget.recipe.slug).then((success) { + if (success) { + context.read().setDraftedState(); + } + }); + }, + style: ElevatedButton.styleFrom( + backgroundColor: appTheme.blueA400, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + padding: const EdgeInsets.symmetric(vertical: 12), + ), + child: Text('Опубликовать', style: TextStyles.textSMedium.copyWith(color: Colors.white)), + ), + ), + ), ], ), ), @@ -172,6 +229,8 @@ class _MyRecipeWidgetState extends State { ); } + bool get isDraft => widget.recipe.state == 'drafted'; + Container _iconButton(String iconPath, [String? text]) { return Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), @@ -192,4 +251,3 @@ class _MyRecipeWidgetState extends State { ); } } - diff --git a/lib/features/recipe/presentation/widgets/my_recipes_tab.dart b/lib/features/recipe/presentation/widgets/my_recipes_tab.dart index f6de8669..af68f89e 100644 --- a/lib/features/recipe/presentation/widgets/my_recipes_tab.dart +++ b/lib/features/recipe/presentation/widgets/my_recipes_tab.dart @@ -4,30 +4,21 @@ import 'package:eatvio_app/core/widgets/animated_option_button.dart'; import 'package:eatvio_app/generated/l10n.dart'; import 'package:flutter/widgets.dart'; -class MyRecipesTab extends StatefulWidget { - const MyRecipesTab({super.key, this.additionalOnTap}); +class MyRecipesTab extends StatelessWidget { + const MyRecipesTab({ + super.key, + required this.selectedIndex, + required this.onTabChanged, + }); - final void Function()? additionalOnTap; - - @override - State createState() => _MyRecipesTabState(); -} - -class _MyRecipesTabState extends State { - final List tabTitles = []; - - @override - void didChangeDependencies() { - final s = S.of(context); - if (tabTitles.isEmpty) { - tabTitles.add(s.published); - tabTitles.add(s.drafted); - } - super.didChangeDependencies(); - } + final int selectedIndex; + final ValueChanged onTabChanged; @override Widget build(BuildContext context) { + final s = S.of(context); + final tabTitles = [s.published, s.drafted]; + return Align( alignment: Alignment.centerLeft, child: Row( @@ -44,18 +35,14 @@ class _MyRecipesTabState extends State { children: [ ...List.generate( 2, - (index) => - Expanded( - child: AnimatedOptionButton( - textStyle: TextStyles.textSMedium, - isSelected: false, // state.selectedStateIndex == index, - text: tabTitles[index], - onTap: () { - widget.additionalOnTap?.call(); - // context.read().changeSelectedStateIndex(index); - }, - ), - ), + (index) => Expanded( + child: AnimatedOptionButton( + textStyle: TextStyles.textSMedium, + isSelected: selectedIndex == index, + text: tabTitles[index], + onTap: () => onTabChanged(index), + ), + ), ), ], ), diff --git a/test/features/create_recipe/reporitories/recipe_repository_test.dart b/test/features/create_recipe/reporitories/recipe_repository_test.dart index 95cb1462..4a82a264 100644 --- a/test/features/create_recipe/reporitories/recipe_repository_test.dart +++ b/test/features/create_recipe/reporitories/recipe_repository_test.dart @@ -10,8 +10,15 @@ import 'package:eatvio_app/features/create_recipe/services/recipe_service.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; +import 'package:retrofit/retrofit.dart'; + class MockCreateRecipeService extends Mock implements CreateRecipeService {} +final mockHttpResponse = HttpResponse( + null, + Response(requestOptions: RequestOptions(path: ''), statusCode: 200), +); + void main() { setUpAll(() { registerFallbackValue(FormData()); @@ -109,7 +116,7 @@ void main() { test('deleteRecipe - удаляет рецепт', () async { const slug = 'recipe-slug'; - when(() => mockService.deleteRecipe(slug: slug)).thenAnswer((_) async => Future.value()); + when(() => mockService.deleteRecipe(slug: slug)).thenAnswer((_) async => Future.value(mockHttpResponse)); await repository.deleteRecipe(slug); @@ -188,7 +195,7 @@ void main() { // Мокируем ImageCompressUtility.compressFile // Это сложно, так как это статический метод, но мы можем проверить, что вызывается updateRecipe when(() => mockService.updateRecipe(slug: slug, formData: any(named: 'formData'))) - .thenAnswer((_) async => Future.value()); + .thenAnswer((_) async => Future.value(mockHttpResponse)); // Этот тест может упасть из-за ImageCompressUtility, но мы проверяем логику try { @@ -216,7 +223,7 @@ void main() { ); when(() => mockService.publishRecipe(formData: any(named: 'formData'))) - .thenAnswer((_) async => Future.value()); + .thenAnswer((_) async => Future.value(mockHttpResponse)); try { await repository.publishRecipe(recipe); @@ -243,7 +250,7 @@ void main() { ); when(() => mockService.publishRecipe(formData: any(named: 'formData'))) - .thenAnswer((_) async => Future.value()); + .thenAnswer((_) async => Future.value(mockHttpResponse)); try { await repository.publishRecipe(recipe); @@ -270,7 +277,7 @@ void main() { ); when(() => mockService.publishRecipe(formData: any(named: 'formData'))) - .thenAnswer((_) async => Future.value()); + .thenAnswer((_) async => Future.value(mockHttpResponse)); try { await repository.publishRecipe(recipe); @@ -297,7 +304,7 @@ void main() { ); when(() => mockService.publishRecipe(formData: any(named: 'formData'))) - .thenAnswer((_) async => Future.value()); + .thenAnswer((_) async => Future.value(mockHttpResponse)); try { await repository.publishRecipe(recipe); @@ -364,7 +371,7 @@ void main() { ); when(() => mockService.publishRecipe(formData: any(named: 'formData'))) - .thenAnswer((_) async => Future.value()); + .thenAnswer((_) async => Future.value(mockHttpResponse)); try { await repository.publishRecipe(recipe); @@ -399,7 +406,7 @@ void main() { ); when(() => mockService.publishRecipe(formData: any(named: 'formData'))) - .thenAnswer((_) async => Future.value()); + .thenAnswer((_) async => Future.value(mockHttpResponse)); try { await repository.publishRecipe(recipe); @@ -435,7 +442,7 @@ void main() { ); when(() => mockService.publishRecipe(formData: any(named: 'formData'))) - .thenAnswer((_) async => Future.value()); + .thenAnswer((_) async => Future.value(mockHttpResponse)); try { await repository.publishRecipe(recipe); @@ -472,7 +479,7 @@ void main() { ); when(() => mockService.publishRecipe(formData: any(named: 'formData'))) - .thenAnswer((_) async => Future.value()); + .thenAnswer((_) async => Future.value(mockHttpResponse)); try { await repository.publishRecipe(recipe); @@ -499,7 +506,7 @@ void main() { ); when(() => mockService.publishRecipe(formData: any(named: 'formData'))) - .thenAnswer((_) async => Future.value()); + .thenAnswer((_) async => Future.value(mockHttpResponse)); try { await repository.publishRecipe(recipe); diff --git a/test/features/recipe/presentation/widgets/my_recipes_tab_test.dart b/test/features/recipe/presentation/widgets/my_recipes_tab_test.dart index efba34ba..58d21ef1 100644 --- a/test/features/recipe/presentation/widgets/my_recipes_tab_test.dart +++ b/test/features/recipe/presentation/widgets/my_recipes_tab_test.dart @@ -7,7 +7,7 @@ import 'package:flutter_test/flutter_test.dart'; void main() { group('MyRecipesTab smoke', () { - Widget wrap(Widget child) { + Widget wrap() { return MaterialApp( theme: ThemeHelper().themeData(), localizationsDelegates: const [ @@ -17,12 +17,17 @@ void main() { GlobalCupertinoLocalizations.delegate, ], supportedLocales: const [Locale('ru')], - home: Scaffold(body: child), + home: Material( + child: MyRecipesTab( + selectedIndex: 0, + onTabChanged: (_) {}, + ), + ), ); } testWidgets('renders my recipes tab', (tester) async { - await tester.pumpWidget(wrap(const MyRecipesTab())); + await tester.pumpWidget(wrap()); await tester.pump(); expect(find.byType(MyRecipesTab), findsOneWidget); From 6e7894b9115c47b80b7b17910230b9d397b1fff4 Mon Sep 17 00:00:00 2001 From: mso7nueh Date: Mon, 16 Feb 2026 12:48:08 +0300 Subject: [PATCH 17/58] build: Remove `eatvio/` from `.gitignore`. --- .gitignore | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 0d94aecf..530ea713 100644 --- a/.gitignore +++ b/.gitignore @@ -20,5 +20,4 @@ env/.env .env android/key.properties app/upload-keystore.jks -coverage/ -eatvio/ \ No newline at end of file +coverage/ \ No newline at end of file From 699148b20228e0796082eb79a205bc28a762b3f5 Mon Sep 17 00:00:00 2001 From: misha Date: Mon, 16 Feb 2026 15:33:15 +0300 Subject: [PATCH 18/58] feat(my_diets): fixed navigation to diet creation screen --- lib/core/routing/other_routes.dart | 8 ++++---- .../create_diet/presentation/create_diet_screen.dart | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/core/routing/other_routes.dart b/lib/core/routing/other_routes.dart index 0e30336e..7c16bf74 100644 --- a/lib/core/routing/other_routes.dart +++ b/lib/core/routing/other_routes.dart @@ -159,9 +159,9 @@ class OtherRoutes { path: CreateDietScreen.path, name: CreateDietScreen.name, builder: (context, state) { - final data = state.extra as Map; - final diet = data['diet'] as Diet?; - final isEditing = data['isEditing'] as bool?; + final data = state.extra as Map?; + final diet = data?['diet'] as Diet?; + final isEditing = data?['isEditing'] as bool?; return CreateDietScreen(diet: diet, isEditing: isEditing); }, ), @@ -172,7 +172,7 @@ class OtherRoutes { final data = state.extra as Map; final diet = data['diet'] as Diet; final isImageInBase64 = data['isImageInBase64'] as bool; - return DietPreviewScreen(diet: diet, isImageInBase64: isImageInBase64,); + return DietPreviewScreen(diet: diet, isImageInBase64: isImageInBase64); }, ), GoRoute(path: RouteConstants.createPlanScreen, name: 'createPlanScreen', builder: (context, state) => const CreatePlanScreen()), diff --git a/lib/features/create_diet/presentation/create_diet_screen.dart b/lib/features/create_diet/presentation/create_diet_screen.dart index 4678f989..5d95e783 100644 --- a/lib/features/create_diet/presentation/create_diet_screen.dart +++ b/lib/features/create_diet/presentation/create_diet_screen.dart @@ -274,7 +274,7 @@ class _CreateDietScreenContentState extends State { : () async { final diet = await context.read().getDietForPreview(); if (diet == null) { - // ignore: use_build_context_synchronously + if (!context.mounted) return; _scrollToFirstError(context.read().state); return; } @@ -315,7 +315,7 @@ class _CreateDietScreenContentState extends State { } else { final result = await context.read().createDiet(); if (result == 'validation') { - // ignore: use_build_context_synchronously + if (!context.mounted) return; _scrollToFirstError(context.read().state); } else if (result == 'error') { if (!context.mounted) return; From 5b149e2fd2eecc53463098a12bb50c7c2df04e06 Mon Sep 17 00:00:00 2001 From: mso7nueh Date: Mon, 16 Feb 2026 18:16:26 +0300 Subject: [PATCH 19/58] refactor: Corrected typo in `MyRecipesCubit` filename and updated related tests, widgets, services, and DI registrations. --- lib/core/di/cubit_registrations.dart | 2 +- .../services/recipe_service.dart | 1 - ...cieps_cubit.dart => my_recipes_cubit.dart} | 0 .../presentation/pages/my_recipes_screen.dart | 2 +- .../widgets/my_recipe_widget.dart | 4 +- temp_analyze_output.txt | 2 + temp_test_output.txt | 92 +++ temp_test_output_recipes.txt | 1 + .../create_recipe_cubit_test.dart | 1 + .../reporitories/recipe_repository_test.dart | 6 +- .../services/recipe_service_test.dart | 6 +- .../my_recipes_cubit_test.dart | 2 +- .../pages/my_recipes_screen_smoke_test.dart | 16 +- .../widgets/my_recipe_widget_test.dart | 2 +- .../cubit/my_recipes_cubit_test.dart | 2 +- test_failures.txt | 57 ++ test_results.txt | 663 ++++++++++++++++++ 17 files changed, 845 insertions(+), 14 deletions(-) rename lib/features/recipe/presentation/cubit/my_recipes_cubit/{my_recieps_cubit.dart => my_recipes_cubit.dart} (100%) create mode 100644 temp_analyze_output.txt create mode 100644 temp_test_output.txt create mode 100644 temp_test_output_recipes.txt create mode 100644 test_failures.txt create mode 100644 test_results.txt diff --git a/lib/core/di/cubit_registrations.dart b/lib/core/di/cubit_registrations.dart index 7b12e0fb..ad4ce637 100644 --- a/lib/core/di/cubit_registrations.dart +++ b/lib/core/di/cubit_registrations.dart @@ -45,7 +45,7 @@ import 'package:eatvio_app/features/profile/presentation/bloc/plans_cubit/plans_ import 'package:eatvio_app/features/profile/presentation/bloc/profile_cubit/profile_cubit.dart'; import 'package:eatvio_app/features/profile/presentation/bloc/sub_cubit/sub_cubit.dart'; import 'package:eatvio_app/features/recipe/presentation/cubit/about_recipe_cubit.dart'; -import 'package:eatvio_app/features/recipe/presentation/cubit/my_recipes_cubit/my_recieps_cubit.dart'; +import 'package:eatvio_app/features/recipe/presentation/cubit/my_recipes_cubit/my_recipes_cubit.dart'; import 'package:eatvio_app/features/registration/presentation/bloc/registration_cubit/registration_cubit.dart'; import 'package:eatvio_app/features/registration/presentation/bloc/verify_email_cubit/verify_email_cubit.dart'; import 'package:eatvio_app/features/registration_v2/cubit/registration_cubit.dart'; diff --git a/lib/features/create_recipe/services/recipe_service.dart b/lib/features/create_recipe/services/recipe_service.dart index 199c7d54..2341d1d2 100644 --- a/lib/features/create_recipe/services/recipe_service.dart +++ b/lib/features/create_recipe/services/recipe_service.dart @@ -2,7 +2,6 @@ import 'package:dio/dio.dart'; import 'package:eatvio_app/features/create_recipe/models/recipe_model.dart'; import 'package:eatvio_app/features/create_recipe/models/recipe_tag_model.dart'; import 'package:retrofit/retrofit.dart'; -import 'package:retrofit/http.dart'; import 'package:eatvio_app/features/create_recipe/models/product.dart'; part 'recipe_service.g.dart'; diff --git a/lib/features/recipe/presentation/cubit/my_recipes_cubit/my_recieps_cubit.dart b/lib/features/recipe/presentation/cubit/my_recipes_cubit/my_recipes_cubit.dart similarity index 100% rename from lib/features/recipe/presentation/cubit/my_recipes_cubit/my_recieps_cubit.dart rename to lib/features/recipe/presentation/cubit/my_recipes_cubit/my_recipes_cubit.dart diff --git a/lib/features/recipe/presentation/pages/my_recipes_screen.dart b/lib/features/recipe/presentation/pages/my_recipes_screen.dart index 1f276cf6..4c409ea7 100644 --- a/lib/features/recipe/presentation/pages/my_recipes_screen.dart +++ b/lib/features/recipe/presentation/pages/my_recipes_screen.dart @@ -1,7 +1,7 @@ import 'package:eatvio_app/core/locator_service.dart'; import 'package:eatvio_app/core/theme/custom_text_style.dart'; import 'package:eatvio_app/core/theme/theme_helper.dart'; -import 'package:eatvio_app/features/recipe/presentation/cubit/my_recipes_cubit/my_recieps_cubit.dart'; +import 'package:eatvio_app/features/recipe/presentation/cubit/my_recipes_cubit/my_recipes_cubit.dart'; import 'package:eatvio_app/features/recipe/presentation/cubit/my_recipes_cubit/my_recipes_state.dart'; import 'package:eatvio_app/features/recipe/presentation/widgets/my_recipe_widget.dart'; import 'package:eatvio_app/features/recipe/presentation/widgets/my_recipes_tab.dart'; diff --git a/lib/features/recipe/presentation/widgets/my_recipe_widget.dart b/lib/features/recipe/presentation/widgets/my_recipe_widget.dart index a47b6161..941971fa 100644 --- a/lib/features/recipe/presentation/widgets/my_recipe_widget.dart +++ b/lib/features/recipe/presentation/widgets/my_recipe_widget.dart @@ -8,7 +8,7 @@ import 'package:eatvio_app/core/utils/toasts.dart'; import 'package:eatvio_app/core/widgets/custom_outlined_button.dart'; import 'package:eatvio_app/features/feed/presentation/bloc/recipes_cubit/recipes_cubit.dart'; import 'package:eatvio_app/features/recipe/models/my_recipe_model.dart'; -import 'package:eatvio_app/features/recipe/presentation/cubit/my_recipes_cubit/my_recieps_cubit.dart'; +import 'package:eatvio_app/features/recipe/presentation/cubit/my_recipes_cubit/my_recipes_cubit.dart'; import 'package:eatvio_app/generated/l10n.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -207,7 +207,7 @@ class _MyRecipeWidgetState extends State { onPressed: () { // Publish draft context.read().publishRecipe(true, widget.recipe.slug).then((success) { - if (success) { + if (success && context.mounted) { context.read().setDraftedState(); } }); diff --git a/temp_analyze_output.txt b/temp_analyze_output.txt new file mode 100644 index 00000000..693739d0 --- /dev/null +++ b/temp_analyze_output.txt @@ -0,0 +1,2 @@ +Analyzing create_recipe, recipe, create_recipe, recipe... +No issues found! diff --git a/temp_test_output.txt b/temp_test_output.txt new file mode 100644 index 00000000..a39d9c57 --- /dev/null +++ b/temp_test_output.txt @@ -0,0 +1,92 @@ + 00:00 +0: loading /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart 00:01 +0: loading /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart 00:02 +0: loading /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart 00:03 +0: loading /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart 00:04 +0: loading /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart 00:05 +0: loading /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart 00:05 +0: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should create CreateRecipeCubit successfully 00:05 +1: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should create CreateRecipeCubit successfully 00:05 +2: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should create CreateRecipeCubit successfully 00:05 +3: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should create CreateRecipeCubit successfully 00:05 +4: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should create CreateRecipeCubit successfully 00:05 +5: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should create CreateRecipeCubit successfully 00:05 +6: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should create CreateRecipeCubit successfully 00:05 +7: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should create CreateRecipeCubit successfully 00:05 +8: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should create CreateRecipeCubit successfully 00:05 +9: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should create CreateRecipeCubit successfully 00:05 +10: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should create CreateRecipeCubit successfully 00:05 +11: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should create CreateRecipeCubit successfully 00:05 +12: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should create CreateRecipeCubit successfully 00:05 +13: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should create CreateRecipeCubit successfully 00:05 +14: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should create CreateRecipeCubit successfully 00:05 +14: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display basic form fields 00:05 +15: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display basic form fields 00:05 +16: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display basic form fields 00:05 +17: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display basic form fields 00:06 +17: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display basic form fields 00:06 +18: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display basic form fields 00:06 +19: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display basic form fields 00:06 +20: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display basic form fields 00:06 +21: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display basic form fields 00:06 +21: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should update title when user types in title field 00:06 +22: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should update title when user types in title field 00:06 +23: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should update title when user types in title field 00:06 +24: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should update title when user types in title field 00:06 +25: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should update title when user types in title field 00:06 +26: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should update title when user types in title field 00:06 +26: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should update description when user types in description field 00:06 +27: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should update description when user types in description field 00:06 +27: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should update portions when user types in portions field 00:06 +28: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should update portions when user types in portions field 00:06 +29: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should update portions when user types in portions field 00:06 +30: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should update portions when user types in portions field 00:06 +31: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should update portions when user types in portions field 00:06 +32: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should update portions when user types in portions field 00:06 +33: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should update portions when user types in portions field 00:06 +33: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should update calories when user types in calories field 00:07 +33: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should update calories when user types in calories field 00:07 +34: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should update calories when user types in calories field 00:07 +34: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display form labels correctly 00:07 +35: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display form labels correctly 00:07 +35: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display placeholder text in title field 00:07 +36: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display placeholder text in title field 00:07 +36: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display publish button 00:07 +37: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display publish button 00:07 +37: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should handle button tap 00:07 +38: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should handle button tap 00:07 +38: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display loading state 00:07 +39: /home/alexander/eatvio_app/test/features/create_recipe/models/recipe_tag_model_test.dart: RecipeTag создает RecipeTag с правильными значениями 00:07 +40: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display error message 00:07 +41: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display error message 00:07 +42: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display error message 00:07 +43: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display error message 00:07 +44: /home/alexander/eatvio_app/test/features/create_recipe/models/recipe_tag_model_test.dart: RecipeTag toJson serializes RecipeTag correctly 00:07 +45: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display success message 00:07 +46: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display success message 00:07 +47: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display success message 00:07 +47: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should validate required fields 00:07 +48: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should validate required fields 00:07 +48: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display form with all required fields 00:07 +49: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display form with all required fields 00:07 +49: loading /home/alexander/eatvio_app/test/features/create_recipe/models/step_test.dart 00:07 +49: /home/alexander/eatvio_app/test/features/create_recipe/models/step_test.dart: Step создает Step с правильными значениями 00:07 +50: /home/alexander/eatvio_app/test/features/create_recipe/models/step_test.dart: Step создает Step с правильными значениями 00:07 +50: /home/alexander/eatvio_app/test/features/create_recipe/models/step_test.dart: Step создает Step с null значениями 00:07 +51: /home/alexander/eatvio_app/test/features/create_recipe/models/step_test.dart: Step создает Step с null значениями 00:07 +51: /home/alexander/eatvio_app/test/features/create_recipe/models/step_test.dart: Step fromJson создает Step из JSON 00:07 +52: /home/alexander/eatvio_app/test/features/create_recipe/models/step_test.dart: Step fromJson создает Step из JSON 00:07 +52: /home/alexander/eatvio_app/test/features/create_recipe/models/step_test.dart: Step fromJson создает Step из JSON с null значениями 00:07 +53: /home/alexander/eatvio_app/test/features/create_recipe/models/step_test.dart: Step fromJson создает Step из JSON с null значениями 00:07 +53: /home/alexander/eatvio_app/test/features/create_recipe/models/step_test.dart: Step toJson serializes Step correctly 00:07 +54: /home/alexander/eatvio_app/test/features/create_recipe/models/step_test.dart: Step toJson serializes Step correctly 00:07 +54: /home/alexander/eatvio_app/test/features/create_recipe/models/step_test.dart: Step toJson serializes Step with null values correctly 00:07 +55: /home/alexander/eatvio_app/test/features/create_recipe/models/step_test.dart: Step toJson serializes Step with null values correctly 00:08 +55: /home/alexander/eatvio_app/test/features/create_recipe/models/step_test.dart: Step toJson serializes Step with null values correctly 00:08 +55: loading /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart 00:08 +55: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit initial state has default values 00:08 +56: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit initial state has default values 00:08 +56: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit addStep adds a new empty step 00:08 +57: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit addStep adds a new empty step 00:08 +57: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit removeStep removes provided step 00:08 +58: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit removeStep removes provided step 00:08 +58: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit addIngredient appends ingredient to list 00:08 +59: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit addIngredient appends ingredient to list 00:08 +59: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit removeIngredient removes ingredient by index 00:08 +60: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit removeIngredient removes ingredient by index 00:08 +60: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit updateIngredient replaces ingredient at index 00:08 +61: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit updateIngredient replaces ingredient at index 00:08 +61: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit replaceIngredientPosition moves ingredient 00:08 +62: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit replaceIngredientPosition moves ingredient 00:08 +62: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... replaceTitle and replaceDescription update entity 00:08 +63: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... replaceTitle and replaceDescription update entity 00:08 +63: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit addTag and removeTag work correctly 00:08 +64: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit addTag and removeTag work correctly 00:08 +64: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit portion and energy setters update entity 00:08 +65: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit portion and energy setters update entity 00:08 +65: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit replaceVideoUrl and replaceImage update entity 00:08 +66: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit replaceVideoUrl and replaceImage update entity 00:08 +66: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... replaceDescriptionInStep and replaceImageInStep update step 00:08 +67: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... replaceDescriptionInStep and replaceImageInStep update step 00:08 +67: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit disableLoading sets loading to false 00:08 +68: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit disableLoading sets loading to false 00:08 +68: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit changeType updates type of recipe 00:08 +69: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit changeType updates type of recipe 00:08 +69: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... replaceTitle with empty string sets title to null 00:08 +70: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... replaceTitle with empty string sets title to null 00:08 +70: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... with empty string sets description to null 00:08 +71: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... with empty string sets description to null 00:08 +71: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit publishRecipe - успешно публикует рецепт 00:08 +72: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit publishRecipe - успешно публикует рецепт 00:08 +72: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit publishRecipe - обновляет рецепт 00:08 +73: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit publishRecipe - обновляет рецепт 00:08 +73: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit publishRecipe - обрабатывает ошибку 00:08 +74: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit publishRecipe - обрабатывает ошибку 00:08 +74: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit deleteRecipe - успешно удаляет рецепт 00:08 +75: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit deleteRecipe - успешно удаляет рецепт 00:08 +75: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit deleteRecipe - обрабатывает ошибку 00:08 +76: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit deleteRecipe - обрабатывает ошибку 00:08 +76: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... getGlossaryListByQuery - получает список глоссария 00:08 +77: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... getGlossaryListByQuery - получает список глоссария 00:08 +77: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit validateTitle - возвращает false для null 00:08 +78: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit validateTitle - возвращает false для null 00:08 +78: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... validateTitle - возвращает false для пустой строки 00:08 +79: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... validateTitle - возвращает false для пустой строки 00:08 +79: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... validateTitle - возвращает false для короткого названия 00:08 +80: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... validateTitle - возвращает false для короткого названия 00:08 +80: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... validateTitle - возвращает true для валидного названия 00:08 +81: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... validateTitle - возвращает true для валидного названия 00:08 +81: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... validateDescription - возвращает false для null 00:08 +82: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... validateDescription - возвращает false для null 00:08 +82: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... validateDescription - возвращает false для пустой строки 00:08 +83: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... validateDescription - возвращает false для пустой строки 00:08 +83: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... - возвращает false для короткого описания 00:08 +84: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... - возвращает false для короткого описания 00:08 +84: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... validateDescription - возвращает true для валидного описания 00:08 +85: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... validateDescription - возвращает true для валидного описания 00:08 +85: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit validateServings - возвращает false для null 00:08 +86: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit validateServings - возвращает false для null 00:08 +86: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... validateServings - возвращает true для валидного значения 00:08 +87: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... validateServings - возвращает true для валидного значения 00:08 +87: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... validateEnergyPerServing - возвращает false для null 00:08 +88: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... validateEnergyPerServing - возвращает false для null 00:08 +88: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... - возвращает true для валидного значения 00:08 +89: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... - возвращает true для валидного значения 00:08 +89: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... - возвращает false когда оба поля null 00:08 +90: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... - возвращает false когда оба поля null 00:08 +90: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... - возвращает true когда хотя бы одно поле заполнено 00:08 +91: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... - возвращает true когда хотя бы одно поле заполнено 00:08 +91: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... - возвращает false когда одно из полей null 00:08 +92: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... - возвращает false когда одно из полей null 00:08 +92: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... - возвращает true когда оба поля заполнены 00:08 +93: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... - возвращает true когда оба поля заполнены 00:08 +93: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit validateImage - возвращает false для null 00:08 +94: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit validateImage - возвращает false для null 00:08 +94: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... validateImage - возвращает true для валидного изображения 00:08 +95: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... validateImage - возвращает true для валидного изображения 00:08 +95: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... validateAll - возвращает false когда не все поля валидны 00:08 +96: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... validateAll - возвращает false когда не все поля валидны 00:08 +96: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... validateAll - возвращает true когда все поля валидны 00:08 +97: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... validateAll - возвращает true когда все поля валидны 00:08 +97: loading /home/alexander/eatvio_app/test/features/create_recipe/presentation/pages/recipe_create_screen_test.dart 00:08 +97: /home/alexander/eatvio_app/test/features/create_recipe/presentation/pages/recipe_create_screen_test.dart: RecipeCreateScreen smoke (setUpAll) 00:09 +97: /home/alexander/eatvio_app/test/features/create_recipe/presentation/pages/recipe_create_screen_test.dart: RecipeCreateScreen smoke (setUpAll) 00:09 +97: /home/alexander/eatvio_app/test/features/create_recipe/presentation/pages/recipe_create_screen_test.dart: RecipeCreateScreen smoke renders recipe create screen - initial state 00:10 +97: /home/alexander/eatvio_app/test/features/create_recipe/presentation/pages/recipe_create_screen_test.dart: RecipeCreateScreen smoke renders recipe create screen - initial state 00:10 +98: /home/alexander/eatvio_app/test/features/create_recipe/presentation/pages/recipe_create_screen_test.dart: RecipeCreateScreen smoke renders recipe create screen - initial state 00:10 +99: /home/alexander/eatvio_app/test/features/create_recipe/presentation/pages/recipe_create_screen_test.dart: RecipeCreateScreen smoke renders recipe create screen - initial state 00:10 +100: /home/alexander/eatvio_app/test/features/create_recipe/presentation/pages/recipe_create_screen_test.dart: RecipeCreateScreen smoke renders recipe create screen - initial state 00:10 +101: /home/alexander/eatvio_app/test/features/create_recipe/presentation/pages/recipe_create_screen_test.dart: RecipeCreateScreen smoke renders recipe create screen - initial state 00:10 +102: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/info_about_recipe_widget_test.dart: InfoAboutRecipeWidget smoke renders info about recipe widget 00:10 +103: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/info_about_recipe_widget_test.dart: InfoAboutRecipeWidget smoke renders info about recipe widget 00:11 +103: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/info_about_recipe_widget_test.dart: InfoAboutRecipeWidget smoke renders info about recipe widget 00:11 +104: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/info_about_recipe_widget_test.dart: InfoAboutRecipeWidget smoke renders info about recipe widget 00:11 +105: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/info_about_recipe_widget_test.dart: InfoAboutRecipeWidget smoke renders info about recipe widget 00:11 +106: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/info_about_recipe_widget_test.dart: InfoAboutRecipeWidget smoke renders info about recipe widget 00:11 +107: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/portion_widget_test.dart: PortionWidget smoke renders portion widget 00:11 +108: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:12 +108: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:12 +109: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:12 +110: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:12 +111: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:12 +112: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:12 +113: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:12 +114: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:12 +115: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:12 +116: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:12 +117: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:12 +118: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:12 +119: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:12 +120: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:12 +121: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:12 +122: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:12 +123: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:12 +124: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:12 +125: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:12 +126: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:12 +127: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:12 +128: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:12 +129: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:12 +130: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:12 +131: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:12 +132: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:12 +133: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/time_of_cook_widget_test.dart: TimeOfCookWidget smoke renders time of cook widget 00:12 +134: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/time_of_cook_widget_test.dart: TimeOfCookWidget smoke renders time of cook widget 00:12 +135: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/time_of_cook_widget_test.dart: TimeOfCookWidget smoke renders time of cook widget 00:12 +136: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/time_of_cook_widget_test.dart: TimeOfCookWidget smoke renders time of cook widget 00:12 +137: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/time_of_cook_widget_test.dart: TimeOfCookWidget smoke renders time of cook widget 00:12 +138: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/time_of_cook_widget_test.dart: TimeOfCookWidget smoke renders time of cook widget 00:12 +138: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/time_of_cook_widget_test.dart: TimeOfCookWidget smoke (tearDownAll) 00:13 +138: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/time_of_cook_widget_test.dart: TimeOfCookWidget smoke (tearDownAll) 00:13 +138: loading /home/alexander/eatvio_app/test/features/create_recipe/simple_widget_test.dart 00:13 +138: /home/alexander/eatvio_app/test/features/create_recipe/simple_widget_test.dart: Recipe Creation Widget Tests should display InfoAboutRecipeWidget with all form fields 00:14 +138: /home/alexander/eatvio_app/test/features/create_recipe/simple_widget_test.dart: Recipe Creation Widget Tests should display InfoAboutRecipeWidget with all form fields 00:14 +139: /home/alexander/eatvio_app/test/features/create_recipe/simple_widget_test.dart: Recipe Creation Widget Tests should display InfoAboutRecipeWidget with all form fields 00:14 +140: /home/alexander/eatvio_app/test/features/create_recipe/simple_widget_test.dart: Recipe Creation Widget Tests should display InfoAboutRecipeWidget with all form fields 00:14 +141: /home/alexander/eatvio_app/test/features/create_recipe/simple_widget_test.dart: Recipe Creation Widget Tests should display InfoAboutRecipeWidget with all form fields 00:14 +142: /home/alexander/eatvio_app/test/features/create_recipe/simple_widget_test.dart: Recipe Creation Widget Tests should display InfoAboutRecipeWidget with all form fields 00:14 +143: /home/alexander/eatvio_app/test/features/create_recipe/simple_widget_test.dart: Recipe Creation Widget Tests should display InfoAboutRecipeWidget with all form fields 00:14 +144: /home/alexander/eatvio_app/test/features/create_recipe/simple_widget_test.dart: Recipe Creation Widget Tests should display InfoAboutRecipeWidget with all form fields 00:14 +145: /home/alexander/eatvio_app/test/features/create_recipe/simple_widget_test.dart: Recipe Creation Widget Tests should display InfoAboutRecipeWidget with all form fields 00:14 +146: /home/alexander/eatvio_app/test/features/create_recipe/simple_widget_test.dart: Recipe Creation Widget Tests should display InfoAboutRecipeWidget with all form fields 00:14 +147: /home/alexander/eatvio_app/test/features/create_recipe/widget_test.dart: RecipeCreateScreen Widget Tests should display all required form fields 00:14 +148: /home/alexander/eatvio_app/test/features/create_recipe/widget_test.dart: RecipeCreateScreen Widget Tests should display all required form fields 00:14 +149: /home/alexander/eatvio_app/test/features/create_recipe/widget_test.dart: RecipeCreateScreen Widget Tests should display all required form fields 00:14 +150: /home/alexander/eatvio_app/test/features/create_recipe/widget_test.dart: RecipeCreateScreen Widget Tests should display all required form fields 00:14 +151: /home/alexander/eatvio_app/test/features/create_recipe/widget_test.dart: RecipeCreateScreen Widget Tests should display all required form fields 00:14 +152: /home/alexander/eatvio_app/test/features/create_recipe/widget_test.dart: RecipeCreateScreen Widget Tests should display all required form fields 00:15 +152: /home/alexander/eatvio_app/test/features/create_recipe/widget_test.dart: RecipeCreateScreen Widget Tests should display all required form fields 00:15 +153: /home/alexander/eatvio_app/test/features/create_recipe/widget_test.dart: RecipeCreateScreen Widget Tests should display all required form fields 00:15 +154: /home/alexander/eatvio_app/test/features/create_recipe/widget_test.dart: RecipeCreateScreen Widget Tests should display all required form fields 00:15 +155: /home/alexander/eatvio_app/test/features/create_recipe/widget_test.dart: RecipeCreateScreen Widget Tests should display all required form fields 00:15 +156: /home/alexander/eatvio_app/test/features/create_recipe/widget_test.dart: RecipeCreateScreen Widget Tests should display all required form fields 00:15 +157: /home/alexander/eatvio_app/test/features/create_recipe/widget_test.dart: RecipeCreateScreen Widget Tests should display all required form fields 00:15 +158: /home/alexander/eatvio_app/test/features/create_recipe/widget_test.dart: RecipeCreateScreen Widget Tests should display all required form fields 00:15 +159: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - лайк рецепта 00:15 +160: /home/alexander/eatvio_app/test/features/create_recipe/simple_widget_test.dart: Recipe Creation Widget Tests should update calories when user types in calories field 00:15 +161: /home/alexander/eatvio_app/test/features/recipe/presentation/pages/about_recipe_screen_smoke_test.dart: AboutRecipeScreen smoke renders loading state 00:15 +162: /home/alexander/eatvio_app/test/features/recipe/presentation/pages/about_recipe_screen_smoke_test.dart: AboutRecipeScreen smoke renders loading state 00:15 +163: /home/alexander/eatvio_app/test/features/recipe/presentation/pages/about_recipe_screen_smoke_test.dart: AboutRecipeScreen smoke renders loading state 00:16 +163: /home/alexander/eatvio_app/test/features/recipe/presentation/pages/about_recipe_screen_smoke_test.dart: AboutRecipeScreen smoke renders loading state 00:16 +164: /home/alexander/eatvio_app/test/features/recipe/presentation/pages/about_recipe_screen_smoke_test.dart: AboutRecipeScreen smoke renders loading state 00:16 +165: /home/alexander/eatvio_app/test/features/recipe/presentation/pages/about_recipe_screen_smoke_test.dart: AboutRecipeScreen smoke renders loading state 00:16 +166: /home/alexander/eatvio_app/test/features/recipe/presentation/pages/about_recipe_screen_smoke_test.dart: AboutRecipeScreen smoke renders loading state 00:16 +167: /home/alexander/eatvio_app/test/features/recipe/presentation/pages/about_recipe_screen_smoke_test.dart: AboutRecipeScreen smoke renders loading state 00:16 +168: /home/alexander/eatvio_app/test/features/recipe/presentation/pages/about_recipe_screen_smoke_test.dart: AboutRecipeScreen smoke renders loading state 00:16 +169: /home/alexander/eatvio_app/test/features/recipe/presentation/pages/about_recipe_screen_smoke_test.dart: AboutRecipeScreen smoke renders loading state 00:16 +170: /home/alexander/eatvio_app/test/features/recipe/presentation/pages/about_recipe_screen_smoke_test.dart: AboutRecipeScreen smoke renders loading state 00:16 +171: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - убирает лайк если уже лайкнуто 00:16 +172: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - убирает лайк если уже лайкнуто 00:16 +173: /home/alexander/eatvio_app/test/features/recipe/presentation/pages/my_recipes_screen_smoke_test.dart: MyRecipesScreen smoke renders my recipes screen 00:17 +173: /home/alexander/eatvio_app/test/features/recipe/presentation/pages/my_recipes_screen_smoke_test.dart: MyRecipesScreen smoke renders my recipes screen 00:17 +174: /home/alexander/eatvio_app/test/features/recipe/presentation/pages/my_recipes_screen_smoke_test.dart: MyRecipesScreen smoke renders my recipes screen 00:17 +175: /home/alexander/eatvio_app/test/features/recipe/presentation/pages/my_recipes_screen_smoke_test.dart: MyRecipesScreen smoke renders my recipes screen 00:17 +176: /home/alexander/eatvio_app/test/features/recipe/presentation/pages/my_recipes_screen_smoke_test.dart: MyRecipesScreen smoke renders my recipes screen 00:17 +177: /home/alexander/eatvio_app/test/features/recipe/presentation/pages/my_recipes_screen_smoke_test.dart: MyRecipesScreen smoke renders my recipes screen 00:17 +178: /home/alexander/eatvio_app/test/features/recipe/presentation/pages/my_recipes_screen_smoke_test.dart: MyRecipesScreen smoke renders my recipes screen 00:17 +179: /home/alexander/eatvio_app/test/features/recipe/presentation/pages/my_recipes_screen_smoke_test.dart: MyRecipesScreen smoke renders my recipes screen 00:17 +180: /home/alexander/eatvio_app/test/features/recipe/presentation/pages/my_recipes_screen_smoke_test.dart: MyRecipesScreen smoke renders my recipes screen 00:17 +181: /home/alexander/eatvio_app/test/features/recipe/presentation/pages/my_recipes_screen_smoke_test.dart: MyRecipesScreen smoke renders my recipes screen 00:17 +182: /home/alexander/eatvio_app/test/features/recipe/presentation/widgets/ingredients_widget_test.dart: IngredientsWidget smoke renders ingredients widget 00:17 +183: /home/alexander/eatvio_app/test/features/recipe/presentation/widgets/ingredients_widget_test.dart: IngredientsWidget smoke renders ingredients widget 00:17 +184: /home/alexander/eatvio_app/test/features/recipe/presentation/widgets/ingredients_widget_test.dart: IngredientsWidget smoke renders ingredients widget 00:17 +185: /home/alexander/eatvio_app/test/features/recipe/presentation/widgets/ingredients_widget_test.dart: IngredientsWidget smoke renders ingredients widget 00:17 +186: /home/alexander/eatvio_app/test/features/recipe/presentation/widgets/ingredients_widget_test.dart: IngredientsWidget smoke renders ingredients widget 00:18 +186: /home/alexander/eatvio_app/test/features/recipe/presentation/widgets/ingredients_widget_test.dart: IngredientsWidget smoke renders ingredients widget 00:18 +187: /home/alexander/eatvio_app/test/features/recipe/presentation/widgets/ingredients_widget_test.dart: IngredientsWidget smoke renders ingredients widget 00:18 +188: /home/alexander/eatvio_app/test/features/recipe/presentation/widgets/my_recipe_widget_test.dart: MyRecipeWidget smoke renders my recipe widget 00:18 +189: /home/alexander/eatvio_app/test/features/recipe/presentation/widgets/my_recipe_widget_test.dart: MyRecipeWidget smoke renders my recipe widget 00:18 +190: /home/alexander/eatvio_app/test/features/recipe/presentation/widgets/my_recipe_widget_test.dart: MyRecipeWidget smoke renders my recipe widget 00:18 +191: /home/alexander/eatvio_app/test/features/recipe/presentation/widgets/my_recipe_widget_test.dart: MyRecipeWidget smoke renders my recipe widget 00:18 +192: /home/alexander/eatvio_app/test/features/recipe/presentation/widgets/my_recipe_widget_test.dart: MyRecipeWidget smoke renders my recipe widget 00:18 +193: /home/alexander/eatvio_app/test/features/recipe/presentation/widgets/my_recipe_widget_test.dart: MyRecipeWidget smoke renders my recipe widget 00:18 +194: /home/alexander/eatvio_app/test/features/recipe/presentation/widgets/my_recipe_widget_test.dart: MyRecipeWidget smoke renders my recipe widget 00:18 +195: /home/alexander/eatvio_app/test/features/recipe/presentation/widgets/my_recipe_widget_test.dart: MyRecipeWidget smoke renders my recipe widget 00:18 +196: /home/alexander/eatvio_app/test/features/recipe/presentation/widgets/my_recipe_widget_test.dart: MyRecipeWidget smoke renders my recipe widget 00:18 +197: /home/alexander/eatvio_app/test/features/recipe/presentation/widgets/my_recipe_widget_test.dart: MyRecipeWidget smoke renders my recipe widget 00:18 +198: /home/alexander/eatvio_app/test/features/recipe/presentation/widgets/my_recipe_widget_test.dart: MyRecipeWidget smoke renders my recipe widget 00:18 +199: /home/alexander/eatvio_app/test/features/recipe/presentation/widgets/my_recipe_widget_test.dart: MyRecipeWidget smoke renders my recipe widget 00:18 +200: /home/alexander/eatvio_app/test/features/recipe/presentation/widgets/my_recipe_widget_test.dart: MyRecipeWidget smoke renders my recipe widget 00:18 +201: /home/alexander/eatvio_app/test/features/recipe/presentation/widgets/my_recipe_widget_test.dart: MyRecipeWidget smoke renders my recipe widget 00:18 +202: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - удаляет рецепт из избранного 00:19 +202: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - удаляет рецепт из избранного 00:19 +203: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - удаляет рецепт из избранного 00:19 +204: /home/alexander/eatvio_app/test/features/create_recipe/widget_test.dart: RecipeCreateScreen Widget Tests should show validation error for invalid portions 00:19 +205: /home/alexander/eatvio_app/test/features/create_recipe/widget_test.dart: RecipeCreateScreen Widget Tests should show validation error for invalid portions 00:19 +206: /home/alexander/eatvio_app/test/features/create_recipe/widget_test.dart: RecipeCreateScreen Widget Tests should show validation error for invalid portions 00:19 +207: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - с recipeIndexInFeed параметром 00:19 +208: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - с recipeIndexInFeed параметром 00:19 +209: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - с recipeIndexInFeed параметром 00:19 +210: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - с recipeIndexInFeed параметром 00:20 +210: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - с recipeIndexInFeed параметром 00:20 +211: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - с recipeIndexInFeed параметром 00:20 +212: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - с recipeIndexInFeed параметром 00:20 +213: /home/alexander/eatvio_app/test/features/create_recipe/widget_test.dart: RecipeCreateScreen Widget Tests should display app bar with correct title for new recipe 00:20 +214: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:20 +215: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:21 +215: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:21 +216: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:21 +216: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:22 +216: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:23 +216: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:23 +217: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:23 +217: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - обработка ошибки 00:23 +218: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - обработка ошибки 00:23 +218: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - с recipeIndex параметром 00:24 +218: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - с recipeIndex параметром 00:24 +219: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - с recipeIndex параметром 00:24 +219: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - обработка ошибки 00:24 +220: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - обработка ошибки 00:24 +220: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - не выполняет действие если state не loaded 00:24 +221: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - не выполняет действие если state не loaded 00:24 +221: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - не выполняет действие если state не loaded 00:24 +222: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - не выполняет действие если state не loaded 00:24 +222: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - не выполняет действие если state не loaded 00:24 +223: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - не выполняет действие если state не loaded 00:24 +223: All tests passed! + 00:00 +0: loading /home/alexander/eatvio_app/test/features/recipes/presentation/cubit/my_recipes_cubit_test.dart 00:01 +0: loading /home/alexander/eatvio_app/test/features/recipes/presentation/cubit/my_recipes_cubit_test.dart 00:02 +0: loading /home/alexander/eatvio_app/test/features/recipes/presentation/cubit/my_recipes_cubit_test.dart test/features/recipes/presentation/cubit/my_recipes_cubit_test.dart:4:8: Error: Error when reading 'lib/features/recipe/presentation/cubit/my_recipes_cubit/my_recieps_cubit.dart': No such file or directory +import 'package:eatvio_app/features/recipe/presentation/cubit/my_recipes_cubit/my_recieps_cubit.dart'; + ^ +test/features/recipes/presentation/cubit/my_recipes_cubit_test.dart:19:5: Error: 'MyRecipesCubit' isn't a type. + MyRecipesCubit? myRecipesCubit; + ^^^^^^^^^^^^^^ +test/features/recipes/presentation/cubit/my_recipes_cubit_test.dart:99:24: Error: Method not found: 'MyRecipesCubit'. + myRecipesCubit = MyRecipesCubit( + ^^^^^^^^^^^^^^ +test/features/recipes/presentation/cubit/my_recipes_cubit_test.dart:107:24: Error: Method not found: 'MyRecipesCubit'. + myRecipesCubit = MyRecipesCubit( + ^^^^^^^^^^^^^^ +test/features/recipes/presentation/cubit/my_recipes_cubit_test.dart:127:24: Error: Method not found: 'MyRecipesCubit'. + myRecipesCubit = MyRecipesCubit( + ^^^^^^^^^^^^^^ +test/features/recipes/presentation/cubit/my_recipes_cubit_test.dart:141:24: Error: Method not found: 'MyRecipesCubit'. + myRecipesCubit = MyRecipesCubit( + ^^^^^^^^^^^^^^ +test/features/recipes/presentation/cubit/my_recipes_cubit_test.dart:161:24: Error: Method not found: 'MyRecipesCubit'. + myRecipesCubit = MyRecipesCubit( + ^^^^^^^^^^^^^^ +test/features/recipes/presentation/cubit/my_recipes_cubit_test.dart:175:24: Error: Method not found: 'MyRecipesCubit'. + myRecipesCubit = MyRecipesCubit( + ^^^^^^^^^^^^^^ +test/features/recipes/presentation/cubit/my_recipes_cubit_test.dart:195:24: Error: Method not found: 'MyRecipesCubit'. + myRecipesCubit = MyRecipesCubit( + ^^^^^^^^^^^^^^ +test/features/recipes/presentation/cubit/my_recipes_cubit_test.dart:209:24: Error: Method not found: 'MyRecipesCubit'. + myRecipesCubit = MyRecipesCubit( + ^^^^^^^^^^^^^^ +test/features/recipes/presentation/cubit/my_recipes_cubit_test.dart:225:24: Error: Method not found: 'MyRecipesCubit'. + myRecipesCubit = MyRecipesCubit( + ^^^^^^^^^^^^^^ + 00:03 +0: loading /home/alexander/eatvio_app/test/features/recipes/presentation/cubit/my_recipes_cubit_test.dart 00:03 +0 -1: loading /home/alexander/eatvio_app/test/features/recipes/presentation/cubit/my_recipes_cubit_test.dart [E] + Failed to load "/home/alexander/eatvio_app/test/features/recipes/presentation/cubit/my_recipes_cubit_test.dart": + Compilation failed for testPath=/home/alexander/eatvio_app/test/features/recipes/presentation/cubit/my_recipes_cubit_test.dart: test/features/recipes/presentation/cubit/my_recipes_cubit_test.dart:4:8: Error: Error when reading 'lib/features/recipe/presentation/cubit/my_recipes_cubit/my_recieps_cubit.dart': No such file or directory + import 'package:eatvio_app/features/recipe/presentation/cubit/my_recipes_cubit/my_recieps_cubit.dart'; + ^ + test/features/recipes/presentation/cubit/my_recipes_cubit_test.dart:19:5: Error: 'MyRecipesCubit' isn't a type. + MyRecipesCubit? myRecipesCubit; + ^^^^^^^^^^^^^^ + test/features/recipes/presentation/cubit/my_recipes_cubit_test.dart:99:24: Error: Method not found: 'MyRecipesCubit'. + myRecipesCubit = MyRecipesCubit( + ^^^^^^^^^^^^^^ + test/features/recipes/presentation/cubit/my_recipes_cubit_test.dart:107:24: Error: Method not found: 'MyRecipesCubit'. + myRecipesCubit = MyRecipesCubit( + ^^^^^^^^^^^^^^ + test/features/recipes/presentation/cubit/my_recipes_cubit_test.dart:127:24: Error: Method not found: 'MyRecipesCubit'. + myRecipesCubit = MyRecipesCubit( + ^^^^^^^^^^^^^^ + test/features/recipes/presentation/cubit/my_recipes_cubit_test.dart:141:24: Error: Method not found: 'MyRecipesCubit'. + myRecipesCubit = MyRecipesCubit( + ^^^^^^^^^^^^^^ + test/features/recipes/presentation/cubit/my_recipes_cubit_test.dart:161:24: Error: Method not found: 'MyRecipesCubit'. + myRecipesCubit = MyRecipesCubit( + ^^^^^^^^^^^^^^ + test/features/recipes/presentation/cubit/my_recipes_cubit_test.dart:175:24: Error: Method not found: 'MyRecipesCubit'. + myRecipesCubit = MyRecipesCubit( + ^^^^^^^^^^^^^^ + test/features/recipes/presentation/cubit/my_recipes_cubit_test.dart:195:24: Error: Method not found: 'MyRecipesCubit'. + myRecipesCubit = MyRecipesCubit( + ^^^^^^^^^^^^^^ + test/features/recipes/presentation/cubit/my_recipes_cubit_test.dart:209:24: Error: Method not found: 'MyRecipesCubit'. + myRecipesCubit = MyRecipesCubit( + ^^^^^^^^^^^^^^ + test/features/recipes/presentation/cubit/my_recipes_cubit_test.dart:225:24: Error: Method not found: 'MyRecipesCubit'. + myRecipesCubit = MyRecipesCubit( + ^^^^^^^^^^^^^^ + . + +To run this test again: /home/alexander/fvm/versions/3.38.9/bin/cache/dart-sdk/bin/dart test /home/alexander/eatvio_app/test/features/recipes/presentation/cubit/my_recipes_cubit_test.dart -p vm --plain-name 'loading /home/alexander/eatvio_app/test/features/recipes/presentation/cubit/my_recipes_cubit_test.dart' + Error: The Dart compiler exited unexpectedly. + package:flutter_tools/src/base/common.dart 34:3 throwToolExit + package:flutter_tools/src/compile.dart 911:11 DefaultResidentCompiler._compile. + dart:async/zone.dart 1538:47 _rootRunUnary + dart:async/zone.dart 1429:19 _CustomZone.runUnary + dart:async/future_impl.dart 948:45 Future._propagateToListeners.handleValueCallback + dart:async/future_impl.dart 977:13 Future._propagateToListeners + dart:async/future_impl.dart 862:9 Future._propagateToListeners + dart:async/future_impl.dart 720:5 Future._completeWithValue + dart:async/future_impl.dart 804:7 Future._asyncCompleteWithValue. + dart:async/zone.dart 1525:13 _rootRun + dart:async/zone.dart 1422:19 _CustomZone.run + dart:async/zone.dart 1321:7 _CustomZone.runGuarded + dart:async/zone.dart 1362:23 _CustomZone.bindCallbackGuarded. + dart:async/schedule_microtask.dart 40:35 _microtaskLoop + dart:async/schedule_microtask.dart 49:5 _startMicrotaskLoop + dart:isolate-patch/isolate_patch.dart 127:13 _runPendingImmediateCallback + dart:isolate-patch/isolate_patch.dart 194:5 _RawReceivePort._handleMessage + + 00:06 +0 -1: loading /home/alexander/eatvio_app/test/features/recipes/presentation/cubit/recipes_cubit_test.dart 00:06 +0 -1: /home/alexander/eatvio_app/test/features/recipes/presentation/cubit/recipes_cubit_test.dart: RecipesCubit начальное состояние - initial state 00:06 +1 -1: /home/alexander/eatvio_app/test/features/recipes/presentation/cubit/recipes_cubit_test.dart: RecipesCubit начальное состояние - initial state 00:06 +1 -1: /home/alexander/eatvio_app/test/features/recipes/presentation/cubit/recipes_cubit_test.dart: RecipesCubit loadRecipesList - загружает рецепты успешно 00:07 +1 -1: /home/alexander/eatvio_app/test/features/recipes/presentation/cubit/recipes_cubit_test.dart: RecipesCubit loadRecipesList - загружает рецепты успешно 00:07 +2 -1: /home/alexander/eatvio_app/test/features/recipes/presentation/cubit/recipes_cubit_test.dart: RecipesCubit loadRecipesList - загружает рецепты успешно 00:07 +2 -1: /home/alexander/eatvio_app/test/features/recipes/presentation/cubit/recipes_cubit_test.dart: RecipesCubit loadRecipesList - обрабатывает ошибку 00:07 +3 -1: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should create RecipesCubit successfully 00:07 +4 -1: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should create RecipesCubit successfully 00:07 +5 -1: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should create RecipesCubit successfully 00:07 +6 -1: /home/alexander/eatvio_app/test/features/recipes/presentation/cubit/recipes_cubit_test.dart: RecipesCubit likeRecipe - лайкает рецепт 00:08 +6 -1: /home/alexander/eatvio_app/test/features/recipes/presentation/cubit/recipes_cubit_test.dart: RecipesCubit likeRecipe - лайкает рецепт 00:08 +7 -1: /home/alexander/eatvio_app/test/features/recipes/presentation/cubit/recipes_cubit_test.dart: RecipesCubit likeRecipe - лайкает рецепт 00:08 +8 -1: /home/alexander/eatvio_app/test/features/recipes/presentation/cubit/recipes_cubit_test.dart: RecipesCubit likeRecipe - лайкает рецепт 00:08 +9 -1: /home/alexander/eatvio_app/test/features/recipes/presentation/cubit/recipes_cubit_test.dart: RecipesCubit likeRecipe - лайкает рецепт 00:08 +10 -1: /home/alexander/eatvio_app/test/features/recipes/presentation/cubit/recipes_cubit_test.dart: RecipesCubit likeRecipe - лайкает рецепт 00:08 +11 -1: /home/alexander/eatvio_app/test/features/recipes/presentation/cubit/recipes_cubit_test.dart: RecipesCubit likeRecipe - лайкает рецепт 00:08 +12 -1: /home/alexander/eatvio_app/test/features/recipes/presentation/cubit/recipes_cubit_test.dart: RecipesCubit likeRecipe - лайкает рецепт 00:08 +13 -1: /home/alexander/eatvio_app/test/features/recipes/presentation/cubit/recipes_cubit_test.dart: RecipesCubit likeRecipe - лайкает рецепт 00:08 +14 -1: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display RecipesWidget with recipe title 00:08 +15 -1: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display RecipesWidget with recipe title 00:08 +16 -1: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display RecipesWidget with recipe title 00:08 +17 -1: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display RecipesWidget with recipe title 00:08 +18 -1: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display RecipesWidget with recipe title 00:08 +19 -1: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display RecipesWidget with recipe title 00:08 +20 -1: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display RecipesWidget with recipe title 00:08 +21 -1: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display RecipesWidget with recipe title 00:08 +22 -1: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/widgets_test.dart: Recipes Widgets Tests StepsWidget should display StepsWidget with steps 00:08 +23 -1: /home/alexander/eatvio_app/test/features/recipes/presentation/cubit/recipes_cubit_test.dart: RecipesCubit removeRecipeFromFavorite - удаляет рецепт из избранного 00:08 +24 -1: /home/alexander/eatvio_app/test/features/recipes/presentation/cubit/recipes_cubit_test.dart: RecipesCubit removeRecipeFromFavorite - удаляет рецепт из избранного 00:08 +25 -1: /home/alexander/eatvio_app/test/features/recipes/presentation/cubit/recipes_cubit_test.dart: RecipesCubit removeRecipeFromFavorite - удаляет рецепт из избранного 00:08 +26 -1: /home/alexander/eatvio_app/test/features/recipes/presentation/cubit/recipes_cubit_test.dart: RecipesCubit removeRecipeFromFavorite - удаляет рецепт из избранного 00:08 +27 -1: /home/alexander/eatvio_app/test/features/recipes/presentation/cubit/recipes_cubit_test.dart: RecipesCubit removeRecipeFromFavorite - удаляет рецепт из избранного 00:08 +28 -1: /home/alexander/eatvio_app/test/features/recipes/presentation/cubit/recipes_cubit_test.dart: RecipesCubit removeRecipeFromFavorite - удаляет рецепт из избранного 00:08 +29 -1: /home/alexander/eatvio_app/test/features/recipes/presentation/cubit/recipes_cubit_test.dart: RecipesCubit removeRecipeFromFavorite - удаляет рецепт из избранного 00:08 +30 -1: /home/alexander/eatvio_app/test/features/recipes/presentation/cubit/recipes_cubit_test.dart: RecipesCubit removeRecipeFromFavorite - удаляет рецепт из избранного 00:08 +31 -1: /home/alexander/eatvio_app/test/features/recipes/presentation/cubit/recipes_cubit_test.dart: RecipesCubit removeRecipeFromFavorite - удаляет рецепт из избранного 00:09 +31 -1: /home/alexander/eatvio_app/test/features/recipes/presentation/cubit/recipes_cubit_test.dart: RecipesCubit removeRecipeFromFavorite - удаляет рецепт из избранного 00:09 +32 -1: /home/alexander/eatvio_app/test/features/recipes/presentation/cubit/recipes_cubit_test.dart: RecipesCubit removeRecipeFromFavorite - удаляет рецепт из избранного 00:09 +33 -1: /home/alexander/eatvio_app/test/features/recipes/presentation/cubit/recipes_cubit_test.dart: RecipesCubit removeRecipeFromFavorite - удаляет рецепт из избранного 00:09 +34 -1: /home/alexander/eatvio_app/test/features/recipes/presentation/cubit/recipes_cubit_test.dart: RecipesCubit removeRecipeFromFavorite - удаляет рецепт из избранного 00:09 +35 -1: /home/alexander/eatvio_app/test/features/recipes/presentation/cubit/recipes_cubit_test.dart: RecipesCubit removeRecipeFromFavorite - удаляет рецепт из избранного 00:09 +36 -1: /home/alexander/eatvio_app/test/features/recipes/presentation/cubit/recipes_cubit_test.dart: RecipesCubit removeRecipeFromFavorite - удаляет рецепт из избранного 00:09 +37 -1: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display Recipes widget with loading state 00:09 +38 -1: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display Recipes widget with loading state 00:09 +39 -1: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display Recipes widget with loading state 00:09 +39 -1: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display Recipes widget with loaded state 00:09 +40 -1: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display Recipes widget with loaded state 00:09 +40 -1: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display Recipes widget with error state 00:09 +41 -1: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display Recipes widget with error state 00:09 +41 -1: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display Recipes widget with multiple recipes 00:09 +42 -1: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display Recipes widget with multiple recipes 00:09 +42 -1: Some tests failed. diff --git a/temp_test_output_recipes.txt b/temp_test_output_recipes.txt new file mode 100644 index 00000000..87d22b46 --- /dev/null +++ b/temp_test_output_recipes.txt @@ -0,0 +1 @@ + 00:00 +0: loading /home/alexander/eatvio_app/test/features/recipes/presentation/cubit/my_recipes_cubit_test.dart 00:01 +0: loading /home/alexander/eatvio_app/test/features/recipes/presentation/cubit/my_recipes_cubit_test.dart 00:02 +0: loading /home/alexander/eatvio_app/test/features/recipes/presentation/cubit/my_recipes_cubit_test.dart 00:03 +0: loading /home/alexander/eatvio_app/test/features/recipes/presentation/cubit/my_recipes_cubit_test.dart 00:04 +0: loading /home/alexander/eatvio_app/test/features/recipes/presentation/cubit/my_recipes_cubit_test.dart 00:04 +0: /home/alexander/eatvio_app/test/features/recipes/presentation/cubit/my_recipes_cubit_test.dart: MyRecipesCubit начальное состояние - loading 00:04 +1: /home/alexander/eatvio_app/test/features/recipes/presentation/cubit/my_recipes_cubit_test.dart: MyRecipesCubit начальное состояние - loading 00:04 +1: /home/alexander/eatvio_app/test/features/recipes/presentation/cubit/my_recipes_cubit_test.dart: MyRecipesCubit getMyRecipes - загружает активные рецепты 00:04 +2: /home/alexander/eatvio_app/test/features/recipes/presentation/cubit/my_recipes_cubit_test.dart: MyRecipesCubit getMyRecipes - загружает активные рецепты 00:04 +2: /home/alexander/eatvio_app/test/features/recipes/presentation/cubit/my_recipes_cubit_test.dart: MyRecipesCubit getMyRecipes - обрабатывает ошибку 00:05 +2: /home/alexander/eatvio_app/test/features/recipes/presentation/cubit/my_recipes_cubit_test.dart: MyRecipesCubit getMyRecipes - обрабатывает ошибку 00:05 +3: /home/alexander/eatvio_app/test/features/recipes/presentation/cubit/my_recipes_cubit_test.dart: MyRecipesCubit getMyRecipes - обрабатывает ошибку 00:05 +3: /home/alexander/eatvio_app/test/features/recipes/presentation/cubit/my_recipes_cubit_test.dart: MyRecipesCubit setActiveState - устанавливает активное состояние 00:05 +4: /home/alexander/eatvio_app/test/features/recipes/presentation/cubit/recipes_cubit_test.dart: RecipesCubit начальное состояние - initial state 00:05 +5: /home/alexander/eatvio_app/test/features/recipes/presentation/cubit/my_recipes_cubit_test.dart: MyRecipesCubit setActiveState - обрабатывает ошибку 00:05 +6: /home/alexander/eatvio_app/test/features/recipes/presentation/cubit/recipes_cubit_test.dart: RecipesCubit loadRecipesList - загружает рецепты успешно 00:05 +7: /home/alexander/eatvio_app/test/features/recipes/presentation/cubit/my_recipes_cubit_test.dart: MyRecipesCubit setArchivedState - устанавливает архивное состояние 00:05 +8: /home/alexander/eatvio_app/test/features/recipes/presentation/cubit/recipes_cubit_test.dart: RecipesCubit loadRecipesList - обрабатывает ошибку 00:05 +9: /home/alexander/eatvio_app/test/features/recipes/presentation/cubit/my_recipes_cubit_test.dart: MyRecipesCubit setArchivedState - обрабатывает ошибку 00:05 +10: /home/alexander/eatvio_app/test/features/recipes/presentation/cubit/my_recipes_cubit_test.dart: MyRecipesCubit setArchivedState - обрабатывает ошибку 00:05 +11: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should create RecipesCubit successfully 00:06 +11: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should create RecipesCubit successfully 00:06 +12: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should create RecipesCubit successfully 00:06 +13: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should create RecipesCubit successfully 00:06 +14: /home/alexander/eatvio_app/test/features/recipes/presentation/cubit/recipes_cubit_test.dart: RecipesCubit likeRecipe - лайкает рецепт 00:06 +15: /home/alexander/eatvio_app/test/features/recipes/presentation/cubit/recipes_cubit_test.dart: RecipesCubit likeRecipe - лайкает рецепт 00:06 +16: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/widgets_test.dart: Recipes Widgets Tests IngredientsWidget should display IngredientsWidget with ingredients 00:06 +17: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/widgets_test.dart: Recipes Widgets Tests IngredientsWidget should display IngredientsWidget with ingredients 00:06 +18: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/widgets_test.dart: Recipes Widgets Tests IngredientsWidget should display IngredientsWidget with ingredients 00:06 +19: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/widgets_test.dart: Recipes Widgets Tests IngredientsWidget should display IngredientsWidget with ingredients 00:06 +20: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/widgets_test.dart: Recipes Widgets Tests IngredientsWidget should display IngredientsWidget with ingredients 00:06 +21: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/widgets_test.dart: Recipes Widgets Tests IngredientsWidget should display IngredientsWidget with ingredients 00:06 +22: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/widgets_test.dart: Recipes Widgets Tests IngredientsWidget should display IngredientsWidget with ingredients 00:06 +23: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/widgets_test.dart: Recipes Widgets Tests IngredientsWidget should display IngredientsWidget with ingredients 00:07 +23: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/widgets_test.dart: Recipes Widgets Tests IngredientsWidget should display IngredientsWidget with ingredients 00:07 +24: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/widgets_test.dart: Recipes Widgets Tests IngredientsWidget should display IngredientsWidget with ingredients 00:07 +25: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/widgets_test.dart: Recipes Widgets Tests IngredientsWidget should display IngredientsWidget with ingredients 00:07 +26: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/widgets_test.dart: Recipes Widgets Tests IngredientsWidget should display IngredientsWidget with ingredients 00:07 +27: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/widgets_test.dart: Recipes Widgets Tests IngredientsWidget should display IngredientsWidget with ingredients 00:07 +28: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/widgets_test.dart: Recipes Widgets Tests IngredientsWidget should display IngredientsWidget with ingredients 00:07 +29: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display RecipesWidget with recipe title 00:07 +30: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display RecipesWidget with recipe title 00:07 +31: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display RecipesWidget with recipe title 00:07 +32: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display RecipesWidget with recipe title 00:07 +33: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display RecipesWidget with recipe title 00:07 +34: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display RecipesWidget with recipe title 00:07 +35: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display RecipesWidget with recipe title 00:07 +36: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display RecipesWidget with recipe title 00:07 +37: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display RecipesWidget with recipe title 00:07 +38: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display RecipesWidget with recipe title 00:07 +39: /home/alexander/eatvio_app/test/features/recipes/presentation/cubit/recipes_cubit_test.dart: RecipesCubit removeRecipeFromFavorite - удаляет рецепт из избранного 00:07 +40: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: ... Widget Tests should display RecipesWidget with description when description is present 00:07 +41: /home/alexander/eatvio_app/test/features/recipes/presentation/cubit/recipes_cubit_test.dart: RecipesCubit deleteRecipe - удаляет рецепт 00:07 +42: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display RecipesWidget structure correctly 00:07 +43: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display RecipesWidget structure correctly 00:07 +44: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display RecipesWidget structure correctly 00:07 +45: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display RecipesWidget structure correctly 00:07 +46: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display RecipesWidget structure correctly 00:07 +47: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display RecipesWidget structure correctly 00:07 +47: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display Recipes widget with loading state 00:08 +47: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display Recipes widget with loading state 00:08 +48: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display Recipes widget with loading state 00:08 +48: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display Recipes widget with loaded state 00:08 +49: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display Recipes widget with loaded state 00:08 +49: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display Recipes widget with error state 00:08 +50: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display Recipes widget with error state 00:08 +50: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display Recipes widget with multiple recipes 00:09 +50: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display Recipes widget with multiple recipes 00:09 +51: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display Recipes widget with multiple recipes 00:09 +51: All tests passed! diff --git a/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart b/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart index 241a9a91..2fb3fb7b 100644 --- a/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart +++ b/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart @@ -433,6 +433,7 @@ void main() { final tempFile = File('${Directory.systemTemp.path}/test_image.jpg'); await tempFile.writeAsBytes([1, 2, 3]); cubit.replaceImage(tempFile); + cubit.addIngredient(const i.Ingredient(name: 'Test Ingredient', unit: 'gr', portion: '100', productId: 1)); cubit.replaceDescriptionInStep('Step 1 description', 0); expect(cubit.validateAll(), true); diff --git a/test/features/create_recipe/reporitories/recipe_repository_test.dart b/test/features/create_recipe/reporitories/recipe_repository_test.dart index 4a82a264..8392d3b7 100644 --- a/test/features/create_recipe/reporitories/recipe_repository_test.dart +++ b/test/features/create_recipe/reporitories/recipe_repository_test.dart @@ -116,7 +116,7 @@ void main() { test('deleteRecipe - удаляет рецепт', () async { const slug = 'recipe-slug'; - when(() => mockService.deleteRecipe(slug: slug)).thenAnswer((_) async => Future.value(mockHttpResponse)); + when(() => mockService.deleteRecipe(slug: slug)).thenAnswer((_) async => Future.value()); await repository.deleteRecipe(slug); @@ -147,7 +147,7 @@ void main() { energyPerServing: 300, ); - await repository.publishRecipe(recipe); + expect(() => repository.publishRecipe(recipe), throwsException); // Методы не должны вызываться, так как изображение отсутствует verifyNever(() => mockService.publishRecipe(formData: any(named: 'formData'))); @@ -169,7 +169,7 @@ void main() { energyPerServing: 300, ); - await repository.publishRecipe(recipe); + expect(() => repository.publishRecipe(recipe), throwsException); // Методы не должны вызываться, так как изображение пустое verifyNever(() => mockService.publishRecipe(formData: any(named: 'formData'))); diff --git a/test/features/create_recipe/services/recipe_service_test.dart b/test/features/create_recipe/services/recipe_service_test.dart index 022eb89a..e9b0f900 100644 --- a/test/features/create_recipe/services/recipe_service_test.dart +++ b/test/features/create_recipe/services/recipe_service_test.dart @@ -9,6 +9,10 @@ import 'package:mockito/mockito.dart'; import 'recipe_service_test.mocks.dart'; +import 'package:retrofit/retrofit.dart'; + +final mockHttpResponse = HttpResponse(null, Response(requestOptions: RequestOptions())); + @GenerateNiceMocks([MockSpec()]) void main() { group('CreateRecipeService', () { @@ -77,7 +81,7 @@ void main() { final formData = FormData.fromMap({}); when(mockCreateRecipeService.publishRecipe(formData: formData)) - .thenAnswer((_) async => Future.value()); + .thenAnswer((_) async => Future.value(mockHttpResponse)); await mockCreateRecipeService.publishRecipe(formData: formData); diff --git a/test/features/recipe/presentation/cubit/my_recipes_cubit/my_recipes_cubit_test.dart b/test/features/recipe/presentation/cubit/my_recipes_cubit/my_recipes_cubit_test.dart index a77e0a7f..3bd1f05f 100644 --- a/test/features/recipe/presentation/cubit/my_recipes_cubit/my_recipes_cubit_test.dart +++ b/test/features/recipe/presentation/cubit/my_recipes_cubit/my_recipes_cubit_test.dart @@ -1,5 +1,5 @@ import 'package:eatvio_app/features/recipe/models/my_recipe_model.dart'; -import 'package:eatvio_app/features/recipe/presentation/cubit/my_recipes_cubit/my_recieps_cubit.dart'; +import 'package:eatvio_app/features/recipe/presentation/cubit/my_recipes_cubit/my_recipes_cubit.dart'; import 'package:eatvio_app/features/recipe/presentation/cubit/my_recipes_cubit/my_recipes_state.dart'; import 'package:eatvio_app/features/recipe/repositories/about_recipe_repository.dart'; import 'package:eatvio_app/features/recipe/repositories/my_recipes_repository.dart'; diff --git a/test/features/recipe/presentation/pages/my_recipes_screen_smoke_test.dart b/test/features/recipe/presentation/pages/my_recipes_screen_smoke_test.dart index fac14897..ebd26e01 100644 --- a/test/features/recipe/presentation/pages/my_recipes_screen_smoke_test.dart +++ b/test/features/recipe/presentation/pages/my_recipes_screen_smoke_test.dart @@ -2,11 +2,13 @@ import 'package:eatvio_app/core/app_config.dart'; import 'package:eatvio_app/core/app_initializer.dart'; import 'package:eatvio_app/core/locator_service.dart'; import 'package:eatvio_app/core/theme/theme_helper.dart'; -import 'package:eatvio_app/features/recipe/presentation/cubit/my_recipes_cubit/my_recieps_cubit.dart'; +import 'package:eatvio_app/features/recipe/presentation/cubit/my_recipes_cubit/my_recipes_cubit.dart'; import 'package:eatvio_app/features/recipe/presentation/cubit/my_recipes_cubit/my_recipes_state.dart'; import 'package:eatvio_app/features/recipe/presentation/pages/my_recipes_screen.dart'; +import 'package:eatvio_app/generated/l10n.dart'; import 'package:flutter/material.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; @@ -26,7 +28,17 @@ void main() { }); Widget wrap(Widget child) { - return MaterialApp(theme: ThemeHelper().themeData(), home: child); + return MaterialApp( + theme: ThemeHelper().themeData(), + localizationsDelegates: const [ + S.delegate, + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ], + supportedLocales: S.delegate.supportedLocales, + home: child, + ); } testWidgets('renders my recipes screen', (tester) async { diff --git a/test/features/recipe/presentation/widgets/my_recipe_widget_test.dart b/test/features/recipe/presentation/widgets/my_recipe_widget_test.dart index 52fca10f..caa91380 100644 --- a/test/features/recipe/presentation/widgets/my_recipe_widget_test.dart +++ b/test/features/recipe/presentation/widgets/my_recipe_widget_test.dart @@ -5,7 +5,7 @@ import 'package:eatvio_app/core/theme/theme_helper.dart'; import 'package:eatvio_app/features/feed/presentation/bloc/recipes_cubit/recipes_cubit.dart'; import 'package:eatvio_app/features/feed/presentation/bloc/recipes_cubit/recipes_state.dart'; import 'package:eatvio_app/features/recipe/models/my_recipe_model.dart'; -import 'package:eatvio_app/features/recipe/presentation/cubit/my_recipes_cubit/my_recieps_cubit.dart'; +import 'package:eatvio_app/features/recipe/presentation/cubit/my_recipes_cubit/my_recipes_cubit.dart'; import 'package:eatvio_app/features/recipe/presentation/cubit/my_recipes_cubit/my_recipes_state.dart'; import 'package:eatvio_app/features/recipe/presentation/widgets/my_recipe_widget.dart'; import 'package:eatvio_app/generated/l10n.dart'; diff --git a/test/features/recipes/presentation/cubit/my_recipes_cubit_test.dart b/test/features/recipes/presentation/cubit/my_recipes_cubit_test.dart index 267e5191..e116dc1e 100644 --- a/test/features/recipes/presentation/cubit/my_recipes_cubit_test.dart +++ b/test/features/recipes/presentation/cubit/my_recipes_cubit_test.dart @@ -1,7 +1,7 @@ import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart'; import 'package:eatvio_app/features/feed/models/recipes_model.dart'; import 'package:eatvio_app/features/recipe/models/my_recipe_model.dart'; -import 'package:eatvio_app/features/recipe/presentation/cubit/my_recipes_cubit/my_recieps_cubit.dart'; +import 'package:eatvio_app/features/recipe/presentation/cubit/my_recipes_cubit/my_recipes_cubit.dart'; import 'package:eatvio_app/features/recipe/presentation/cubit/my_recipes_cubit/my_recipes_state.dart'; import 'package:eatvio_app/features/recipe/repositories/about_recipe_repository.dart'; import 'package:eatvio_app/features/recipe/repositories/my_recipes_repository.dart'; diff --git a/test_failures.txt b/test_failures.txt new file mode 100644 index 00000000..a655442b --- /dev/null +++ b/test_failures.txt @@ -0,0 +1,57 @@ + 00:00 +0: loading /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart 00:01 +0: loading /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart 00:02 +0: loading /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart 00:02 +0: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should create CreateRecipeCubit successfully 00:02 +1: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should create CreateRecipeCubit successfully 00:02 +1: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display basic form fields 00:02 +2: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display basic form fields 00:02 +3: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display basic form fields 00:02 +4: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display basic form fields 00:02 +5: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display basic form fields 00:02 +6: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display basic form fields 00:02 +7: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display basic form fields 00:02 +8: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display basic form fields 00:02 +9: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display basic form fields 00:02 +10: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display basic form fields 00:02 +11: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display basic form fields 00:02 +12: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display basic form fields 00:02 +13: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display basic form fields 00:02 +14: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display basic form fields 00:03 +14: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display basic form fields 00:03 +15: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display basic form fields 00:03 +16: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display basic form fields 00:03 +17: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display basic form fields 00:03 +18: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display basic form fields 00:03 +19: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display basic form fields 00:03 +20: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display basic form fields 00:03 +21: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display basic form fields 00:03 +21: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should update title when user types in title field 00:03 +22: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should update title when user types in title field 00:03 +22: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should update description when user types in description field 00:03 +23: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should update description when user types in description field 00:03 +23: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should update portions when user types in portions field 00:03 +24: /home/alexander/eatvio_app/test/features/create_recipe/models/recipe_create_model_test.dart: RecipeType enum has all expected values 00:03 +25: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should update calories when user types in calories field 00:03 +26: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should update calories when user types in calories field 00:03 +27: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should update calories when user types in calories field 00:03 +28: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should update calories when user types in calories field 00:03 +29: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should update calories when user types in calories field 00:03 +29: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display form labels correctly 00:03 +30: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display form labels correctly 00:03 +30: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display placeholder text in title field 00:03 +31: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display placeholder text in title field 00:03 +31: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display publish button 00:03 +32: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display publish button 00:03 +32: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should handle button tap 00:03 +33: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should handle button tap 00:03 +34: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should handle button tap 00:03 +35: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should handle button tap 00:03 +36: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should handle button tap 00:03 +37: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should handle button tap 00:03 +38: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should handle button tap 00:03 +38: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display loading state 00:03 +39: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display loading state 00:03 +39: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display error message 00:03 +40: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display error message 00:03 +40: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display success message 00:04 +40: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display success message 00:04 +41: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display success message 00:04 +41: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should validate required fields 00:04 +42: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should validate required fields 00:04 +42: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display form with all required fields 00:04 +43: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display form with all required fields 00:04 +43: loading /home/alexander/eatvio_app/test/features/create_recipe/models/recipe_tag_model_test.dart 00:04 +43: /home/alexander/eatvio_app/test/features/create_recipe/models/recipe_tag_model_test.dart: RecipeTag создает RecipeTag с правильными значениями 00:04 +44: /home/alexander/eatvio_app/test/features/create_recipe/models/recipe_tag_model_test.dart: RecipeTag создает RecipeTag с правильными значениями 00:04 +44: /home/alexander/eatvio_app/test/features/create_recipe/models/recipe_tag_model_test.dart: RecipeTagModel создает RecipeTagModel с правильными значениями 00:04 +45: /home/alexander/eatvio_app/test/features/create_recipe/models/recipe_tag_model_test.dart: RecipeTagModel создает RecipeTagModel с правильными значениями 00:04 +45: /home/alexander/eatvio_app/test/features/create_recipe/models/recipe_tag_model_test.dart: RecipeTagModel fromJson создает RecipeTagModel из JSON 00:04 +46: /home/alexander/eatvio_app/test/features/create_recipe/models/recipe_tag_model_test.dart: RecipeTagModel fromJson создает RecipeTagModel из JSON 00:04 +46: /home/alexander/eatvio_app/test/features/create_recipe/models/recipe_tag_model_test.dart: RecipeTag fromJson создает RecipeTag из JSON 00:04 +47: /home/alexander/eatvio_app/test/features/create_recipe/models/recipe_tag_model_test.dart: RecipeTag fromJson создает RecipeTag из JSON 00:04 +47: /home/alexander/eatvio_app/test/features/create_recipe/models/recipe_tag_model_test.dart: RecipeTag toJson serializes RecipeTag correctly 00:04 +48: /home/alexander/eatvio_app/test/features/create_recipe/models/recipe_tag_model_test.dart: RecipeTag toJson serializes RecipeTag correctly 00:04 +48: /home/alexander/eatvio_app/test/features/create_recipe/models/recipe_tag_model_test.dart: RecipeTagModel toJson serializes RecipeTagModel correctly 00:04 +49: /home/alexander/eatvio_app/test/features/create_recipe/models/recipe_tag_model_test.dart: RecipeTagModel toJson serializes RecipeTagModel correctly 00:04 +49: loading /home/alexander/eatvio_app/test/features/create_recipe/models/step_test.dart 00:04 +49: /home/alexander/eatvio_app/test/features/create_recipe/models/step_test.dart: Step создает Step с правильными значениями 00:04 +50: /home/alexander/eatvio_app/test/features/create_recipe/models/step_test.dart: Step создает Step с правильными значениями 00:04 +50: /home/alexander/eatvio_app/test/features/create_recipe/models/step_test.dart: Step создает Step с null значениями 00:04 +51: /home/alexander/eatvio_app/test/features/create_recipe/models/step_test.dart: Step создает Step с null значениями 00:04 +51: /home/alexander/eatvio_app/test/features/create_recipe/models/step_test.dart: Step fromJson создает Step из JSON 00:04 +52: /home/alexander/eatvio_app/test/features/create_recipe/models/step_test.dart: Step fromJson создает Step из JSON 00:04 +52: /home/alexander/eatvio_app/test/features/create_recipe/models/step_test.dart: Step fromJson создает Step из JSON с null значениями 00:04 +53: /home/alexander/eatvio_app/test/features/create_recipe/models/step_test.dart: Step fromJson создает Step из JSON с null значениями 00:04 +53: /home/alexander/eatvio_app/test/features/create_recipe/models/step_test.dart: Step toJson serializes Step correctly 00:04 +54: /home/alexander/eatvio_app/test/features/create_recipe/models/step_test.dart: Step toJson serializes Step correctly 00:04 +54: /home/alexander/eatvio_app/test/features/create_recipe/models/step_test.dart: Step toJson serializes Step with null values correctly 00:04 +55: /home/alexander/eatvio_app/test/features/create_recipe/models/step_test.dart: Step toJson serializes Step with null values correctly 00:04 +55: loading /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart 00:04 +55: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit initial state has default values 00:04 +56: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit initial state has default values 00:04 +56: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit addStep adds a new empty step 00:04 +57: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit addStep adds a new empty step 00:04 +57: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit removeStep removes provided step 00:04 +58: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit removeStep removes provided step 00:04 +58: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit addIngredient appends ingredient to list 00:04 +59: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit addIngredient appends ingredient to list 00:04 +59: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit removeIngredient removes ingredient by index 00:04 +60: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit removeIngredient removes ingredient by index 00:04 +60: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit updateIngredient replaces ingredient at index 00:04 +61: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit updateIngredient replaces ingredient at index 00:04 +61: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit replaceIngredientPosition moves ingredient 00:04 +62: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit replaceIngredientPosition moves ingredient 00:04 +62: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... replaceTitle and replaceDescription update entity 00:04 +63: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... replaceTitle and replaceDescription update entity 00:04 +63: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit addTag and removeTag work correctly 00:04 +64: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit addTag and removeTag work correctly 00:04 +64: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit portion and energy setters update entity 00:04 +65: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit portion and energy setters update entity 00:04 +65: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit replaceVideoUrl and replaceImage update entity 00:04 +66: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit replaceVideoUrl and replaceImage update entity 00:04 +66: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... replaceDescriptionInStep and replaceImageInStep update step 00:04 +67: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... replaceDescriptionInStep and replaceImageInStep update step 00:04 +67: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit disableLoading sets loading to false 00:04 +68: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit disableLoading sets loading to false 00:04 +68: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit changeType updates type of recipe 00:04 +69: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit changeType updates type of recipe 00:04 +69: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... replaceTitle with empty string sets title to null 00:04 +70: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... replaceTitle with empty string sets title to null 00:04 +70: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... with empty string sets description to null 00:04 +71: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... with empty string sets description to null 00:04 +71: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit publishRecipe - успешно публикует рецепт 00:04 +72: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit publishRecipe - успешно публикует рецепт 00:04 +72: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit publishRecipe - обновляет рецепт 00:04 +73: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit publishRecipe - обновляет рецепт 00:04 +73: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit publishRecipe - обрабатывает ошибку 00:04 +74: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit publishRecipe - обрабатывает ошибку 00:04 +74: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit deleteRecipe - успешно удаляет рецепт 00:04 +75: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit deleteRecipe - успешно удаляет рецепт 00:04 +75: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit deleteRecipe - обрабатывает ошибку 00:04 +76: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit deleteRecipe - обрабатывает ошибку 00:04 +76: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... getGlossaryListByQuery - получает список глоссария 00:04 +77: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... getGlossaryListByQuery - получает список глоссария 00:04 +77: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit validateTitle - возвращает false для null 00:04 +78: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit validateTitle - возвращает false для null 00:04 +78: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... validateTitle - возвращает false для пустой строки 00:04 +79: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... validateTitle - возвращает false для пустой строки 00:04 +79: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... validateTitle - возвращает false для короткого названия 00:04 +80: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... validateTitle - возвращает false для короткого названия 00:04 +80: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... validateTitle - возвращает true для валидного названия 00:04 +81: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... validateTitle - возвращает true для валидного названия 00:04 +81: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... validateDescription - возвращает false для null 00:04 +82: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... validateDescription - возвращает false для null 00:04 +82: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... validateDescription - возвращает false для пустой строки 00:04 +83: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... validateDescription - возвращает false для пустой строки 00:04 +83: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... - возвращает false для короткого описания 00:04 +84: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... - возвращает false для короткого описания 00:04 +84: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... validateDescription - возвращает true для валидного описания 00:04 +85: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... validateDescription - возвращает true для валидного описания 00:04 +85: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit validateServings - возвращает false для null 00:04 +86: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit validateServings - возвращает false для null 00:04 +86: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... validateServings - возвращает true для валидного значения 00:04 +87: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... validateServings - возвращает true для валидного значения 00:04 +87: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... validateEnergyPerServing - возвращает false для null 00:04 +88: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... validateEnergyPerServing - возвращает false для null 00:04 +88: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... - возвращает true для валидного значения 00:04 +89: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... - возвращает true для валидного значения 00:04 +89: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... - возвращает false когда оба поля null 00:04 +90: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... - возвращает false когда оба поля null 00:04 +90: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... - возвращает true когда хотя бы одно поле заполнено 00:04 +91: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... - возвращает true когда хотя бы одно поле заполнено 00:04 +91: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... - возвращает false когда одно из полей null 00:04 +92: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... - возвращает false когда одно из полей null 00:04 +92: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... - возвращает true когда оба поля заполнены 00:04 +93: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... - возвращает true когда оба поля заполнены 00:04 +93: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit validateImage - возвращает false для null 00:05 +93: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit validateImage - возвращает false для null 00:05 +94: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit validateImage - возвращает false для null 00:05 +94: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... validateImage - возвращает true для валидного изображения 00:05 +95: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... validateImage - возвращает true для валидного изображения 00:05 +95: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... validateAll - возвращает false когда не все поля валидны 00:05 +96: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... validateAll - возвращает false когда не все поля валидны 00:05 +96: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... validateAll - возвращает true когда все поля валидны 00:05 +97: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... validateAll - возвращает true когда все поля валидны 00:05 +97: loading /home/alexander/eatvio_app/test/features/create_recipe/presentation/pages/recipe_create_screen_test.dart 00:05 +97: /home/alexander/eatvio_app/test/features/create_recipe/presentation/pages/recipe_create_screen_test.dart: RecipeCreateScreen smoke (setUpAll) 00:05 +97: /home/alexander/eatvio_app/test/features/create_recipe/presentation/pages/recipe_create_screen_test.dart: RecipeCreateScreen smoke renders recipe create screen - initial state 00:06 +97: /home/alexander/eatvio_app/test/features/create_recipe/presentation/pages/recipe_create_screen_test.dart: RecipeCreateScreen smoke renders recipe create screen - initial state 00:06 +98: /home/alexander/eatvio_app/test/features/create_recipe/presentation/pages/recipe_create_screen_test.dart: RecipeCreateScreen smoke renders recipe create screen - initial state 00:06 +99: /home/alexander/eatvio_app/test/features/create_recipe/presentation/pages/recipe_create_screen_test.dart: RecipeCreateScreen smoke renders recipe create screen - initial state 00:06 +100: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/portion_widget_test.dart: PortionWidget smoke renders portion widget 00:06 +101: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/portion_widget_test.dart: PortionWidget smoke renders portion widget 00:07 +101: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/portion_widget_test.dart: PortionWidget smoke renders portion widget 00:07 +102: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/portion_widget_test.dart: PortionWidget smoke renders portion widget 00:07 +103: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:07 +104: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:07 +105: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:07 +106: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:07 +107: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:07 +108: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:07 +109: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:07 +110: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:07 +111: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:07 +112: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:07 +113: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:07 +114: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:07 +115: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:07 +116: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:07 +117: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:07 +118: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:07 +119: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:07 +120: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:07 +121: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:07 +122: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:07 +123: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:07 +124: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:07 +125: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:07 +126: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:07 +127: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:07 +128: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:07 +129: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:07 +130: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:07 +131: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/time_of_cook_widget_test.dart: TimeOfCookWidget smoke renders time of cook widget 00:07 +132: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/time_of_cook_widget_test.dart: TimeOfCookWidget smoke renders time of cook widget 00:07 +133: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/time_of_cook_widget_test.dart: TimeOfCookWidget smoke renders time of cook widget 00:07 +134: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/time_of_cook_widget_test.dart: TimeOfCookWidget smoke renders time of cook widget 00:07 +135: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/time_of_cook_widget_test.dart: TimeOfCookWidget smoke renders time of cook widget 00:07 +136: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/time_of_cook_widget_test.dart: TimeOfCookWidget smoke renders time of cook widget 00:07 +137: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/time_of_cook_widget_test.dart: TimeOfCookWidget smoke renders time of cook widget 00:08 +137: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/time_of_cook_widget_test.dart: TimeOfCookWidget smoke renders time of cook widget 00:08 +138: /home/alexander/eatvio_app/test/features/create_recipe/simple_widget_test.dart: Recipe Creation Widget Tests should display InfoAboutRecipeWidget with all form fields 00:08 +139: /home/alexander/eatvio_app/test/features/create_recipe/simple_widget_test.dart: Recipe Creation Widget Tests should display InfoAboutRecipeWidget with all form fields 00:08 +140: /home/alexander/eatvio_app/test/features/create_recipe/simple_widget_test.dart: Recipe Creation Widget Tests should display InfoAboutRecipeWidget with all form fields 00:08 +141: /home/alexander/eatvio_app/test/features/create_recipe/simple_widget_test.dart: Recipe Creation Widget Tests should display InfoAboutRecipeWidget with all form fields 00:08 +142: /home/alexander/eatvio_app/test/features/create_recipe/simple_widget_test.dart: Recipe Creation Widget Tests should display InfoAboutRecipeWidget with all form fields 00:08 +143: /home/alexander/eatvio_app/test/features/create_recipe/simple_widget_test.dart: Recipe Creation Widget Tests should display InfoAboutRecipeWidget with all form fields 00:08 +144: /home/alexander/eatvio_app/test/features/create_recipe/simple_widget_test.dart: Recipe Creation Widget Tests should display InfoAboutRecipeWidget with all form fields 00:08 +145: /home/alexander/eatvio_app/test/features/create_recipe/simple_widget_test.dart: Recipe Creation Widget Tests should display InfoAboutRecipeWidget with all form fields 00:08 +146: /home/alexander/eatvio_app/test/features/create_recipe/simple_widget_test.dart: Recipe Creation Widget Tests should display InfoAboutRecipeWidget with all form fields 00:09 +146: /home/alexander/eatvio_app/test/features/create_recipe/simple_widget_test.dart: Recipe Creation Widget Tests should display InfoAboutRecipeWidget with all form fields 00:09 +147: /home/alexander/eatvio_app/test/features/create_recipe/widget_test.dart: RecipeCreateScreen Widget Tests should display all required form fields 00:09 +148: /home/alexander/eatvio_app/test/features/create_recipe/widget_test.dart: RecipeCreateScreen Widget Tests should display all required form fields 00:09 +149: /home/alexander/eatvio_app/test/features/create_recipe/widget_test.dart: RecipeCreateScreen Widget Tests should display all required form fields 00:09 +150: /home/alexander/eatvio_app/test/features/create_recipe/widget_test.dart: RecipeCreateScreen Widget Tests should display all required form fields 00:09 +151: /home/alexander/eatvio_app/test/features/create_recipe/widget_test.dart: RecipeCreateScreen Widget Tests should display all required form fields 00:09 +152: /home/alexander/eatvio_app/test/features/create_recipe/widget_test.dart: RecipeCreateScreen Widget Tests should display all required form fields 00:09 +153: /home/alexander/eatvio_app/test/features/create_recipe/widget_test.dart: RecipeCreateScreen Widget Tests should display all required form fields 00:09 +154: /home/alexander/eatvio_app/test/features/create_recipe/widget_test.dart: RecipeCreateScreen Widget Tests should display all required form fields 00:09 +155: /home/alexander/eatvio_app/test/features/create_recipe/widget_test.dart: RecipeCreateScreen Widget Tests should display all required form fields 00:09 +156: /home/alexander/eatvio_app/test/features/create_recipe/widget_test.dart: RecipeCreateScreen Widget Tests should display all required form fields 00:09 +157: /home/alexander/eatvio_app/test/features/create_recipe/widget_test.dart: RecipeCreateScreen Widget Tests should display all required form fields 00:09 +158: /home/alexander/eatvio_app/test/features/create_recipe/widget_test.dart: RecipeCreateScreen Widget Tests should display all required form fields 00:09 +159: /home/alexander/eatvio_app/test/features/create_recipe/widget_test.dart: RecipeCreateScreen Widget Tests should display all required form fields 00:09 +160: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - лайк рецепта 00:09 +161: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - лайк рецепта 00:10 +161: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - лайк рецепта 00:10 +162: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - лайк рецепта 00:10 +163: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - лайк рецепта 00:10 +164: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - лайк рецепта 00:10 +165: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - лайк рецепта 00:10 +166: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - лайк рецепта 00:10 +167: /home/alexander/eatvio_app/test/features/recipe/presentation/pages/about_recipe_screen_smoke_test.dart: AboutRecipeScreen smoke renders loading state 00:10 +168: /home/alexander/eatvio_app/test/features/recipe/presentation/pages/about_recipe_screen_smoke_test.dart: AboutRecipeScreen smoke renders loading state 00:10 +169: /home/alexander/eatvio_app/test/features/recipe/presentation/pages/about_recipe_screen_smoke_test.dart: AboutRecipeScreen smoke renders loading state 00:10 +170: /home/alexander/eatvio_app/test/features/recipe/presentation/pages/about_recipe_screen_smoke_test.dart: AboutRecipeScreen smoke renders loading state 00:10 +171: /home/alexander/eatvio_app/test/features/recipe/presentation/pages/about_recipe_screen_smoke_test.dart: AboutRecipeScreen smoke renders loading state 00:10 +172: /home/alexander/eatvio_app/test/features/recipe/presentation/pages/about_recipe_screen_smoke_test.dart: AboutRecipeScreen smoke renders loading state 00:10 +173: /home/alexander/eatvio_app/test/features/recipe/presentation/pages/about_recipe_screen_smoke_test.dart: AboutRecipeScreen smoke renders loading state 00:10 +174: /home/alexander/eatvio_app/test/features/recipe/presentation/pages/about_recipe_screen_smoke_test.dart: AboutRecipeScreen smoke renders loading state 00:10 +174: /home/alexander/eatvio_app/test/features/recipe/presentation/pages/my_recipes_screen_smoke_test.dart: MyRecipesScreen smoke renders my recipes screen +══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════ +Failed assertion: line 47 pos 7: 'instance != null' +The relevant error-causing widget was: +When the exception was thrown, this was the stack: +(elided 7 frames from class _AssertionError, dart:async, and package:stack_trace) +══╡ EXCEPTION CAUGHT BY RENDERING LIBRARY ╞═════════════════════════════════════════════════════════ +The relevant error-causing widget was: +This is considered an error condition because it indicates that there is content that cannot be +══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════ +Failed assertion: line 47 pos 7: 'instance != null' +The relevant error-causing widget was: +When the exception was thrown, this was the stack: +(elided 7 frames from class _AssertionError, dart:async, and package:stack_trace) +══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════ +Multiple exceptions (3) were detected during the running of the current test, and at least one was +unexpected. + Test failed. See exception logs above. +══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════ +Failed assertion: line 47 pos 7: 'instance != null' +The relevant error-causing widget was: +When the exception was thrown, this was the stack: +(elided 7 frames from class _AssertionError, dart:async, and package:stack_trace) +══╡ EXCEPTION CAUGHT BY RENDERING LIBRARY ╞═════════════════════════════════════════════════════════ +The relevant error-causing widget was: +This is considered an error condition because it indicates that there is content that cannot be +══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════ +Failed assertion: line 47 pos 7: 'instance != null' +The relevant error-causing widget was: +When the exception was thrown, this was the stack: +(elided 7 frames from class _AssertionError, dart:async, and package:stack_trace) +══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════ +Multiple exceptions (3) were detected during the running of the current test, and at least one was +unexpected. + Test failed. See exception logs above. + 00:11 +178 -2: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - убирает лайк если уже лайкнуто 00:11 +178 -2: /home/alexander/eatvio_app/test/features/recipe/presentation/pages/my_recipes_screen_smoke_test.dart: MyRecipesScreen smoke renders my recipes screen with error state +══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════ +Failed assertion: line 47 pos 7: 'instance != null' +The relevant error-causing widget was: +When the exception was thrown, this was the stack: +(elided 7 frames from class _AssertionError, dart:async, and package:stack_trace) +══╡ EXCEPTION CAUGHT BY RENDERING LIBRARY ╞═════════════════════════════════════════════════════════ +The relevant error-causing widget was: +This is considered an error condition because it indicates that there is content that cannot be +══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════ +Failed assertion: line 47 pos 7: 'instance != null' +The relevant error-causing widget was: +When the exception was thrown, this was the stack: +(elided 7 frames from class _AssertionError, dart:async, and package:stack_trace) +══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════ +Multiple exceptions (3) were detected during the running of the current test, and at least one was +unexpected. + 00:11 +178 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/pages/my_recipes_screen_smoke_test.dart: MyRecipesScreen smoke renders my recipes screen with error state [E] + Test failed. See exception logs above. + The test description was: renders my recipes screen with error state +To run this test again: /home/alexander/fvm/versions/3.38.9/bin/cache/dart-sdk/bin/dart test /home/alexander/eatvio_app/test/features/recipe/presentation/pages/my_recipes_screen_smoke_test.dart -p vm --plain-name 'MyRecipesScreen smoke renders my recipes screen with error state' + 00:11 +178 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - убирает лайк если уже лайкнуто 00:11 +179 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - убирает лайк если уже лайкнуто 00:11 +180 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - убирает лайк если уже лайкнуто 00:11 +181 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/widgets/my_recipe_widget_test.dart: MyRecipeWidget smoke renders my recipe widget 00:11 +182 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/widgets/my_recipe_widget_test.dart: MyRecipeWidget smoke renders my recipe widget 00:11 +183 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/widgets/my_recipe_widget_test.dart: MyRecipeWidget smoke renders my recipe widget 00:11 +184 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/widgets/my_recipe_widget_test.dart: MyRecipeWidget smoke renders my recipe widget 00:12 +184 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/widgets/my_recipe_widget_test.dart: MyRecipeWidget smoke renders my recipe widget 00:12 +185 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/widgets/my_recipe_widget_test.dart: MyRecipeWidget smoke renders my recipe widget 00:12 +186 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/widgets/my_recipe_widget_test.dart: MyRecipeWidget smoke renders my recipe widget 00:12 +187 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/widgets/my_recipe_widget_test.dart: MyRecipeWidget smoke renders my recipe widget 00:12 +188 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/widgets/my_recipe_widget_test.dart: MyRecipeWidget smoke renders my recipe widget 00:12 +189 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/widgets/my_recipe_widget_test.dart: MyRecipeWidget smoke renders my recipe widget 00:12 +190 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/widgets/my_recipe_widget_test.dart: MyRecipeWidget smoke renders my recipe widget 00:12 +191 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/widgets/my_recipe_widget_test.dart: MyRecipeWidget smoke renders my recipe widget 00:12 +192 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/widgets/my_recipe_widget_test.dart: MyRecipeWidget smoke renders my recipe widget 00:12 +193 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/widgets/my_recipe_widget_test.dart: MyRecipeWidget smoke renders my recipe widget 00:12 +194 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/widgets/my_recipe_widget_test.dart: MyRecipeWidget smoke renders my recipe widget 00:12 +195 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - добавляет рецепт в избранное 00:12 +196 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - добавляет рецепт в избранное 00:12 +197 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - добавляет рецепт в избранное 00:12 +198 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - добавляет рецепт в избранное 00:12 +199 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - добавляет рецепт в избранное 00:12 +200 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - добавляет рецепт в избранное 00:12 +201 -3: /home/alexander/eatvio_app/test/features/recipes/presentation/cubit/my_recipes_cubit_test.dart: MyRecipesCubit getMyRecipes - загружает активные рецепты 00:12 +202 -3: /home/alexander/eatvio_app/test/features/create_recipe/widget_test.dart: RecipeCreateScreen Widget Tests should show validation error for invalid portions 00:12 +203 -3: /home/alexander/eatvio_app/test/features/create_recipe/widget_test.dart: RecipeCreateScreen Widget Tests should show validation error for invalid portions 00:13 +203 -3: /home/alexander/eatvio_app/test/features/create_recipe/widget_test.dart: RecipeCreateScreen Widget Tests should show validation error for invalid portions 00:13 +204 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - удаляет рецепт из избранного 00:13 +205 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - удаляет рецепт из избранного 00:13 +206 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - удаляет рецепт из избранного 00:13 +207 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - удаляет рецепт из избранного 00:13 +208 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - удаляет рецепт из избранного 00:13 +209 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - удаляет рецепт из избранного 00:13 +210 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - удаляет рецепт из избранного 00:13 +211 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - удаляет рецепт из избранного 00:13 +212 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - удаляет рецепт из избранного 00:13 +213 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - удаляет рецепт из избранного 00:13 +214 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - удаляет рецепт из избранного 00:13 +215 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - удаляет рецепт из избранного 00:13 +216 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - удаляет рецепт из избранного 00:13 +217 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - удаляет рецепт из избранного 00:13 +218 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - удаляет рецепт из избранного 00:13 +219 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - удаляет рецепт из избранного 00:14 +219 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - удаляет рецепт из избранного 00:14 +220 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - удаляет рецепт из избранного 00:14 +221 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - удаляет рецепт из избранного 00:14 +222 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - удаляет рецепт из избранного 00:14 +223 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - удаляет рецепт из избранного 00:14 +224 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - удаляет рецепт из избранного 00:14 +225 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - удаляет рецепт из избранного 00:14 +226 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - удаляет рецепт из избранного 00:14 +227 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - удаляет рецепт из избранного 00:14 +228 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - удаляет рецепт из избранного 00:14 +229 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - удаляет рецепт из избранного 00:14 +230 -3: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display RecipesWidget with recipe title 00:14 +231 -3: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display RecipesWidget with recipe title 00:14 +232 -3: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display RecipesWidget with recipe title 00:14 +233 -3: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display RecipesWidget with recipe title 00:14 +234 -3: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display RecipesWidget with recipe title 00:14 +235 -3: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display RecipesWidget with recipe title 00:14 +236 -3: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display RecipesWidget with recipe title 00:14 +237 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - с recipeIndexInFeed параметром 00:14 +238 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - с recipeIndexInFeed параметром 00:14 +239 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - с recipeIndexInFeed параметром 00:14 +240 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - с recipeIndexInFeed параметром 00:14 +241 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - с recipeIndexInFeed параметром 00:14 +242 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - с recipeIndexInFeed параметром 00:14 +243 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - с recipeIndexInFeed параметром 00:14 +244 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - с recipeIndexInFeed параметром 00:14 +245 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - с recipeIndexInFeed параметром 00:14 +246 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - с recipeIndexInFeed параметром 00:14 +247 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - с recipeIndexInFeed параметром 00:14 +248 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - с recipeIndexInFeed параметром 00:14 +249 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - с recipeIndexInFeed параметром 00:14 +250 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - с recipeIndexInFeed параметром 00:14 +251 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - с recipeIndexInFeed параметром 00:14 +252 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - с recipeIndexInFeed параметром 00:14 +253 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - с recipeIndexInFeed параметром 00:15 +253 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - с recipeIndexInFeed параметром 00:15 +254 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - с recipeIndexInFeed параметром 00:15 +255 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - с recipeIndexInFeed параметром 00:15 +256 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - с recipeIndexInFeed параметром 00:15 +257 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - с recipeIndexInFeed параметром 00:15 +258 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - с recipeIndexInFeed параметром 00:15 +259 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - с recipeIndexInFeed параметром 00:15 +260 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - с recipeIndexInFeed параметром 00:15 +261 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - с recipeIndexInFeed параметром 00:15 +262 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - с recipeIndexInFeed параметром 00:15 +263 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - с recipeIndexInFeed параметром 00:15 +264 -3: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display Recipes widget with multiple recipes 00:15 +265 -3: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display Recipes widget with multiple recipes 00:15 +266 -3: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display Recipes widget with multiple recipes 00:15 +267 -3: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display Recipes widget with multiple recipes 00:15 +268 -3: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display Recipes widget with multiple recipes 00:15 +269 -3: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display Recipes widget with multiple recipes 00:15 +270 -3: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display Recipes widget with multiple recipes 00:15 +271 -3: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display Recipes widget with multiple recipes 00:15 +272 -3: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display Recipes widget with multiple recipes 00:15 +273 -3: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display Recipes widget with multiple recipes 00:15 +274 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:15 +275 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:15 +276 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:15 +277 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:15 +278 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:15 +279 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:15 +280 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:15 +281 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:15 +282 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:15 +283 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:15 +284 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:15 +285 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:15 +286 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:15 +287 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:15 +288 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:15 +289 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:15 +290 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:15 +291 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:15 +292 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:15 +293 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:15 +294 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:15 +295 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:15 +296 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:15 +297 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:15 +298 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:16 +298 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:16 +299 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:16 +300 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:16 +301 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:16 +302 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:16 +303 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:16 +304 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:16 +305 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:16 +306 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:16 +307 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:16 +308 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:16 +309 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:16 +310 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:16 +310 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:16 +311 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:16 +312 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:16 +313 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:16 +314 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:16 +315 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:16 +316 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:16 +317 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:16 +318 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:16 +319 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:16 +320 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:16 +321 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:16 +322 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:16 +323 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:16 +324 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:16 +325 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:16 +326 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:16 +327 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:16 +328 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:16 +329 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:16 +330 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:16 +331 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:17 +331 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:17 +332 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:17 +333 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:17 +334 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:17 +335 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:17 +336 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:17 +337 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:17 +338 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:17 +339 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:17 +340 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:17 +341 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:17 +342 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:17 +343 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:17 +344 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:17 +345 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:17 +346 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:17 +347 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:17 +348 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:17 +349 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:17 +350 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:17 +351 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:17 +352 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:17 +352 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - обработка ошибки 00:17 +353 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - обработка ошибки 00:17 +353 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - с recipeIndex параметром 00:17 +354 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - с recipeIndex параметром 00:17 +355 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - с recipeIndex параметром 00:17 +356 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - с recipeIndex параметром 00:17 +357 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - с recipeIndex параметром 00:17 +358 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - с recipeIndex параметром 00:17 +359 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - с recipeIndex параметром 00:18 +359 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - с recipeIndex параметром 00:18 +360 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - с recipeIndex параметром 00:18 +361 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - с recipeIndex параметром 00:18 +362 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - с recipeIndex параметром 00:18 +363 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - с recipeIndex параметром 00:18 +364 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - с recipeIndex параметром 00:18 +365 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - с recipeIndex параметром 00:18 +366 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - с recipeIndex параметром 00:18 +367 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - с recipeIndex параметром 00:18 +368 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - с recipeIndex параметром 00:18 +369 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - с recipeIndex параметром 00:18 +370 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - с recipeIndex параметром 00:18 +371 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - с recipeIndex параметром 00:18 +372 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - с recipeIndex параметром 00:18 +373 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - с recipeIndex параметром 00:18 +374 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - с recipeIndex параметром 00:18 +375 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - с recipeIndex параметром 00:18 +376 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - с recipeIndex параметром 00:18 +377 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - с recipeIndex параметром 00:18 +378 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - с recipeIndex параметром 00:18 +379 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - с recipeIndex параметром 00:18 +380 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/feed_cubit/feed_cubit_test.dart: FeedCubit loadFeedBlogPostList - успешная загрузка постов 00:18 +381 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/feed_cubit/feed_cubit_test.dart: FeedCubit loadFeedBlogPostList - успешная загрузка постов 00:18 +382 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/feed_cubit/feed_cubit_test.dart: FeedCubit loadFeedBlogPostList - успешная загрузка постов 00:18 +383 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/feed_cubit/feed_cubit_test.dart: FeedCubit loadFeedBlogPostList - успешная загрузка постов 00:18 +384 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/feed_cubit/feed_cubit_test.dart: FeedCubit loadFeedBlogPostList - успешная загрузка постов 00:18 +385 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/feed_cubit/feed_cubit_test.dart: FeedCubit loadFeedBlogPostList - успешная загрузка постов 00:19 +386 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit loadDiets - обработка ошибки 00:19 +387 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit loadDiets - обработка ошибки 00:19 +388 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/feed_cubit_test.dart: FeedCubit начальное состояние - loading 00:19 +389 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/feed_cubit/feed_cubit_test.dart: FeedCubit loadFeedBlogPostList - обработка ошибки 00:19 +390 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit loadNextPage - загружает следующую страницу 00:19 +391 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit loadNextPage - загружает следующую страницу 00:19 +392 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit loadNextPage - загружает следующую страницу 00:19 +393 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit loadNextPage - загружает следующую страницу 00:19 +394 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit loadNextPage - загружает следующую страницу 00:19 +395 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit loadNextPage - загружает следующую страницу 00:19 +396 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit loadNextPage - загружает следующую страницу 00:19 +397 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit loadNextPage - загружает следующую страницу 00:19 +398 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit loadNextPage - загружает следующую страницу 00:19 +399 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/feed_cubit_test.dart: FeedCubit loadFeedBlogPostList - обрабатывает ошибку 00:19 +400 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/feed_cubit_test.dart: FeedCubit loadFeedBlogPostList - обрабатывает ошибку 00:19 +401 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit loadNextPage - не загружает если уже загружается 00:19 +402 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit loadNextPage - не загружает если уже загружается 00:20 +402 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit loadNextPage - не загружает если уже загружается 00:20 +403 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit loadNextPage - не загружает если уже загружается 00:20 +404 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit loadNextPage - не загружает если уже загружается 00:20 +405 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit loadNextPage - не загружает если уже загружается 00:20 +406 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit loadNextPage - не загружает если уже загружается 00:20 +407 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit loadNextPage - не загружает если уже загружается 00:20 +408 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit loadNextPage - не загружает если уже загружается 00:20 +409 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit loadNextPage - не загружает если уже загружается 00:20 +410 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit loadNextPage - не загружает если уже загружается 00:20 +411 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit loadNextPage - не загружает если уже загружается 00:20 +412 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit loadNextPage - не загружает если уже загружается 00:20 +413 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/feed_cubit_test.dart: FeedCubit loadNextPage - не загружает если уже на последней странице 00:20 +414 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/plans_cubit/plans_cubit_test.dart: PlansCubit loadPlans - обработка ошибки 00:20 +415 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit likeDiet - добавляет лайк 00:20 +416 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit likeDiet - добавляет лайк 00:20 +417 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit likeDiet - добавляет лайк 00:20 +418 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit likeDiet - добавляет лайк 00:20 +419 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/feed_cubit_test.dart: FeedCubit loadNextPage - не загружает если уже загружается 00:20 +420 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/feed_cubit_test.dart: FeedCubit loadNextPage - не загружает если уже загружается 00:20 +421 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/plans_cubit/plans_cubit_test.dart: PlansCubit loadNextPage - загружает следующую страницу 00:21 +421 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/plans_cubit/plans_cubit_test.dart: PlansCubit loadNextPage - загружает следующую страницу 00:21 +422 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/plans_cubit/plans_cubit_test.dart: PlansCubit loadNextPage - загружает следующую страницу 00:21 +423 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/plans_cubit/plans_cubit_test.dart: PlansCubit loadNextPage - загружает следующую страницу 00:21 +424 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/plans_cubit/plans_cubit_test.dart: PlansCubit loadNextPage - загружает следующую страницу 00:21 +425 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit likeDiet - убирает лайк 00:21 +426 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/recipes_cubit/recipes_cubit_test.dart: RecipesCubit loadNextPage - загружает следующую страницу 00:21 +427 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/recipes_cubit/recipes_cubit_test.dart: RecipesCubit loadNextPage - загружает следующую страницу 00:21 +428 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/plans_cubit/plans_cubit_test.dart: PlansCubit loadNextPage - не загружает если уже загружается 00:21 +428 ~1 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/plans_cubit/plans_cubit_test.dart: PlansCubit loadNextPage - не загружает если уже загружается 00:21 +429 ~1 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/plans_cubit/plans_cubit_test.dart: PlansCubit loadNextPage - не загружает если уже загружается 00:21 +430 ~1 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit addDietToFavorite - добавляет диету в избранное 00:21 +431 ~1 -3: /home/alexander/eatvio_app/test/features/feed/presentation/pages/feed_screen_smoke_test.dart: FeedScreen smoke renders FeedScreen with all tabs 00:21 +432 ~1 -3: /home/alexander/eatvio_app/test/features/feed/presentation/pages/feed_screen_smoke_test.dart: FeedScreen smoke renders FeedScreen with all tabs 00:22 +432 ~1 -3: /home/alexander/eatvio_app/test/features/feed/presentation/pages/feed_screen_smoke_test.dart: FeedScreen smoke renders FeedScreen with all tabs 00:22 +433 ~1 -3: /home/alexander/eatvio_app/test/features/feed/presentation/pages/feed_screen_smoke_test.dart: FeedScreen smoke renders FeedScreen with all tabs 00:22 +434 ~1 -3: /home/alexander/eatvio_app/test/features/feed/presentation/pages/feed_screen_smoke_test.dart: FeedScreen smoke renders FeedScreen with all tabs 00:22 +435 ~1 -3: /home/alexander/eatvio_app/test/features/feed/presentation/pages/feed_screen_smoke_test.dart: FeedScreen smoke renders FeedScreen with all tabs 00:22 +436 ~1 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit removeDietFromFavorite - убирает диету из избранного 00:22 +437 ~1 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit removeDietFromFavorite - убирает диету из избранного 00:22 +438 ~1 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit removeDietFromFavorite - убирает диету из избранного 00:22 +439 ~1 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit removeDietFromFavorite - убирает диету из избранного 00:22 +440 ~1 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit removeDietFromFavorite - убирает диету из избранного 00:22 +441 ~1 -3: /home/alexander/eatvio_app/test/features/feed/presentation/widgets/blog_posts_test.dart: BlogPosts smoke renders blog posts widget 00:22 +442 ~1 -3: /home/alexander/eatvio_app/test/features/feed/presentation/widgets/blog_posts_test.dart: BlogPosts smoke renders blog posts widget 00:22 +443 ~1 -3: /home/alexander/eatvio_app/test/features/feed/presentation/widgets/blog_posts_test.dart: BlogPosts smoke renders blog posts widget 00:22 +444 ~1 -3: /home/alexander/eatvio_app/test/features/feed/presentation/widgets/blog_posts_test.dart: BlogPosts smoke renders blog posts widget 00:22 +445 ~1 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/recipes_cubit/recipes_cubit_test.dart: RecipesCubit removeRecipeFromFavorite - удаляет рецепт из избранного 00:22 +446 ~1 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/recipes_cubit/recipes_cubit_test.dart: RecipesCubit removeRecipeFromFavorite - удаляет рецепт из избранного 00:22 +447 ~1 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/recipes_cubit/recipes_cubit_test.dart: RecipesCubit removeRecipeFromFavorite - удаляет рецепт из избранного 00:22 +448 ~1 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/recipes_cubit/recipes_cubit_test.dart: RecipesCubit removeRecipeFromFavorite - удаляет рецепт из избранного 00:23 +448 ~1 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/recipes_cubit/recipes_cubit_test.dart: RecipesCubit removeRecipeFromFavorite - удаляет рецепт из избранного 00:23 +449 ~1 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/recipes_cubit/recipes_cubit_test.dart: RecipesCubit removeRecipeFromFavorite - удаляет рецепт из избранного 00:23 +450 ~1 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/recipes_cubit/recipes_cubit_test.dart: RecipesCubit removeRecipeFromFavorite - удаляет рецепт из избранного 00:23 +451 ~1 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/recipes_cubit/recipes_cubit_test.dart: RecipesCubit removeRecipeFromFavorite - удаляет рецепт из избранного 00:23 +452 ~1 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/recipes_cubit/recipes_cubit_test.dart: RecipesCubit removeRecipeFromFavorite - удаляет рецепт из избранного 00:23 +453 ~1 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/recipes_cubit/recipes_cubit_test.dart: RecipesCubit removeRecipeFromFavorite - удаляет рецепт из избранного 00:23 +454 ~1 -3: /home/alexander/eatvio_app/test/features/feed/presentation/widgets/chip_group_test.dart: ChipGroup smoke renders chip group 00:23 +455 ~1 -3: /home/alexander/eatvio_app/test/features/feed/presentation/widgets/chip_group_test.dart: ChipGroup smoke renders chip group 00:23 +456 ~1 -3: /home/alexander/eatvio_app/test/features/feed/presentation/widgets/chip_group_test.dart: ChipGroup smoke renders chip group 00:23 +456 ~1 -3: /home/alexander/eatvio_app/test/features/feed/presentation/widgets/basic_widget_test.dart: Feed Basic Widget Tests should display BlogPosts widget with loaded state diff --git a/test_results.txt b/test_results.txt new file mode 100644 index 00000000..6558de97 --- /dev/null +++ b/test_results.txt @@ -0,0 +1,663 @@ + 00:00 +0: loading /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart 00:01 +0: loading /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart 00:02 +0: loading /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart 00:02 +0: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should create CreateRecipeCubit successfully 00:02 +1: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should create CreateRecipeCubit successfully 00:02 +1: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display basic form fields 00:02 +2: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display basic form fields 00:02 +3: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display basic form fields 00:02 +4: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display basic form fields 00:02 +5: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display basic form fields 00:02 +6: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display basic form fields 00:02 +7: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display basic form fields 00:02 +8: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display basic form fields 00:02 +9: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display basic form fields 00:02 +10: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display basic form fields 00:02 +11: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display basic form fields 00:02 +12: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display basic form fields 00:02 +13: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display basic form fields 00:02 +14: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display basic form fields 00:03 +14: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display basic form fields 00:03 +15: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display basic form fields 00:03 +16: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display basic form fields 00:03 +17: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display basic form fields 00:03 +18: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display basic form fields 00:03 +19: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display basic form fields 00:03 +20: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display basic form fields 00:03 +21: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display basic form fields 00:03 +21: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should update title when user types in title field 00:03 +22: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should update title when user types in title field 00:03 +22: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should update description when user types in description field 00:03 +23: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should update description when user types in description field 00:03 +23: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should update portions when user types in portions field 00:03 +24: /home/alexander/eatvio_app/test/features/create_recipe/models/recipe_create_model_test.dart: RecipeType enum has all expected values 00:03 +25: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should update calories when user types in calories field 00:03 +26: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should update calories when user types in calories field 00:03 +27: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should update calories when user types in calories field 00:03 +28: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should update calories when user types in calories field 00:03 +29: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should update calories when user types in calories field 00:03 +29: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display form labels correctly 00:03 +30: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display form labels correctly 00:03 +30: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display placeholder text in title field 00:03 +31: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display placeholder text in title field 00:03 +31: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display publish button 00:03 +32: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display publish button 00:03 +32: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should handle button tap 00:03 +33: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should handle button tap 00:03 +34: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should handle button tap 00:03 +35: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should handle button tap 00:03 +36: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should handle button tap 00:03 +37: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should handle button tap 00:03 +38: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should handle button tap 00:03 +38: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display loading state 00:03 +39: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display loading state 00:03 +39: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display error message 00:03 +40: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display error message 00:03 +40: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display success message 00:04 +40: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display success message 00:04 +41: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display success message 00:04 +41: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should validate required fields 00:04 +42: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should validate required fields 00:04 +42: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display form with all required fields 00:04 +43: /home/alexander/eatvio_app/test/features/create_recipe/basic_widget_test.dart: Recipe Creation Basic Widget Tests should display form with all required fields 00:04 +43: loading /home/alexander/eatvio_app/test/features/create_recipe/models/recipe_tag_model_test.dart 00:04 +43: /home/alexander/eatvio_app/test/features/create_recipe/models/recipe_tag_model_test.dart: RecipeTag создает RecipeTag с правильными значениями 00:04 +44: /home/alexander/eatvio_app/test/features/create_recipe/models/recipe_tag_model_test.dart: RecipeTag создает RecipeTag с правильными значениями 00:04 +44: /home/alexander/eatvio_app/test/features/create_recipe/models/recipe_tag_model_test.dart: RecipeTagModel создает RecipeTagModel с правильными значениями 00:04 +45: /home/alexander/eatvio_app/test/features/create_recipe/models/recipe_tag_model_test.dart: RecipeTagModel создает RecipeTagModel с правильными значениями 00:04 +45: /home/alexander/eatvio_app/test/features/create_recipe/models/recipe_tag_model_test.dart: RecipeTagModel fromJson создает RecipeTagModel из JSON 00:04 +46: /home/alexander/eatvio_app/test/features/create_recipe/models/recipe_tag_model_test.dart: RecipeTagModel fromJson создает RecipeTagModel из JSON 00:04 +46: /home/alexander/eatvio_app/test/features/create_recipe/models/recipe_tag_model_test.dart: RecipeTag fromJson создает RecipeTag из JSON 00:04 +47: /home/alexander/eatvio_app/test/features/create_recipe/models/recipe_tag_model_test.dart: RecipeTag fromJson создает RecipeTag из JSON 00:04 +47: /home/alexander/eatvio_app/test/features/create_recipe/models/recipe_tag_model_test.dart: RecipeTag toJson serializes RecipeTag correctly 00:04 +48: /home/alexander/eatvio_app/test/features/create_recipe/models/recipe_tag_model_test.dart: RecipeTag toJson serializes RecipeTag correctly 00:04 +48: /home/alexander/eatvio_app/test/features/create_recipe/models/recipe_tag_model_test.dart: RecipeTagModel toJson serializes RecipeTagModel correctly 00:04 +49: /home/alexander/eatvio_app/test/features/create_recipe/models/recipe_tag_model_test.dart: RecipeTagModel toJson serializes RecipeTagModel correctly 00:04 +49: loading /home/alexander/eatvio_app/test/features/create_recipe/models/step_test.dart 00:04 +49: /home/alexander/eatvio_app/test/features/create_recipe/models/step_test.dart: Step создает Step с правильными значениями 00:04 +50: /home/alexander/eatvio_app/test/features/create_recipe/models/step_test.dart: Step создает Step с правильными значениями 00:04 +50: /home/alexander/eatvio_app/test/features/create_recipe/models/step_test.dart: Step создает Step с null значениями 00:04 +51: /home/alexander/eatvio_app/test/features/create_recipe/models/step_test.dart: Step создает Step с null значениями 00:04 +51: /home/alexander/eatvio_app/test/features/create_recipe/models/step_test.dart: Step fromJson создает Step из JSON 00:04 +52: /home/alexander/eatvio_app/test/features/create_recipe/models/step_test.dart: Step fromJson создает Step из JSON 00:04 +52: /home/alexander/eatvio_app/test/features/create_recipe/models/step_test.dart: Step fromJson создает Step из JSON с null значениями 00:04 +53: /home/alexander/eatvio_app/test/features/create_recipe/models/step_test.dart: Step fromJson создает Step из JSON с null значениями 00:04 +53: /home/alexander/eatvio_app/test/features/create_recipe/models/step_test.dart: Step toJson serializes Step correctly 00:04 +54: /home/alexander/eatvio_app/test/features/create_recipe/models/step_test.dart: Step toJson serializes Step correctly 00:04 +54: /home/alexander/eatvio_app/test/features/create_recipe/models/step_test.dart: Step toJson serializes Step with null values correctly 00:04 +55: /home/alexander/eatvio_app/test/features/create_recipe/models/step_test.dart: Step toJson serializes Step with null values correctly 00:04 +55: loading /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart 00:04 +55: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit initial state has default values 00:04 +56: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit initial state has default values 00:04 +56: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit addStep adds a new empty step 00:04 +57: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit addStep adds a new empty step 00:04 +57: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit removeStep removes provided step 00:04 +58: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit removeStep removes provided step 00:04 +58: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit addIngredient appends ingredient to list 00:04 +59: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit addIngredient appends ingredient to list 00:04 +59: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit removeIngredient removes ingredient by index 00:04 +60: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit removeIngredient removes ingredient by index 00:04 +60: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit updateIngredient replaces ingredient at index 00:04 +61: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit updateIngredient replaces ingredient at index 00:04 +61: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit replaceIngredientPosition moves ingredient 00:04 +62: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit replaceIngredientPosition moves ingredient 00:04 +62: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... replaceTitle and replaceDescription update entity 00:04 +63: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... replaceTitle and replaceDescription update entity 00:04 +63: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit addTag and removeTag work correctly 00:04 +64: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit addTag and removeTag work correctly 00:04 +64: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit portion and energy setters update entity 00:04 +65: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit portion and energy setters update entity 00:04 +65: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit replaceVideoUrl and replaceImage update entity 00:04 +66: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit replaceVideoUrl and replaceImage update entity 00:04 +66: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... replaceDescriptionInStep and replaceImageInStep update step 00:04 +67: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... replaceDescriptionInStep and replaceImageInStep update step 00:04 +67: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit disableLoading sets loading to false 00:04 +68: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit disableLoading sets loading to false 00:04 +68: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit changeType updates type of recipe 00:04 +69: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit changeType updates type of recipe 00:04 +69: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... replaceTitle with empty string sets title to null 00:04 +70: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... replaceTitle with empty string sets title to null 00:04 +70: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... with empty string sets description to null 00:04 +71: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... with empty string sets description to null 00:04 +71: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit publishRecipe - успешно публикует рецепт 00:04 +72: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit publishRecipe - успешно публикует рецепт 00:04 +72: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit publishRecipe - обновляет рецепт 00:04 +73: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit publishRecipe - обновляет рецепт 00:04 +73: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit publishRecipe - обрабатывает ошибку 00:04 +74: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit publishRecipe - обрабатывает ошибку 00:04 +74: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit deleteRecipe - успешно удаляет рецепт 00:04 +75: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit deleteRecipe - успешно удаляет рецепт 00:04 +75: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit deleteRecipe - обрабатывает ошибку 00:04 +76: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit deleteRecipe - обрабатывает ошибку 00:04 +76: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... getGlossaryListByQuery - получает список глоссария 00:04 +77: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... getGlossaryListByQuery - получает список глоссария 00:04 +77: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit validateTitle - возвращает false для null 00:04 +78: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit validateTitle - возвращает false для null 00:04 +78: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... validateTitle - возвращает false для пустой строки 00:04 +79: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... validateTitle - возвращает false для пустой строки 00:04 +79: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... validateTitle - возвращает false для короткого названия 00:04 +80: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... validateTitle - возвращает false для короткого названия 00:04 +80: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... validateTitle - возвращает true для валидного названия 00:04 +81: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... validateTitle - возвращает true для валидного названия 00:04 +81: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... validateDescription - возвращает false для null 00:04 +82: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... validateDescription - возвращает false для null 00:04 +82: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... validateDescription - возвращает false для пустой строки 00:04 +83: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... validateDescription - возвращает false для пустой строки 00:04 +83: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... - возвращает false для короткого описания 00:04 +84: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... - возвращает false для короткого описания 00:04 +84: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... validateDescription - возвращает true для валидного описания 00:04 +85: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... validateDescription - возвращает true для валидного описания 00:04 +85: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit validateServings - возвращает false для null 00:04 +86: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit validateServings - возвращает false для null 00:04 +86: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... validateServings - возвращает true для валидного значения 00:04 +87: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... validateServings - возвращает true для валидного значения 00:04 +87: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... validateEnergyPerServing - возвращает false для null 00:04 +88: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... validateEnergyPerServing - возвращает false для null 00:04 +88: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... - возвращает true для валидного значения 00:04 +89: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... - возвращает true для валидного значения 00:04 +89: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... - возвращает false когда оба поля null 00:04 +90: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... - возвращает false когда оба поля null 00:04 +90: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... - возвращает true когда хотя бы одно поле заполнено 00:04 +91: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... - возвращает true когда хотя бы одно поле заполнено 00:04 +91: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... - возвращает false когда одно из полей null 00:04 +92: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... - возвращает false когда одно из полей null 00:04 +92: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... - возвращает true когда оба поля заполнены 00:04 +93: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... - возвращает true когда оба поля заполнены 00:04 +93: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit validateImage - возвращает false для null 00:05 +93: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit validateImage - возвращает false для null 00:05 +94: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: CreateRecipeCubit validateImage - возвращает false для null 00:05 +94: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... validateImage - возвращает true для валидного изображения 00:05 +95: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... validateImage - возвращает true для валидного изображения 00:05 +95: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... validateAll - возвращает false когда не все поля валидны 00:05 +96: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... validateAll - возвращает false когда не все поля валидны 00:05 +96: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... validateAll - возвращает true когда все поля валидны 00:05 +97: /home/alexander/eatvio_app/test/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit_test.dart: ... validateAll - возвращает true когда все поля валидны 00:05 +97: loading /home/alexander/eatvio_app/test/features/create_recipe/presentation/pages/recipe_create_screen_test.dart 00:05 +97: /home/alexander/eatvio_app/test/features/create_recipe/presentation/pages/recipe_create_screen_test.dart: RecipeCreateScreen smoke (setUpAll) 00:05 +97: /home/alexander/eatvio_app/test/features/create_recipe/presentation/pages/recipe_create_screen_test.dart: RecipeCreateScreen smoke renders recipe create screen - initial state 00:06 +97: /home/alexander/eatvio_app/test/features/create_recipe/presentation/pages/recipe_create_screen_test.dart: RecipeCreateScreen smoke renders recipe create screen - initial state 00:06 +98: /home/alexander/eatvio_app/test/features/create_recipe/presentation/pages/recipe_create_screen_test.dart: RecipeCreateScreen smoke renders recipe create screen - initial state 00:06 +99: /home/alexander/eatvio_app/test/features/create_recipe/presentation/pages/recipe_create_screen_test.dart: RecipeCreateScreen smoke renders recipe create screen - initial state 00:06 +100: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/portion_widget_test.dart: PortionWidget smoke renders portion widget 00:06 +101: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/portion_widget_test.dart: PortionWidget smoke renders portion widget 00:07 +101: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/portion_widget_test.dart: PortionWidget smoke renders portion widget 00:07 +102: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/portion_widget_test.dart: PortionWidget smoke renders portion widget 00:07 +103: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:07 +104: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:07 +105: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:07 +106: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:07 +107: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:07 +108: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:07 +109: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:07 +110: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:07 +111: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:07 +112: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:07 +113: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:07 +114: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:07 +115: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:07 +116: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:07 +117: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:07 +118: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:07 +119: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:07 +120: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:07 +121: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:07 +122: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:07 +123: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:07 +124: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:07 +125: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:07 +126: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:07 +127: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:07 +128: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:07 +129: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:07 +130: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/steps_widget_test.dart: StepsWidget smoke renders steps widget 00:07 +131: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/time_of_cook_widget_test.dart: TimeOfCookWidget smoke renders time of cook widget 00:07 +132: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/time_of_cook_widget_test.dart: TimeOfCookWidget smoke renders time of cook widget 00:07 +133: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/time_of_cook_widget_test.dart: TimeOfCookWidget smoke renders time of cook widget 00:07 +134: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/time_of_cook_widget_test.dart: TimeOfCookWidget smoke renders time of cook widget 00:07 +135: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/time_of_cook_widget_test.dart: TimeOfCookWidget smoke renders time of cook widget 00:07 +136: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/time_of_cook_widget_test.dart: TimeOfCookWidget smoke renders time of cook widget 00:07 +137: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/time_of_cook_widget_test.dart: TimeOfCookWidget smoke renders time of cook widget 00:08 +137: /home/alexander/eatvio_app/test/features/create_recipe/presentation/widgets/time_of_cook_widget_test.dart: TimeOfCookWidget smoke renders time of cook widget 00:08 +138: /home/alexander/eatvio_app/test/features/create_recipe/simple_widget_test.dart: Recipe Creation Widget Tests should display InfoAboutRecipeWidget with all form fields 00:08 +139: /home/alexander/eatvio_app/test/features/create_recipe/simple_widget_test.dart: Recipe Creation Widget Tests should display InfoAboutRecipeWidget with all form fields 00:08 +140: /home/alexander/eatvio_app/test/features/create_recipe/simple_widget_test.dart: Recipe Creation Widget Tests should display InfoAboutRecipeWidget with all form fields 00:08 +141: /home/alexander/eatvio_app/test/features/create_recipe/simple_widget_test.dart: Recipe Creation Widget Tests should display InfoAboutRecipeWidget with all form fields 00:08 +142: /home/alexander/eatvio_app/test/features/create_recipe/simple_widget_test.dart: Recipe Creation Widget Tests should display InfoAboutRecipeWidget with all form fields 00:08 +143: /home/alexander/eatvio_app/test/features/create_recipe/simple_widget_test.dart: Recipe Creation Widget Tests should display InfoAboutRecipeWidget with all form fields 00:08 +144: /home/alexander/eatvio_app/test/features/create_recipe/simple_widget_test.dart: Recipe Creation Widget Tests should display InfoAboutRecipeWidget with all form fields 00:08 +145: /home/alexander/eatvio_app/test/features/create_recipe/simple_widget_test.dart: Recipe Creation Widget Tests should display InfoAboutRecipeWidget with all form fields 00:08 +146: /home/alexander/eatvio_app/test/features/create_recipe/simple_widget_test.dart: Recipe Creation Widget Tests should display InfoAboutRecipeWidget with all form fields 00:09 +146: /home/alexander/eatvio_app/test/features/create_recipe/simple_widget_test.dart: Recipe Creation Widget Tests should display InfoAboutRecipeWidget with all form fields 00:09 +147: /home/alexander/eatvio_app/test/features/create_recipe/widget_test.dart: RecipeCreateScreen Widget Tests should display all required form fields 00:09 +148: /home/alexander/eatvio_app/test/features/create_recipe/widget_test.dart: RecipeCreateScreen Widget Tests should display all required form fields 00:09 +149: /home/alexander/eatvio_app/test/features/create_recipe/widget_test.dart: RecipeCreateScreen Widget Tests should display all required form fields 00:09 +150: /home/alexander/eatvio_app/test/features/create_recipe/widget_test.dart: RecipeCreateScreen Widget Tests should display all required form fields 00:09 +151: /home/alexander/eatvio_app/test/features/create_recipe/widget_test.dart: RecipeCreateScreen Widget Tests should display all required form fields 00:09 +152: /home/alexander/eatvio_app/test/features/create_recipe/widget_test.dart: RecipeCreateScreen Widget Tests should display all required form fields 00:09 +153: /home/alexander/eatvio_app/test/features/create_recipe/widget_test.dart: RecipeCreateScreen Widget Tests should display all required form fields 00:09 +154: /home/alexander/eatvio_app/test/features/create_recipe/widget_test.dart: RecipeCreateScreen Widget Tests should display all required form fields 00:09 +155: /home/alexander/eatvio_app/test/features/create_recipe/widget_test.dart: RecipeCreateScreen Widget Tests should display all required form fields 00:09 +156: /home/alexander/eatvio_app/test/features/create_recipe/widget_test.dart: RecipeCreateScreen Widget Tests should display all required form fields 00:09 +157: /home/alexander/eatvio_app/test/features/create_recipe/widget_test.dart: RecipeCreateScreen Widget Tests should display all required form fields 00:09 +158: /home/alexander/eatvio_app/test/features/create_recipe/widget_test.dart: RecipeCreateScreen Widget Tests should display all required form fields 00:09 +159: /home/alexander/eatvio_app/test/features/create_recipe/widget_test.dart: RecipeCreateScreen Widget Tests should display all required form fields 00:09 +160: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - лайк рецепта 00:09 +161: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - лайк рецепта 00:10 +161: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - лайк рецепта 00:10 +162: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - лайк рецепта 00:10 +163: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - лайк рецепта 00:10 +164: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - лайк рецепта 00:10 +165: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - лайк рецепта 00:10 +166: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - лайк рецепта 00:10 +167: /home/alexander/eatvio_app/test/features/recipe/presentation/pages/about_recipe_screen_smoke_test.dart: AboutRecipeScreen smoke renders loading state 00:10 +168: /home/alexander/eatvio_app/test/features/recipe/presentation/pages/about_recipe_screen_smoke_test.dart: AboutRecipeScreen smoke renders loading state 00:10 +169: /home/alexander/eatvio_app/test/features/recipe/presentation/pages/about_recipe_screen_smoke_test.dart: AboutRecipeScreen smoke renders loading state 00:10 +170: /home/alexander/eatvio_app/test/features/recipe/presentation/pages/about_recipe_screen_smoke_test.dart: AboutRecipeScreen smoke renders loading state 00:10 +171: /home/alexander/eatvio_app/test/features/recipe/presentation/pages/about_recipe_screen_smoke_test.dart: AboutRecipeScreen smoke renders loading state 00:10 +172: /home/alexander/eatvio_app/test/features/recipe/presentation/pages/about_recipe_screen_smoke_test.dart: AboutRecipeScreen smoke renders loading state 00:10 +173: /home/alexander/eatvio_app/test/features/recipe/presentation/pages/about_recipe_screen_smoke_test.dart: AboutRecipeScreen smoke renders loading state 00:10 +174: /home/alexander/eatvio_app/test/features/recipe/presentation/pages/about_recipe_screen_smoke_test.dart: AboutRecipeScreen smoke renders loading state 00:10 +174: /home/alexander/eatvio_app/test/features/recipe/presentation/pages/my_recipes_screen_smoke_test.dart: MyRecipesScreen smoke renders my recipes screen +══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════ +The following assertion was thrown building MyRecipesTab(dirty, dependencies: +[_LocalizationsScope-[GlobalKey#5add2]]): +No instance of S present in the widget tree. Did you add S.delegate in localizationsDelegates? +'package:eatvio_app/generated/l10n.dart': +Failed assertion: line 47 pos 7: 'instance != null' + +The relevant error-causing widget was: + MyRecipesTab + MyRecipesTab:file:///home/alexander/eatvio_app/lib/features/recipe/presentation/pages/my_recipes_screen.dart:38:17 + +When the exception was thrown, this was the stack: +#2 S.of (package:eatvio_app/generated/l10n.dart:47:7) +#3 MyRecipesTab.build (package:eatvio_app/features/recipe/presentation/widgets/my_recipes_tab.dart:19:17) +#4 StatelessElement.build (package:flutter/src/widgets/framework.dart:5892:49) +#5 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5820:15) +#6 Element.rebuild (package:flutter/src/widgets/framework.dart:5532:7) +#7 ComponentElement._firstBuild (package:flutter/src/widgets/framework.dart:5802:5) +#8 ComponentElement.mount (package:flutter/src/widgets/framework.dart:5796:5) +#9 Element.inflateWidget (package:flutter/src/widgets/framework.dart:4590:20) +#10 MultiChildRenderObjectElement.inflateWidget (package:flutter/src/widgets/framework.dart:7269:36) +#11 MultiChildRenderObjectElement.mount (package:flutter/src/widgets/framework.dart:7285:32) +... Normal element mounting (9 frames) +#20 SingleChildWidgetElementMixin.mount (package:nested/nested.dart:222:11) +... Normal element mounting (15 frames) +#35 _InheritedProviderScopeElement.mount (package:provider/src/inherited_provider.dart:424:11) +... Normal element mounting (7 frames) +#42 SingleChildWidgetElementMixin.mount (package:nested/nested.dart:222:11) +... Normal element mounting (7 frames) +#49 SingleChildWidgetElementMixin.mount (package:nested/nested.dart:222:11) +... Normal element mounting (25 frames) +#74 Element.inflateWidget (package:flutter/src/widgets/framework.dart:4590:20) +#75 MultiChildRenderObjectElement.inflateWidget (package:flutter/src/widgets/framework.dart:7269:36) +#76 MultiChildRenderObjectElement.mount (package:flutter/src/widgets/framework.dart:7285:32) +... Normal element mounting (370 frames) +#446 Element.inflateWidget (package:flutter/src/widgets/framework.dart:4590:20) +#447 MultiChildRenderObjectElement.inflateWidget (package:flutter/src/widgets/framework.dart:7269:36) +#448 MultiChildRenderObjectElement.mount (package:flutter/src/widgets/framework.dart:7285:32) +... Normal element mounting (465 frames) +#913 Element.inflateWidget (package:flutter/src/widgets/framework.dart:4590:20) +#914 Element.updateChild (package:flutter/src/widgets/framework.dart:4053:20) +#915 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5844:16) +#916 Element.rebuild (package:flutter/src/widgets/framework.dart:5532:7) +#917 ProxyElement.update (package:flutter/src/widgets/framework.dart:6152:5) +#918 _InheritedNotifierElement.update (package:flutter/src/widgets/inherited_notifier.dart:108:11) +#919 Element.updateChild (package:flutter/src/widgets/framework.dart:4037:15) +#920 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5844:16) +#921 StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5985:11) +#922 Element.rebuild (package:flutter/src/widgets/framework.dart:5532:7) +#923 StatefulElement.update (package:flutter/src/widgets/framework.dart:6010:5) +#924 Element.updateChild (package:flutter/src/widgets/framework.dart:4037:15) +#925 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5844:16) +#926 Element.rebuild (package:flutter/src/widgets/framework.dart:5532:7) +#927 ProxyElement.update (package:flutter/src/widgets/framework.dart:6152:5) +#928 _InheritedNotifierElement.update (package:flutter/src/widgets/inherited_notifier.dart:108:11) +#929 Element.updateChild (package:flutter/src/widgets/framework.dart:4037:15) +#930 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5844:16) +#931 StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5985:11) +#932 Element.rebuild (package:flutter/src/widgets/framework.dart:5532:7) +#933 StatefulElement.update (package:flutter/src/widgets/framework.dart:6010:5) +#934 Element.updateChild (package:flutter/src/widgets/framework.dart:4037:15) +#935 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5844:16) +#936 StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5985:11) +#937 Element.rebuild (package:flutter/src/widgets/framework.dart:5532:7) +#938 StatefulElement.update (package:flutter/src/widgets/framework.dart:6010:5) +#939 Element.updateChild (package:flutter/src/widgets/framework.dart:4037:15) +#940 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5844:16) +#941 Element.rebuild (package:flutter/src/widgets/framework.dart:5532:7) +#942 ProxyElement.update (package:flutter/src/widgets/framework.dart:6152:5) +#943 Element.updateChild (package:flutter/src/widgets/framework.dart:4037:15) +#944 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5844:16) +#945 StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5985:11) +#946 Element.rebuild (package:flutter/src/widgets/framework.dart:5532:7) +#947 StatefulElement.update (package:flutter/src/widgets/framework.dart:6010:5) +#948 Element.updateChild (package:flutter/src/widgets/framework.dart:4037:15) +#949 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5844:16) +#950 Element.rebuild (package:flutter/src/widgets/framework.dart:5532:7) +#951 ProxyElement.update (package:flutter/src/widgets/framework.dart:6152:5) +#952 Element.updateChild (package:flutter/src/widgets/framework.dart:4037:15) +#953 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5844:16) +#954 Element.rebuild (package:flutter/src/widgets/framework.dart:5532:7) +#955 ProxyElement.update (package:flutter/src/widgets/framework.dart:6152:5) +#956 Element.updateChild (package:flutter/src/widgets/framework.dart:4037:15) +#957 _RawViewElement._updateChild (package:flutter/src/widgets/view.dart:481:16) +#958 _RawViewElement.update (package:flutter/src/widgets/view.dart:568:5) +#959 Element.updateChild (package:flutter/src/widgets/framework.dart:4037:15) +#960 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5844:16) +#961 Element.rebuild (package:flutter/src/widgets/framework.dart:5532:7) +#962 StatelessElement.update (package:flutter/src/widgets/framework.dart:5898:5) +#963 Element.updateChild (package:flutter/src/widgets/framework.dart:4037:15) +#964 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5844:16) +#965 StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5985:11) +#966 Element.rebuild (package:flutter/src/widgets/framework.dart:5532:7) +#967 StatefulElement.update (package:flutter/src/widgets/framework.dart:6010:5) +#968 Element.updateChild (package:flutter/src/widgets/framework.dart:4037:15) +#969 RootElement._rebuild (package:flutter/src/widgets/binding.dart:1782:16) +#970 RootElement.update (package:flutter/src/widgets/binding.dart:1760:5) +#971 RootElement.performRebuild (package:flutter/src/widgets/binding.dart:1774:7) +#972 Element.rebuild (package:flutter/src/widgets/framework.dart:5532:7) +#973 BuildScope._tryRebuild (package:flutter/src/widgets/framework.dart:2750:15) +#974 BuildScope._flushDirtyElements (package:flutter/src/widgets/framework.dart:2807:11) +#975 BuildOwner.buildScope (package:flutter/src/widgets/framework.dart:3111:18) +#976 AutomatedTestWidgetsFlutterBinding.drawFrame (package:flutter_test/src/binding.dart:1506:19) +#977 RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:495:5) +#978 SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1434:15) +#979 SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1347:9) +#980 AutomatedTestWidgetsFlutterBinding.pump. (package:flutter_test/src/binding.dart:1335:9) +#983 TestAsyncUtils.guard (package:flutter_test/src/test_async_utils.dart:74:41) +#984 AutomatedTestWidgetsFlutterBinding.pump (package:flutter_test/src/binding.dart:1324:27) +#985 WidgetTester.pumpWidget. (package:flutter_test/src/widget_tester.dart:598:22) +#988 TestAsyncUtils.guard (package:flutter_test/src/test_async_utils.dart:74:41) +#989 WidgetTester.pumpWidget (package:flutter_test/src/widget_tester.dart:595:27) +#990 main.. (file:///home/alexander/eatvio_app/test/features/recipe/presentation/pages/my_recipes_screen_smoke_test.dart:44:20) +#991 testWidgets.. (package:flutter_test/src/widget_tester.dart:192:29) + +#992 TestWidgetsFlutterBinding._runTestBody (package:flutter_test/src/binding.dart:1059:5) + + +(elided 7 frames from class _AssertionError, dart:async, and package:stack_trace) + +════════════════════════════════════════════════════════════════════════════════════════════════════ +══╡ EXCEPTION CAUGHT BY RENDERING LIBRARY ╞═════════════════════════════════════════════════════════ +The following assertion was thrown during layout: +A RenderFlex overflowed by 99456 pixels on the bottom. + +The relevant error-causing widget was: + Column + Column:file:///home/alexander/eatvio_app/lib/features/recipe/presentation/pages/my_recipes_screen.dart:36:20 + +The overflowing RenderFlex has an orientation of Axis.vertical. +The edge of the RenderFlex that is overflowing has been marked in the rendering with a yellow and +black striped pattern. This is usually caused by the contents being too big for the RenderFlex. +Consider applying a flex factor (e.g. using an Expanded widget) to force the children of the +RenderFlex to fit within the available space instead of being sized to their natural size. +This is considered an error condition because it indicates that there is content that cannot be +seen. If the content is legitimately bigger than the available space, consider clipping it with a +ClipRect widget before putting it in the flex, or using a scrollable container rather than a Flex, +like a ListView. +The specific RenderFlex in question is: RenderFlex#1a36f relayoutBoundary=up1 OVERFLOWING: + creator: Column ← BlocListener ← BlocBuilder ← _InheritedProviderScope ← InheritedProvider ← + BlocProvider ← KeyedSubtree-[GlobalKey#37e81] ← _BodyBuilder ← MediaQuery ← + LayoutId-[<_ScaffoldSlot.body>] ← CustomMultiChildLayout ← _ActionsScope ← ⋯ + parentData: offset=Offset(0.0, 56.0); id=_ScaffoldSlot.body (can use size) + constraints: BoxConstraints(0.0<=w<=800.0, 0.0<=h<=544.0) + size: Size(800.0, 544.0) + direction: vertical + mainAxisAlignment: start + mainAxisSize: max + crossAxisAlignment: center + verticalDirection: down + spacing: 0.0 +◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤ +════════════════════════════════════════════════════════════════════════════════════════════════════ + 00:10 +175: /home/alexander/eatvio_app/test/features/recipe/presentation/pages/about_recipe_screen_smoke_test.dart: AboutRecipeScreen smoke renders loading state 00:11 +175: /home/alexander/eatvio_app/test/features/recipe/presentation/pages/about_recipe_screen_smoke_test.dart: AboutRecipeScreen smoke renders loading state 00:11 +175: /home/alexander/eatvio_app/test/features/recipe/presentation/pages/my_recipes_screen_smoke_test.dart: MyRecipesScreen smoke renders my recipes screen +══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════ +The following assertion was thrown building MyRecipesTab(dependencies: +[_LocalizationsScope-[GlobalKey#5add2]]): +No instance of S present in the widget tree. Did you add S.delegate in localizationsDelegates? +'package:eatvio_app/generated/l10n.dart': +Failed assertion: line 47 pos 7: 'instance != null' + +The relevant error-causing widget was: + MyRecipesTab + MyRecipesTab:file:///home/alexander/eatvio_app/lib/features/recipe/presentation/pages/my_recipes_screen.dart:38:17 + +When the exception was thrown, this was the stack: +#2 S.of (package:eatvio_app/generated/l10n.dart:47:7) +#3 MyRecipesTab.build (package:eatvio_app/features/recipe/presentation/widgets/my_recipes_tab.dart:19:17) +#4 StatelessElement.build (package:flutter/src/widgets/framework.dart:5892:49) +#5 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5820:15) +#6 Element.rebuild (package:flutter/src/widgets/framework.dart:5532:7) +#7 StatelessElement.update (package:flutter/src/widgets/framework.dart:5898:5) +#8 Element.updateChild (package:flutter/src/widgets/framework.dart:4037:15) +#9 Element.updateChildren (package:flutter/src/widgets/framework.dart:4194:32) +#10 MultiChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:7302:17) +#11 Element.updateChild (package:flutter/src/widgets/framework.dart:4037:15) +#12 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5844:16) +#13 StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5985:11) +#14 Element.rebuild (package:flutter/src/widgets/framework.dart:5532:7) +#15 StatefulElement.update (package:flutter/src/widgets/framework.dart:6010:5) +#16 Element.updateChild (package:flutter/src/widgets/framework.dart:4037:15) +#17 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5844:16) +#18 StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5985:11) +#19 Element.rebuild (package:flutter/src/widgets/framework.dart:5532:7) +#20 BuildScope._tryRebuild (package:flutter/src/widgets/framework.dart:2750:15) +#21 BuildScope._flushDirtyElements (package:flutter/src/widgets/framework.dart:2807:11) +#22 BuildOwner.buildScope (package:flutter/src/widgets/framework.dart:3111:18) +#23 AutomatedTestWidgetsFlutterBinding.drawFrame (package:flutter_test/src/binding.dart:1506:19) +#24 RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:495:5) +#25 SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1434:15) +#26 SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1347:9) +#27 AutomatedTestWidgetsFlutterBinding.pump. (package:flutter_test/src/binding.dart:1335:9) +#30 TestAsyncUtils.guard (package:flutter_test/src/test_async_utils.dart:74:41) +#31 AutomatedTestWidgetsFlutterBinding.pump (package:flutter_test/src/binding.dart:1324:27) +#32 WidgetTester.pump. (package:flutter_test/src/widget_tester.dart:652:53) +#35 TestAsyncUtils.guard (package:flutter_test/src/test_async_utils.dart:74:41) +#36 WidgetTester.pump (package:flutter_test/src/widget_tester.dart:652:27) +#37 main.. (file:///home/alexander/eatvio_app/test/features/recipe/presentation/pages/my_recipes_screen_smoke_test.dart:45:20) + +#38 testWidgets.. (package:flutter_test/src/widget_tester.dart:192:15) + +#39 TestWidgetsFlutterBinding._runTestBody (package:flutter_test/src/binding.dart:1059:5) + + +(elided 7 frames from class _AssertionError, dart:async, and package:stack_trace) + +════════════════════════════════════════════════════════════════════════════════════════════════════ + 00:11 +176: /home/alexander/eatvio_app/test/features/recipe/presentation/pages/about_recipe_screen_smoke_test.dart: AboutRecipeScreen smoke renders loading state 00:11 +176: /home/alexander/eatvio_app/test/features/recipe/presentation/pages/my_recipes_screen_smoke_test.dart: MyRecipesScreen smoke renders my recipes screen +══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════ +The following message was thrown: +Multiple exceptions (3) were detected during the running of the current test, and at least one was +unexpected. +════════════════════════════════════════════════════════════════════════════════════════════════════ + 00:11 +176 -1: /home/alexander/eatvio_app/test/features/recipe/presentation/pages/my_recipes_screen_smoke_test.dart: MyRecipesScreen smoke renders my recipes screen [E] + Test failed. See exception logs above. + The test description was: renders my recipes screen + + +To run this test again: /home/alexander/fvm/versions/3.38.9/bin/cache/dart-sdk/bin/dart test /home/alexander/eatvio_app/test/features/recipe/presentation/pages/my_recipes_screen_smoke_test.dart -p vm --plain-name 'MyRecipesScreen smoke renders my recipes screen' + 00:11 +177 -1: /home/alexander/eatvio_app/test/features/recipe/presentation/pages/about_recipe_screen_smoke_test.dart: AboutRecipeScreen smoke renders loading state 00:11 +177 -1: /home/alexander/eatvio_app/test/features/recipe/presentation/pages/my_recipes_screen_smoke_test.dart: MyRecipesScreen smoke renders my recipes screen with loaded state +══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════ +The following assertion was thrown building MyRecipesTab(dirty, dependencies: +[_LocalizationsScope-[GlobalKey#bd6eb]]): +No instance of S present in the widget tree. Did you add S.delegate in localizationsDelegates? +'package:eatvio_app/generated/l10n.dart': +Failed assertion: line 47 pos 7: 'instance != null' + +The relevant error-causing widget was: + MyRecipesTab + MyRecipesTab:file:///home/alexander/eatvio_app/lib/features/recipe/presentation/pages/my_recipes_screen.dart:38:17 + +When the exception was thrown, this was the stack: +#2 S.of (package:eatvio_app/generated/l10n.dart:47:7) +#3 MyRecipesTab.build (package:eatvio_app/features/recipe/presentation/widgets/my_recipes_tab.dart:19:17) +#4 StatelessElement.build (package:flutter/src/widgets/framework.dart:5892:49) +#5 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5820:15) +#6 Element.rebuild (package:flutter/src/widgets/framework.dart:5532:7) +#7 ComponentElement._firstBuild (package:flutter/src/widgets/framework.dart:5802:5) +#8 ComponentElement.mount (package:flutter/src/widgets/framework.dart:5796:5) +#9 Element.inflateWidget (package:flutter/src/widgets/framework.dart:4590:20) +#10 MultiChildRenderObjectElement.inflateWidget (package:flutter/src/widgets/framework.dart:7269:36) +#11 MultiChildRenderObjectElement.mount (package:flutter/src/widgets/framework.dart:7285:32) +... Normal element mounting (9 frames) +#20 SingleChildWidgetElementMixin.mount (package:nested/nested.dart:222:11) +... Normal element mounting (15 frames) +#35 _InheritedProviderScopeElement.mount (package:provider/src/inherited_provider.dart:424:11) +... Normal element mounting (7 frames) +#42 SingleChildWidgetElementMixin.mount (package:nested/nested.dart:222:11) +... Normal element mounting (7 frames) +#49 SingleChildWidgetElementMixin.mount (package:nested/nested.dart:222:11) +... Normal element mounting (25 frames) +#74 Element.inflateWidget (package:flutter/src/widgets/framework.dart:4590:20) +#75 MultiChildRenderObjectElement.inflateWidget (package:flutter/src/widgets/framework.dart:7269:36) +#76 MultiChildRenderObjectElement.mount (package:flutter/src/widgets/framework.dart:7285:32) +... Normal element mounting (370 frames) +#446 Element.inflateWidget (package:flutter/src/widgets/framework.dart:4590:20) +#447 MultiChildRenderObjectElement.inflateWidget (package:flutter/src/widgets/framework.dart:7269:36) +#448 MultiChildRenderObjectElement.mount (package:flutter/src/widgets/framework.dart:7285:32) +... Normal element mounting (465 frames) +#913 Element.inflateWidget (package:flutter/src/widgets/framework.dart:4590:20) +#914 Element.updateChild (package:flutter/src/widgets/framework.dart:4053:20) +#915 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5844:16) +#916 Element.rebuild (package:flutter/src/widgets/framework.dart:5532:7) +#917 ProxyElement.update (package:flutter/src/widgets/framework.dart:6152:5) +#918 _InheritedNotifierElement.update (package:flutter/src/widgets/inherited_notifier.dart:108:11) +#919 Element.updateChild (package:flutter/src/widgets/framework.dart:4037:15) +#920 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5844:16) +#921 StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5985:11) +#922 Element.rebuild (package:flutter/src/widgets/framework.dart:5532:7) +#923 StatefulElement.update (package:flutter/src/widgets/framework.dart:6010:5) +#924 Element.updateChild (package:flutter/src/widgets/framework.dart:4037:15) +#925 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5844:16) +#926 Element.rebuild (package:flutter/src/widgets/framework.dart:5532:7) +#927 ProxyElement.update (package:flutter/src/widgets/framework.dart:6152:5) +#928 _InheritedNotifierElement.update (package:flutter/src/widgets/inherited_notifier.dart:108:11) +#929 Element.updateChild (package:flutter/src/widgets/framework.dart:4037:15) +#930 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5844:16) +#931 StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5985:11) +#932 Element.rebuild (package:flutter/src/widgets/framework.dart:5532:7) +#933 StatefulElement.update (package:flutter/src/widgets/framework.dart:6010:5) +#934 Element.updateChild (package:flutter/src/widgets/framework.dart:4037:15) +#935 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5844:16) +#936 StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5985:11) +#937 Element.rebuild (package:flutter/src/widgets/framework.dart:5532:7) +#938 StatefulElement.update (package:flutter/src/widgets/framework.dart:6010:5) +#939 Element.updateChild (package:flutter/src/widgets/framework.dart:4037:15) +#940 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5844:16) +#941 Element.rebuild (package:flutter/src/widgets/framework.dart:5532:7) +#942 ProxyElement.update (package:flutter/src/widgets/framework.dart:6152:5) +#943 Element.updateChild (package:flutter/src/widgets/framework.dart:4037:15) +#944 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5844:16) +#945 StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5985:11) +#946 Element.rebuild (package:flutter/src/widgets/framework.dart:5532:7) +#947 StatefulElement.update (package:flutter/src/widgets/framework.dart:6010:5) +#948 Element.updateChild (package:flutter/src/widgets/framework.dart:4037:15) +#949 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5844:16) +#950 Element.rebuild (package:flutter/src/widgets/framework.dart:5532:7) +#951 ProxyElement.update (package:flutter/src/widgets/framework.dart:6152:5) +#952 Element.updateChild (package:flutter/src/widgets/framework.dart:4037:15) +#953 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5844:16) +#954 Element.rebuild (package:flutter/src/widgets/framework.dart:5532:7) +#955 ProxyElement.update (package:flutter/src/widgets/framework.dart:6152:5) +#956 Element.updateChild (package:flutter/src/widgets/framework.dart:4037:15) +#957 _RawViewElement._updateChild (package:flutter/src/widgets/view.dart:481:16) +#958 _RawViewElement.update (package:flutter/src/widgets/view.dart:568:5) +#959 Element.updateChild (package:flutter/src/widgets/framework.dart:4037:15) +#960 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5844:16) +#961 Element.rebuild (package:flutter/src/widgets/framework.dart:5532:7) +#962 StatelessElement.update (package:flutter/src/widgets/framework.dart:5898:5) +#963 Element.updateChild (package:flutter/src/widgets/framework.dart:4037:15) +#964 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5844:16) +#965 StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5985:11) +#966 Element.rebuild (package:flutter/src/widgets/framework.dart:5532:7) +#967 StatefulElement.update (package:flutter/src/widgets/framework.dart:6010:5) +#968 Element.updateChild (package:flutter/src/widgets/framework.dart:4037:15) +#969 RootElement._rebuild (package:flutter/src/widgets/binding.dart:1782:16) +#970 RootElement.update (package:flutter/src/widgets/binding.dart:1760:5) +#971 RootElement.performRebuild (package:flutter/src/widgets/binding.dart:1774:7) +#972 Element.rebuild (package:flutter/src/widgets/framework.dart:5532:7) +#973 BuildScope._tryRebuild (package:flutter/src/widgets/framework.dart:2750:15) +#974 BuildScope._flushDirtyElements (package:flutter/src/widgets/framework.dart:2807:11) +#975 BuildOwner.buildScope (package:flutter/src/widgets/framework.dart:3111:18) +#976 AutomatedTestWidgetsFlutterBinding.drawFrame (package:flutter_test/src/binding.dart:1506:19) +#977 RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:495:5) +#978 SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1434:15) +#979 SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1347:9) +#980 AutomatedTestWidgetsFlutterBinding.pump. (package:flutter_test/src/binding.dart:1335:9) +#983 TestAsyncUtils.guard (package:flutter_test/src/test_async_utils.dart:74:41) +#984 AutomatedTestWidgetsFlutterBinding.pump (package:flutter_test/src/binding.dart:1324:27) +#985 WidgetTester.pumpWidget. (package:flutter_test/src/widget_tester.dart:598:22) +#988 TestAsyncUtils.guard (package:flutter_test/src/test_async_utils.dart:74:41) +#989 WidgetTester.pumpWidget (package:flutter_test/src/widget_tester.dart:595:27) +#990 main.. (file:///home/alexander/eatvio_app/test/features/recipe/presentation/pages/my_recipes_screen_smoke_test.dart:63:20) +#991 testWidgets.. (package:flutter_test/src/widget_tester.dart:192:29) + +#992 TestWidgetsFlutterBinding._runTestBody (package:flutter_test/src/binding.dart:1059:5) + + +(elided 7 frames from class _AssertionError, dart:async, and package:stack_trace) + +════════════════════════════════════════════════════════════════════════════════════════════════════ +══╡ EXCEPTION CAUGHT BY RENDERING LIBRARY ╞═════════════════════════════════════════════════════════ +The following assertion was thrown during layout: +A RenderFlex overflowed by 99456 pixels on the bottom. + +The relevant error-causing widget was: + Column + Column:file:///home/alexander/eatvio_app/lib/features/recipe/presentation/pages/my_recipes_screen.dart:36:20 + +The overflowing RenderFlex has an orientation of Axis.vertical. +The edge of the RenderFlex that is overflowing has been marked in the rendering with a yellow and +black striped pattern. This is usually caused by the contents being too big for the RenderFlex. +Consider applying a flex factor (e.g. using an Expanded widget) to force the children of the +RenderFlex to fit within the available space instead of being sized to their natural size. +This is considered an error condition because it indicates that there is content that cannot be +seen. If the content is legitimately bigger than the available space, consider clipping it with a +ClipRect widget before putting it in the flex, or using a scrollable container rather than a Flex, +like a ListView. +The specific RenderFlex in question is: RenderFlex#64d94 relayoutBoundary=up1 OVERFLOWING: + creator: Column ← BlocListener ← BlocBuilder ← _InheritedProviderScope ← InheritedProvider ← + BlocProvider ← KeyedSubtree-[GlobalKey#a68dd] ← _BodyBuilder ← MediaQuery ← + LayoutId-[<_ScaffoldSlot.body>] ← CustomMultiChildLayout ← _ActionsScope ← ⋯ + parentData: offset=Offset(0.0, 56.0); id=_ScaffoldSlot.body (can use size) + constraints: BoxConstraints(0.0<=w<=800.0, 0.0<=h<=544.0) + size: Size(800.0, 544.0) + direction: vertical + mainAxisAlignment: start + mainAxisSize: max + crossAxisAlignment: center + verticalDirection: down + spacing: 0.0 +◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤ +════════════════════════════════════════════════════════════════════════════════════════════════════ +══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════ +The following assertion was thrown building MyRecipesTab(dependencies: +[_LocalizationsScope-[GlobalKey#bd6eb]]): +No instance of S present in the widget tree. Did you add S.delegate in localizationsDelegates? +'package:eatvio_app/generated/l10n.dart': +Failed assertion: line 47 pos 7: 'instance != null' + +The relevant error-causing widget was: + MyRecipesTab + MyRecipesTab:file:///home/alexander/eatvio_app/lib/features/recipe/presentation/pages/my_recipes_screen.dart:38:17 + +When the exception was thrown, this was the stack: +#2 S.of (package:eatvio_app/generated/l10n.dart:47:7) +#3 MyRecipesTab.build (package:eatvio_app/features/recipe/presentation/widgets/my_recipes_tab.dart:19:17) +#4 StatelessElement.build (package:flutter/src/widgets/framework.dart:5892:49) +#5 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5820:15) +#6 Element.rebuild (package:flutter/src/widgets/framework.dart:5532:7) +#7 StatelessElement.update (package:flutter/src/widgets/framework.dart:5898:5) +#8 Element.updateChild (package:flutter/src/widgets/framework.dart:4037:15) +#9 Element.updateChildren (package:flutter/src/widgets/framework.dart:4194:32) +#10 MultiChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:7302:17) +#11 Element.updateChild (package:flutter/src/widgets/framework.dart:4037:15) +#12 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5844:16) +#13 StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5985:11) +#14 Element.rebuild (package:flutter/src/widgets/framework.dart:5532:7) +#15 StatefulElement.update (package:flutter/src/widgets/framework.dart:6010:5) +#16 Element.updateChild (package:flutter/src/widgets/framework.dart:4037:15) +#17 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5844:16) +#18 StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5985:11) +#19 Element.rebuild (package:flutter/src/widgets/framework.dart:5532:7) +#20 BuildScope._tryRebuild (package:flutter/src/widgets/framework.dart:2750:15) +#21 BuildScope._flushDirtyElements (package:flutter/src/widgets/framework.dart:2807:11) +#22 BuildOwner.buildScope (package:flutter/src/widgets/framework.dart:3111:18) +#23 AutomatedTestWidgetsFlutterBinding.drawFrame (package:flutter_test/src/binding.dart:1506:19) +#24 RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:495:5) +#25 SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1434:15) +#26 SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1347:9) +#27 AutomatedTestWidgetsFlutterBinding.pump. (package:flutter_test/src/binding.dart:1335:9) +#30 TestAsyncUtils.guard (package:flutter_test/src/test_async_utils.dart:74:41) +#31 AutomatedTestWidgetsFlutterBinding.pump (package:flutter_test/src/binding.dart:1324:27) +#32 WidgetTester.pump. (package:flutter_test/src/widget_tester.dart:652:53) +#35 TestAsyncUtils.guard (package:flutter_test/src/test_async_utils.dart:74:41) +#36 WidgetTester.pump (package:flutter_test/src/widget_tester.dart:652:27) +#37 main.. (file:///home/alexander/eatvio_app/test/features/recipe/presentation/pages/my_recipes_screen_smoke_test.dart:64:20) + +#38 testWidgets.. (package:flutter_test/src/widget_tester.dart:192:15) + +#39 TestWidgetsFlutterBinding._runTestBody (package:flutter_test/src/binding.dart:1059:5) + + +(elided 7 frames from class _AssertionError, dart:async, and package:stack_trace) + +════════════════════════════════════════════════════════════════════════════════════════════════════ +══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════ +The following message was thrown: +Multiple exceptions (3) were detected during the running of the current test, and at least one was +unexpected. +════════════════════════════════════════════════════════════════════════════════════════════════════ + 00:11 +177 -2: /home/alexander/eatvio_app/test/features/recipe/presentation/pages/my_recipes_screen_smoke_test.dart: MyRecipesScreen smoke renders my recipes screen with loaded state [E] + Test failed. See exception logs above. + The test description was: renders my recipes screen with loaded state + + +To run this test again: /home/alexander/fvm/versions/3.38.9/bin/cache/dart-sdk/bin/dart test /home/alexander/eatvio_app/test/features/recipe/presentation/pages/my_recipes_screen_smoke_test.dart -p vm --plain-name 'MyRecipesScreen smoke renders my recipes screen with loaded state' + 00:11 +178 -2: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - убирает лайк если уже лайкнуто 00:11 +178 -2: /home/alexander/eatvio_app/test/features/recipe/presentation/pages/my_recipes_screen_smoke_test.dart: MyRecipesScreen smoke renders my recipes screen with error state +══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════ +The following assertion was thrown building MyRecipesTab(dirty, dependencies: +[_LocalizationsScope-[GlobalKey#5fdae]]): +No instance of S present in the widget tree. Did you add S.delegate in localizationsDelegates? +'package:eatvio_app/generated/l10n.dart': +Failed assertion: line 47 pos 7: 'instance != null' + +The relevant error-causing widget was: + MyRecipesTab + MyRecipesTab:file:///home/alexander/eatvio_app/lib/features/recipe/presentation/pages/my_recipes_screen.dart:38:17 + +When the exception was thrown, this was the stack: +#2 S.of (package:eatvio_app/generated/l10n.dart:47:7) +#3 MyRecipesTab.build (package:eatvio_app/features/recipe/presentation/widgets/my_recipes_tab.dart:19:17) +#4 StatelessElement.build (package:flutter/src/widgets/framework.dart:5892:49) +#5 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5820:15) +#6 Element.rebuild (package:flutter/src/widgets/framework.dart:5532:7) +#7 ComponentElement._firstBuild (package:flutter/src/widgets/framework.dart:5802:5) +#8 ComponentElement.mount (package:flutter/src/widgets/framework.dart:5796:5) +#9 Element.inflateWidget (package:flutter/src/widgets/framework.dart:4590:20) +#10 MultiChildRenderObjectElement.inflateWidget (package:flutter/src/widgets/framework.dart:7269:36) +#11 MultiChildRenderObjectElement.mount (package:flutter/src/widgets/framework.dart:7285:32) +... Normal element mounting (9 frames) +#20 SingleChildWidgetElementMixin.mount (package:nested/nested.dart:222:11) +... Normal element mounting (15 frames) +#35 _InheritedProviderScopeElement.mount (package:provider/src/inherited_provider.dart:424:11) +... Normal element mounting (7 frames) +#42 SingleChildWidgetElementMixin.mount (package:nested/nested.dart:222:11) +... Normal element mounting (7 frames) +#49 SingleChildWidgetElementMixin.mount (package:nested/nested.dart:222:11) +... Normal element mounting (25 frames) +#74 Element.inflateWidget (package:flutter/src/widgets/framework.dart:4590:20) +#75 MultiChildRenderObjectElement.inflateWidget (package:flutter/src/widgets/framework.dart:7269:36) +#76 MultiChildRenderObjectElement.mount (package:flutter/src/widgets/framework.dart:7285:32) +... Normal element mounting (370 frames) +#446 Element.inflateWidget (package:flutter/src/widgets/framework.dart:4590:20) +#447 MultiChildRenderObjectElement.inflateWidget (package:flutter/src/widgets/framework.dart:7269:36) +#448 MultiChildRenderObjectElement.mount (package:flutter/src/widgets/framework.dart:7285:32) +... Normal element mounting (465 frames) +#913 Element.inflateWidget (package:flutter/src/widgets/framework.dart:4590:20) +#914 Element.updateChild (package:flutter/src/widgets/framework.dart:4053:20) +#915 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5844:16) +#916 Element.rebuild (package:flutter/src/widgets/framework.dart:5532:7) +#917 ProxyElement.update (package:flutter/src/widgets/framework.dart:6152:5) +#918 _InheritedNotifierElement.update (package:flutter/src/widgets/inherited_notifier.dart:108:11) +#919 Element.updateChild (package:flutter/src/widgets/framework.dart:4037:15) +#920 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5844:16) +#921 StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5985:11) +#922 Element.rebuild (package:flutter/src/widgets/framework.dart:5532:7) +#923 StatefulElement.update (package:flutter/src/widgets/framework.dart:6010:5) +#924 Element.updateChild (package:flutter/src/widgets/framework.dart:4037:15) +#925 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5844:16) +#926 Element.rebuild (package:flutter/src/widgets/framework.dart:5532:7) +#927 ProxyElement.update (package:flutter/src/widgets/framework.dart:6152:5) +#928 _InheritedNotifierElement.update (package:flutter/src/widgets/inherited_notifier.dart:108:11) +#929 Element.updateChild (package:flutter/src/widgets/framework.dart:4037:15) +#930 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5844:16) +#931 StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5985:11) +#932 Element.rebuild (package:flutter/src/widgets/framework.dart:5532:7) +#933 StatefulElement.update (package:flutter/src/widgets/framework.dart:6010:5) +#934 Element.updateChild (package:flutter/src/widgets/framework.dart:4037:15) +#935 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5844:16) +#936 StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5985:11) +#937 Element.rebuild (package:flutter/src/widgets/framework.dart:5532:7) +#938 StatefulElement.update (package:flutter/src/widgets/framework.dart:6010:5) +#939 Element.updateChild (package:flutter/src/widgets/framework.dart:4037:15) +#940 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5844:16) +#941 Element.rebuild (package:flutter/src/widgets/framework.dart:5532:7) +#942 ProxyElement.update (package:flutter/src/widgets/framework.dart:6152:5) +#943 Element.updateChild (package:flutter/src/widgets/framework.dart:4037:15) +#944 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5844:16) +#945 StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5985:11) +#946 Element.rebuild (package:flutter/src/widgets/framework.dart:5532:7) +#947 StatefulElement.update (package:flutter/src/widgets/framework.dart:6010:5) +#948 Element.updateChild (package:flutter/src/widgets/framework.dart:4037:15) +#949 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5844:16) +#950 Element.rebuild (package:flutter/src/widgets/framework.dart:5532:7) +#951 ProxyElement.update (package:flutter/src/widgets/framework.dart:6152:5) +#952 Element.updateChild (package:flutter/src/widgets/framework.dart:4037:15) +#953 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5844:16) +#954 Element.rebuild (package:flutter/src/widgets/framework.dart:5532:7) +#955 ProxyElement.update (package:flutter/src/widgets/framework.dart:6152:5) +#956 Element.updateChild (package:flutter/src/widgets/framework.dart:4037:15) +#957 _RawViewElement._updateChild (package:flutter/src/widgets/view.dart:481:16) +#958 _RawViewElement.update (package:flutter/src/widgets/view.dart:568:5) +#959 Element.updateChild (package:flutter/src/widgets/framework.dart:4037:15) +#960 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5844:16) +#961 Element.rebuild (package:flutter/src/widgets/framework.dart:5532:7) +#962 StatelessElement.update (package:flutter/src/widgets/framework.dart:5898:5) +#963 Element.updateChild (package:flutter/src/widgets/framework.dart:4037:15) +#964 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5844:16) +#965 StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5985:11) +#966 Element.rebuild (package:flutter/src/widgets/framework.dart:5532:7) +#967 StatefulElement.update (package:flutter/src/widgets/framework.dart:6010:5) +#968 Element.updateChild (package:flutter/src/widgets/framework.dart:4037:15) +#969 RootElement._rebuild (package:flutter/src/widgets/binding.dart:1782:16) +#970 RootElement.update (package:flutter/src/widgets/binding.dart:1760:5) +#971 RootElement.performRebuild (package:flutter/src/widgets/binding.dart:1774:7) +#972 Element.rebuild (package:flutter/src/widgets/framework.dart:5532:7) +#973 BuildScope._tryRebuild (package:flutter/src/widgets/framework.dart:2750:15) +#974 BuildScope._flushDirtyElements (package:flutter/src/widgets/framework.dart:2807:11) +#975 BuildOwner.buildScope (package:flutter/src/widgets/framework.dart:3111:18) +#976 AutomatedTestWidgetsFlutterBinding.drawFrame (package:flutter_test/src/binding.dart:1506:19) +#977 RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:495:5) +#978 SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1434:15) +#979 SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1347:9) +#980 AutomatedTestWidgetsFlutterBinding.pump. (package:flutter_test/src/binding.dart:1335:9) +#983 TestAsyncUtils.guard (package:flutter_test/src/test_async_utils.dart:74:41) +#984 AutomatedTestWidgetsFlutterBinding.pump (package:flutter_test/src/binding.dart:1324:27) +#985 WidgetTester.pumpWidget. (package:flutter_test/src/widget_tester.dart:598:22) +#988 TestAsyncUtils.guard (package:flutter_test/src/test_async_utils.dart:74:41) +#989 WidgetTester.pumpWidget (package:flutter_test/src/widget_tester.dart:595:27) +#990 main.. (file:///home/alexander/eatvio_app/test/features/recipe/presentation/pages/my_recipes_screen_smoke_test.dart:81:20) +#991 testWidgets.. (package:flutter_test/src/widget_tester.dart:192:29) + +#992 TestWidgetsFlutterBinding._runTestBody (package:flutter_test/src/binding.dart:1059:5) + + +(elided 7 frames from class _AssertionError, dart:async, and package:stack_trace) + +════════════════════════════════════════════════════════════════════════════════════════════════════ +══╡ EXCEPTION CAUGHT BY RENDERING LIBRARY ╞═════════════════════════════════════════════════════════ +The following assertion was thrown during layout: +A RenderFlex overflowed by 99456 pixels on the bottom. + +The relevant error-causing widget was: + Column + Column:file:///home/alexander/eatvio_app/lib/features/recipe/presentation/pages/my_recipes_screen.dart:36:20 + +The overflowing RenderFlex has an orientation of Axis.vertical. +The edge of the RenderFlex that is overflowing has been marked in the rendering with a yellow and +black striped pattern. This is usually caused by the contents being too big for the RenderFlex. +Consider applying a flex factor (e.g. using an Expanded widget) to force the children of the +RenderFlex to fit within the available space instead of being sized to their natural size. +This is considered an error condition because it indicates that there is content that cannot be +seen. If the content is legitimately bigger than the available space, consider clipping it with a +ClipRect widget before putting it in the flex, or using a scrollable container rather than a Flex, +like a ListView. +The specific RenderFlex in question is: RenderFlex#b1ec5 relayoutBoundary=up1 OVERFLOWING: + creator: Column ← BlocListener ← BlocBuilder ← _InheritedProviderScope ← InheritedProvider ← + BlocProvider ← KeyedSubtree-[GlobalKey#1a452] ← _BodyBuilder ← MediaQuery ← + LayoutId-[<_ScaffoldSlot.body>] ← CustomMultiChildLayout ← _ActionsScope ← ⋯ + parentData: offset=Offset(0.0, 56.0); id=_ScaffoldSlot.body (can use size) + constraints: BoxConstraints(0.0<=w<=800.0, 0.0<=h<=544.0) + size: Size(800.0, 544.0) + direction: vertical + mainAxisAlignment: start + mainAxisSize: max + crossAxisAlignment: center + verticalDirection: down + spacing: 0.0 +◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤ +════════════════════════════════════════════════════════════════════════════════════════════════════ +══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════ +The following assertion was thrown building MyRecipesTab(dependencies: +[_LocalizationsScope-[GlobalKey#5fdae]]): +No instance of S present in the widget tree. Did you add S.delegate in localizationsDelegates? +'package:eatvio_app/generated/l10n.dart': +Failed assertion: line 47 pos 7: 'instance != null' + +The relevant error-causing widget was: + MyRecipesTab + MyRecipesTab:file:///home/alexander/eatvio_app/lib/features/recipe/presentation/pages/my_recipes_screen.dart:38:17 + +When the exception was thrown, this was the stack: +#2 S.of (package:eatvio_app/generated/l10n.dart:47:7) +#3 MyRecipesTab.build (package:eatvio_app/features/recipe/presentation/widgets/my_recipes_tab.dart:19:17) +#4 StatelessElement.build (package:flutter/src/widgets/framework.dart:5892:49) +#5 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5820:15) +#6 Element.rebuild (package:flutter/src/widgets/framework.dart:5532:7) +#7 StatelessElement.update (package:flutter/src/widgets/framework.dart:5898:5) +#8 Element.updateChild (package:flutter/src/widgets/framework.dart:4037:15) +#9 Element.updateChildren (package:flutter/src/widgets/framework.dart:4194:32) +#10 MultiChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:7302:17) +#11 Element.updateChild (package:flutter/src/widgets/framework.dart:4037:15) +#12 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5844:16) +#13 StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5985:11) +#14 Element.rebuild (package:flutter/src/widgets/framework.dart:5532:7) +#15 StatefulElement.update (package:flutter/src/widgets/framework.dart:6010:5) +#16 Element.updateChild (package:flutter/src/widgets/framework.dart:4037:15) +#17 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5844:16) +#18 StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5985:11) +#19 Element.rebuild (package:flutter/src/widgets/framework.dart:5532:7) +#20 BuildScope._tryRebuild (package:flutter/src/widgets/framework.dart:2750:15) +#21 BuildScope._flushDirtyElements (package:flutter/src/widgets/framework.dart:2807:11) +#22 BuildOwner.buildScope (package:flutter/src/widgets/framework.dart:3111:18) +#23 AutomatedTestWidgetsFlutterBinding.drawFrame (package:flutter_test/src/binding.dart:1506:19) +#24 RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:495:5) +#25 SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1434:15) +#26 SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1347:9) +#27 AutomatedTestWidgetsFlutterBinding.pump. (package:flutter_test/src/binding.dart:1335:9) +#30 TestAsyncUtils.guard (package:flutter_test/src/test_async_utils.dart:74:41) +#31 AutomatedTestWidgetsFlutterBinding.pump (package:flutter_test/src/binding.dart:1324:27) +#32 WidgetTester.pump. (package:flutter_test/src/widget_tester.dart:652:53) +#35 TestAsyncUtils.guard (package:flutter_test/src/test_async_utils.dart:74:41) +#36 WidgetTester.pump (package:flutter_test/src/widget_tester.dart:652:27) +#37 main.. (file:///home/alexander/eatvio_app/test/features/recipe/presentation/pages/my_recipes_screen_smoke_test.dart:82:20) + +#38 testWidgets.. (package:flutter_test/src/widget_tester.dart:192:15) + +#39 TestWidgetsFlutterBinding._runTestBody (package:flutter_test/src/binding.dart:1059:5) + + +(elided 7 frames from class _AssertionError, dart:async, and package:stack_trace) + +════════════════════════════════════════════════════════════════════════════════════════════════════ +══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════ +The following message was thrown: +Multiple exceptions (3) were detected during the running of the current test, and at least one was +unexpected. +════════════════════════════════════════════════════════════════════════════════════════════════════ + 00:11 +178 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/pages/my_recipes_screen_smoke_test.dart: MyRecipesScreen smoke renders my recipes screen with error state [E] + Test failed. See exception logs above. + The test description was: renders my recipes screen with error state + + +To run this test again: /home/alexander/fvm/versions/3.38.9/bin/cache/dart-sdk/bin/dart test /home/alexander/eatvio_app/test/features/recipe/presentation/pages/my_recipes_screen_smoke_test.dart -p vm --plain-name 'MyRecipesScreen smoke renders my recipes screen with error state' + 00:11 +178 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - убирает лайк если уже лайкнуто 00:11 +179 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - убирает лайк если уже лайкнуто 00:11 +180 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - убирает лайк если уже лайкнуто 00:11 +181 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/widgets/my_recipe_widget_test.dart: MyRecipeWidget smoke renders my recipe widget 00:11 +182 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/widgets/my_recipe_widget_test.dart: MyRecipeWidget smoke renders my recipe widget 00:11 +183 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/widgets/my_recipe_widget_test.dart: MyRecipeWidget smoke renders my recipe widget 00:11 +184 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/widgets/my_recipe_widget_test.dart: MyRecipeWidget smoke renders my recipe widget 00:12 +184 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/widgets/my_recipe_widget_test.dart: MyRecipeWidget smoke renders my recipe widget 00:12 +185 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/widgets/my_recipe_widget_test.dart: MyRecipeWidget smoke renders my recipe widget 00:12 +186 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/widgets/my_recipe_widget_test.dart: MyRecipeWidget smoke renders my recipe widget 00:12 +187 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/widgets/my_recipe_widget_test.dart: MyRecipeWidget smoke renders my recipe widget 00:12 +188 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/widgets/my_recipe_widget_test.dart: MyRecipeWidget smoke renders my recipe widget 00:12 +189 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/widgets/my_recipe_widget_test.dart: MyRecipeWidget smoke renders my recipe widget 00:12 +190 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/widgets/my_recipe_widget_test.dart: MyRecipeWidget smoke renders my recipe widget 00:12 +191 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/widgets/my_recipe_widget_test.dart: MyRecipeWidget smoke renders my recipe widget 00:12 +192 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/widgets/my_recipe_widget_test.dart: MyRecipeWidget smoke renders my recipe widget 00:12 +193 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/widgets/my_recipe_widget_test.dart: MyRecipeWidget smoke renders my recipe widget 00:12 +194 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/widgets/my_recipe_widget_test.dart: MyRecipeWidget smoke renders my recipe widget 00:12 +195 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - добавляет рецепт в избранное 00:12 +196 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - добавляет рецепт в избранное 00:12 +197 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - добавляет рецепт в избранное 00:12 +198 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - добавляет рецепт в избранное 00:12 +199 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - добавляет рецепт в избранное 00:12 +200 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - добавляет рецепт в избранное 00:12 +201 -3: /home/alexander/eatvio_app/test/features/recipes/presentation/cubit/my_recipes_cubit_test.dart: MyRecipesCubit getMyRecipes - загружает активные рецепты 00:12 +202 -3: /home/alexander/eatvio_app/test/features/create_recipe/widget_test.dart: RecipeCreateScreen Widget Tests should show validation error for invalid portions 00:12 +203 -3: /home/alexander/eatvio_app/test/features/create_recipe/widget_test.dart: RecipeCreateScreen Widget Tests should show validation error for invalid portions 00:13 +203 -3: /home/alexander/eatvio_app/test/features/create_recipe/widget_test.dart: RecipeCreateScreen Widget Tests should show validation error for invalid portions 00:13 +204 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - удаляет рецепт из избранного 00:13 +205 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - удаляет рецепт из избранного 00:13 +206 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - удаляет рецепт из избранного 00:13 +207 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - удаляет рецепт из избранного 00:13 +208 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - удаляет рецепт из избранного 00:13 +209 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - удаляет рецепт из избранного 00:13 +210 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - удаляет рецепт из избранного 00:13 +211 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - удаляет рецепт из избранного 00:13 +212 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - удаляет рецепт из избранного 00:13 +213 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - удаляет рецепт из избранного 00:13 +214 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - удаляет рецепт из избранного 00:13 +215 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - удаляет рецепт из избранного 00:13 +216 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - удаляет рецепт из избранного 00:13 +217 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - удаляет рецепт из избранного 00:13 +218 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - удаляет рецепт из избранного 00:13 +219 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - удаляет рецепт из избранного 00:14 +219 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - удаляет рецепт из избранного 00:14 +220 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - удаляет рецепт из избранного 00:14 +221 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - удаляет рецепт из избранного 00:14 +222 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - удаляет рецепт из избранного 00:14 +223 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - удаляет рецепт из избранного 00:14 +224 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - удаляет рецепт из избранного 00:14 +225 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - удаляет рецепт из избранного 00:14 +226 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - удаляет рецепт из избранного 00:14 +227 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - удаляет рецепт из избранного 00:14 +228 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - удаляет рецепт из избранного 00:14 +229 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - удаляет рецепт из избранного 00:14 +230 -3: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display RecipesWidget with recipe title 00:14 +231 -3: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display RecipesWidget with recipe title 00:14 +232 -3: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display RecipesWidget with recipe title 00:14 +233 -3: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display RecipesWidget with recipe title 00:14 +234 -3: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display RecipesWidget with recipe title 00:14 +235 -3: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display RecipesWidget with recipe title 00:14 +236 -3: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display RecipesWidget with recipe title 00:14 +237 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - с recipeIndexInFeed параметром 00:14 +238 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - с recipeIndexInFeed параметром 00:14 +239 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - с recipeIndexInFeed параметром 00:14 +240 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - с recipeIndexInFeed параметром 00:14 +241 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - с recipeIndexInFeed параметром 00:14 +242 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - с recipeIndexInFeed параметром 00:14 +243 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - с recipeIndexInFeed параметром 00:14 +244 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - с recipeIndexInFeed параметром 00:14 +245 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - с recipeIndexInFeed параметром 00:14 +246 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - с recipeIndexInFeed параметром 00:14 +247 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - с recipeIndexInFeed параметром 00:14 +248 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - с recipeIndexInFeed параметром 00:14 +249 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - с recipeIndexInFeed параметром 00:14 +250 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - с recipeIndexInFeed параметром 00:14 +251 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - с recipeIndexInFeed параметром 00:14 +252 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - с recipeIndexInFeed параметром 00:14 +253 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - с recipeIndexInFeed параметром 00:15 +253 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - с recipeIndexInFeed параметром 00:15 +254 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - с recipeIndexInFeed параметром 00:15 +255 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - с recipeIndexInFeed параметром 00:15 +256 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - с recipeIndexInFeed параметром 00:15 +257 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - с recipeIndexInFeed параметром 00:15 +258 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - с recipeIndexInFeed параметром 00:15 +259 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - с recipeIndexInFeed параметром 00:15 +260 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - с recipeIndexInFeed параметром 00:15 +261 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - с recipeIndexInFeed параметром 00:15 +262 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - с recipeIndexInFeed параметром 00:15 +263 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - с recipeIndexInFeed параметром 00:15 +264 -3: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display Recipes widget with multiple recipes 00:15 +265 -3: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display Recipes widget with multiple recipes 00:15 +266 -3: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display Recipes widget with multiple recipes 00:15 +267 -3: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display Recipes widget with multiple recipes 00:15 +268 -3: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display Recipes widget with multiple recipes 00:15 +269 -3: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display Recipes widget with multiple recipes 00:15 +270 -3: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display Recipes widget with multiple recipes 00:15 +271 -3: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display Recipes widget with multiple recipes 00:15 +272 -3: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display Recipes widget with multiple recipes 00:15 +273 -3: /home/alexander/eatvio_app/test/features/recipes/presentation/widgets/basic_widget_test.dart: Recipes Basic Widget Tests should display Recipes widget with multiple recipes 00:15 +274 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:15 +275 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:15 +276 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:15 +277 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:15 +278 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:15 +279 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:15 +280 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:15 +281 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:15 +282 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:15 +283 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:15 +284 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:15 +285 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:15 +286 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:15 +287 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:15 +288 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:15 +289 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:15 +290 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:15 +291 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:15 +292 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:15 +293 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:15 +294 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:15 +295 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:15 +296 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:15 +297 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:15 +298 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:16 +298 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:16 +299 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:16 +300 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:16 +301 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:16 +302 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:16 +303 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:16 +304 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:16 +305 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:16 +306 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:16 +307 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:16 +308 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:16 +309 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:16 +310 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit likeRecipe - обработка ошибки 00:16 +310 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:16 +311 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:16 +312 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:16 +313 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:16 +314 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:16 +315 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:16 +316 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:16 +317 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:16 +318 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:16 +319 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:16 +320 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:16 +321 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:16 +322 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:16 +323 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:16 +324 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:16 +325 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:16 +326 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:16 +327 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:16 +328 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:16 +329 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:16 +330 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:16 +331 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:17 +331 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:17 +332 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:17 +333 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:17 +334 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:17 +335 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:17 +336 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:17 +337 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:17 +338 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:17 +339 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:17 +340 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:17 +341 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:17 +342 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:17 +343 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:17 +344 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:17 +345 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:17 +346 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:17 +347 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:17 +348 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:17 +349 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:17 +350 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:17 +351 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:17 +352 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - с recipeIndex параметром 00:17 +352 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - обработка ошибки 00:17 +353 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit addToFavorite - обработка ошибки 00:17 +353 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - с recipeIndex параметром 00:17 +354 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - с recipeIndex параметром 00:17 +355 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - с recipeIndex параметром 00:17 +356 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - с recipeIndex параметром 00:17 +357 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - с recipeIndex параметром 00:17 +358 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - с recipeIndex параметром 00:17 +359 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - с recipeIndex параметром 00:18 +359 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - с recipeIndex параметром 00:18 +360 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - с recipeIndex параметром 00:18 +361 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - с recipeIndex параметром 00:18 +362 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - с recipeIndex параметром 00:18 +363 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - с recipeIndex параметром 00:18 +364 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - с recipeIndex параметром 00:18 +365 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - с recipeIndex параметром 00:18 +366 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - с recipeIndex параметром 00:18 +367 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - с recipeIndex параметром 00:18 +368 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - с recipeIndex параметром 00:18 +369 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - с recipeIndex параметром 00:18 +370 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - с recipeIndex параметром 00:18 +371 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - с recipeIndex параметром 00:18 +372 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - с recipeIndex параметром 00:18 +373 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - с recipeIndex параметром 00:18 +374 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - с recipeIndex параметром 00:18 +375 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - с recipeIndex параметром 00:18 +376 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - с recipeIndex параметром 00:18 +377 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - с recipeIndex параметром 00:18 +378 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - с recipeIndex параметром 00:18 +379 -3: /home/alexander/eatvio_app/test/features/recipe/presentation/cubit/about_recipe_cubit_test.dart: AboutRecipeCubit removeFromFavorite - с recipeIndex параметром 00:18 +380 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/feed_cubit/feed_cubit_test.dart: FeedCubit loadFeedBlogPostList - успешная загрузка постов 00:18 +381 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/feed_cubit/feed_cubit_test.dart: FeedCubit loadFeedBlogPostList - успешная загрузка постов 00:18 +382 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/feed_cubit/feed_cubit_test.dart: FeedCubit loadFeedBlogPostList - успешная загрузка постов 00:18 +383 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/feed_cubit/feed_cubit_test.dart: FeedCubit loadFeedBlogPostList - успешная загрузка постов 00:18 +384 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/feed_cubit/feed_cubit_test.dart: FeedCubit loadFeedBlogPostList - успешная загрузка постов 00:18 +385 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/feed_cubit/feed_cubit_test.dart: FeedCubit loadFeedBlogPostList - успешная загрузка постов 00:19 +386 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit loadDiets - обработка ошибки 00:19 +387 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit loadDiets - обработка ошибки 00:19 +388 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/feed_cubit_test.dart: FeedCubit начальное состояние - loading 00:19 +389 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/feed_cubit/feed_cubit_test.dart: FeedCubit loadFeedBlogPostList - обработка ошибки 00:19 +390 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit loadNextPage - загружает следующую страницу 00:19 +391 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit loadNextPage - загружает следующую страницу 00:19 +392 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit loadNextPage - загружает следующую страницу 00:19 +393 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit loadNextPage - загружает следующую страницу 00:19 +394 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit loadNextPage - загружает следующую страницу 00:19 +395 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit loadNextPage - загружает следующую страницу 00:19 +396 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit loadNextPage - загружает следующую страницу 00:19 +397 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit loadNextPage - загружает следующую страницу 00:19 +398 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit loadNextPage - загружает следующую страницу 00:19 +399 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/feed_cubit_test.dart: FeedCubit loadFeedBlogPostList - обрабатывает ошибку 00:19 +400 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/feed_cubit_test.dart: FeedCubit loadFeedBlogPostList - обрабатывает ошибку 00:19 +401 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit loadNextPage - не загружает если уже загружается 00:19 +402 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit loadNextPage - не загружает если уже загружается 00:20 +402 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit loadNextPage - не загружает если уже загружается 00:20 +403 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit loadNextPage - не загружает если уже загружается 00:20 +404 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit loadNextPage - не загружает если уже загружается 00:20 +405 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit loadNextPage - не загружает если уже загружается 00:20 +406 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit loadNextPage - не загружает если уже загружается 00:20 +407 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit loadNextPage - не загружает если уже загружается 00:20 +408 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit loadNextPage - не загружает если уже загружается 00:20 +409 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit loadNextPage - не загружает если уже загружается 00:20 +410 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit loadNextPage - не загружает если уже загружается 00:20 +411 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit loadNextPage - не загружает если уже загружается 00:20 +412 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit loadNextPage - не загружает если уже загружается 00:20 +413 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/feed_cubit_test.dart: FeedCubit loadNextPage - не загружает если уже на последней странице 00:20 +414 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/plans_cubit/plans_cubit_test.dart: PlansCubit loadPlans - обработка ошибки 00:20 +415 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit likeDiet - добавляет лайк 00:20 +416 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit likeDiet - добавляет лайк 00:20 +417 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit likeDiet - добавляет лайк 00:20 +418 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit likeDiet - добавляет лайк 00:20 +419 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/feed_cubit_test.dart: FeedCubit loadNextPage - не загружает если уже загружается 00:20 +420 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/feed_cubit_test.dart: FeedCubit loadNextPage - не загружает если уже загружается 00:20 +421 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/plans_cubit/plans_cubit_test.dart: PlansCubit loadNextPage - загружает следующую страницу 00:21 +421 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/plans_cubit/plans_cubit_test.dart: PlansCubit loadNextPage - загружает следующую страницу 00:21 +422 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/plans_cubit/plans_cubit_test.dart: PlansCubit loadNextPage - загружает следующую страницу 00:21 +423 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/plans_cubit/plans_cubit_test.dart: PlansCubit loadNextPage - загружает следующую страницу 00:21 +424 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/plans_cubit/plans_cubit_test.dart: PlansCubit loadNextPage - загружает следующую страницу 00:21 +425 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit likeDiet - убирает лайк 00:21 +426 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/recipes_cubit/recipes_cubit_test.dart: RecipesCubit loadNextPage - загружает следующую страницу 00:21 +427 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/recipes_cubit/recipes_cubit_test.dart: RecipesCubit loadNextPage - загружает следующую страницу 00:21 +428 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/plans_cubit/plans_cubit_test.dart: PlansCubit loadNextPage - не загружает если уже загружается 00:21 +428 ~1 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/plans_cubit/plans_cubit_test.dart: PlansCubit loadNextPage - не загружает если уже загружается 00:21 +429 ~1 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/plans_cubit/plans_cubit_test.dart: PlansCubit loadNextPage - не загружает если уже загружается 00:21 +430 ~1 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit addDietToFavorite - добавляет диету в избранное 00:21 +431 ~1 -3: /home/alexander/eatvio_app/test/features/feed/presentation/pages/feed_screen_smoke_test.dart: FeedScreen smoke renders FeedScreen with all tabs 00:21 +432 ~1 -3: /home/alexander/eatvio_app/test/features/feed/presentation/pages/feed_screen_smoke_test.dart: FeedScreen smoke renders FeedScreen with all tabs 00:22 +432 ~1 -3: /home/alexander/eatvio_app/test/features/feed/presentation/pages/feed_screen_smoke_test.dart: FeedScreen smoke renders FeedScreen with all tabs 00:22 +433 ~1 -3: /home/alexander/eatvio_app/test/features/feed/presentation/pages/feed_screen_smoke_test.dart: FeedScreen smoke renders FeedScreen with all tabs 00:22 +434 ~1 -3: /home/alexander/eatvio_app/test/features/feed/presentation/pages/feed_screen_smoke_test.dart: FeedScreen smoke renders FeedScreen with all tabs 00:22 +435 ~1 -3: /home/alexander/eatvio_app/test/features/feed/presentation/pages/feed_screen_smoke_test.dart: FeedScreen smoke renders FeedScreen with all tabs 00:22 +436 ~1 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit removeDietFromFavorite - убирает диету из избранного 00:22 +437 ~1 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit removeDietFromFavorite - убирает диету из избранного 00:22 +438 ~1 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit removeDietFromFavorite - убирает диету из избранного 00:22 +439 ~1 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit removeDietFromFavorite - убирает диету из избранного 00:22 +440 ~1 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/diets_cubit/diets_cubit_test.dart: DietsCubit removeDietFromFavorite - убирает диету из избранного 00:22 +441 ~1 -3: /home/alexander/eatvio_app/test/features/feed/presentation/widgets/blog_posts_test.dart: BlogPosts smoke renders blog posts widget 00:22 +442 ~1 -3: /home/alexander/eatvio_app/test/features/feed/presentation/widgets/blog_posts_test.dart: BlogPosts smoke renders blog posts widget 00:22 +443 ~1 -3: /home/alexander/eatvio_app/test/features/feed/presentation/widgets/blog_posts_test.dart: BlogPosts smoke renders blog posts widget 00:22 +444 ~1 -3: /home/alexander/eatvio_app/test/features/feed/presentation/widgets/blog_posts_test.dart: BlogPosts smoke renders blog posts widget 00:22 +445 ~1 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/recipes_cubit/recipes_cubit_test.dart: RecipesCubit removeRecipeFromFavorite - удаляет рецепт из избранного 00:22 +446 ~1 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/recipes_cubit/recipes_cubit_test.dart: RecipesCubit removeRecipeFromFavorite - удаляет рецепт из избранного 00:22 +447 ~1 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/recipes_cubit/recipes_cubit_test.dart: RecipesCubit removeRecipeFromFavorite - удаляет рецепт из избранного 00:22 +448 ~1 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/recipes_cubit/recipes_cubit_test.dart: RecipesCubit removeRecipeFromFavorite - удаляет рецепт из избранного 00:23 +448 ~1 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/recipes_cubit/recipes_cubit_test.dart: RecipesCubit removeRecipeFromFavorite - удаляет рецепт из избранного 00:23 +449 ~1 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/recipes_cubit/recipes_cubit_test.dart: RecipesCubit removeRecipeFromFavorite - удаляет рецепт из избранного 00:23 +450 ~1 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/recipes_cubit/recipes_cubit_test.dart: RecipesCubit removeRecipeFromFavorite - удаляет рецепт из избранного 00:23 +451 ~1 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/recipes_cubit/recipes_cubit_test.dart: RecipesCubit removeRecipeFromFavorite - удаляет рецепт из избранного 00:23 +452 ~1 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/recipes_cubit/recipes_cubit_test.dart: RecipesCubit removeRecipeFromFavorite - удаляет рецепт из избранного 00:23 +453 ~1 -3: /home/alexander/eatvio_app/test/features/feed/presentation/bloc/recipes_cubit/recipes_cubit_test.dart: RecipesCubit removeRecipeFromFavorite - удаляет рецепт из избранного 00:23 +454 ~1 -3: /home/alexander/eatvio_app/test/features/feed/presentation/widgets/chip_group_test.dart: ChipGroup smoke renders chip group 00:23 +455 ~1 -3: /home/alexander/eatvio_app/test/features/feed/presentation/widgets/chip_group_test.dart: ChipGroup smoke renders chip group 00:23 +456 ~1 -3: /home/alexander/eatvio_app/test/features/feed/presentation/widgets/chip_group_test.dart: ChipGroup smoke renders chip group 00:23 +456 ~1 -3: /home/alexander/eatvio_app/test/features/feed/presentation/widgets/basic_widget_test.dart: Feed Basic Widget Tests should display BlogPosts widget with loaded state +unhandled element