From 0cdd40102f5fc76ad0ae1cbfec91875058557103 Mon Sep 17 00:00:00 2001 From: Username1548 Date: Fri, 28 Feb 2025 19:18:01 +0700 Subject: [PATCH] updated error handling, consultant rating layout and complaining logic --- assets/icons/cloud.svg | 5 + assets/icons/no_internet.svg | 6 + assets/icons/right_arrow.svg | 3 + ios/Runner.xcodeproj/project.pbxproj | 9 + lib/core/app_router.dart | 19 +- .../enums.dart => enums/base_state.dart} | 6 - lib/core/enums/complained_content.dart | 7 + lib/core/enums/product_type.dart | 5 + lib/core/locator_service.dart | 6 + lib/core/theme/custom_text_style.dart | 6 + lib/core/theme/theme_helper.dart | 4 + lib/core/widgets/data_error_widget.dart | 68 ++ lib/core/widgets/info_row.dart | 2 +- lib/core/widgets/show_error_dialog.dart | 59 ++ .../success_published_diet_bottom_sheet.dart | 4 +- ...success_published_recipe_bottom_sheet.dart | 57 ++ .../presentation/pages/blog_post_screen.dart | 22 +- .../blog_post_create_cubit.dart | 65 +- .../pages/blog_post_create_screen.dart | 25 +- .../bloc/chat_room_cubit/chat_room_cubit.dart | 7 + .../bloc/chats_cubit/chats_cubit.dart | 2 +- .../bloc/chats_cubit/chats_state.dart | 2 +- .../chat/presentation/pages/chat_screen.dart | 5 +- .../chat/presentation/pages/chats_screen.dart | 8 +- .../widgets/chat_entry_widget.dart | 14 +- .../complaining/models/complain_model.dart | 15 + .../models/complain_model.freezed.dart | 193 ++++++ .../complaining/models/complain_model.g.dart | 21 + .../complaining_bottom_sheet.dart | 590 ++++++++++++++++ .../presentation/complaining_screen.dart | 10 - .../presentation/cubit/complaining_cubit.dart | 79 +++ .../presentation/cubit/complaining_state.dart | 20 + .../cubit/complaining_state.freezed.dart | 298 +++++++++ .../repositories/complaining_repository.dart | 55 ++ .../services/complaining_service.dart | 39 ++ .../services/complaining_service.g.dart | 138 ++++ .../models/rating_model.dart | 16 + .../models/rating_model.freezed.dart | 200 ++++++ .../models/rating_model.g.dart | 21 + .../consultant_rating_screen.dart | 76 +++ .../widgets/period_filter_widget.dart | 67 ++ .../widgets/rating_history_item.dart | 49 ++ .../widgets/rating_history_widget.dart | 69 ++ .../presentation/widgets/rating_widget.dart | 56 ++ .../widgets/statistic_bouble.dart | 37 + .../widgets/statistic_widget.dart | 79 +++ .../presentation/create_diet_screen.dart | 13 +- .../presentation/cubit/create_diet_cubit.dart | 10 +- .../presentation/diet_preview_screen.dart | 2 +- .../repositories/create_diet_repository.dart | 2 +- .../services/create_diet_service.dart | 6 +- .../services/create_diet_service.g.dart | 6 +- .../pages/recipe_create_screen.dart | 21 +- .../presentation/pages/diary_screen.dart | 6 + .../presentation/cubit/about_diet_cubit.dart | 2 + .../presentation/pages/about_diet_screen.dart | 94 ++- .../presentation/widgets/more_options.dart | 20 +- .../feed/models/diets_pagination_model.g.dart | 115 ++-- .../bloc/diets_cubit/diets_cubit.dart | 8 +- .../feed/presentation/pages/feed_screen.dart | 1 - .../feed/presentation/widgets/blog_posts.dart | 13 +- .../presentation/widgets/consultants.dart | 17 +- .../feed/presentation/widgets/diets.dart | 11 +- .../presentation/widgets/diets_widget.dart | 2 +- .../presentation/widgets/plan_widget.dart | 2 +- .../feed/presentation/widgets/plans.dart | 13 +- .../feed/presentation/widgets/recipes.dart | 13 +- .../internet_connection_checker_screen.dart | 4 +- .../presentation/cubit/my_diets_cubit.dart | 13 +- .../presentation/cubit/my_diets_state.dart | 3 + .../cubit/my_diets_state.freezed.dart | 71 +- .../presentation/my_diets_screen.dart | 24 +- .../presentation/widgets/my_diet_widget.dart | 2 +- .../presentation/my_plans_screen.dart | 11 +- .../presentation/widgets/my_plan_widget.dart | 2 +- .../presentation/cubit/about_plan_cubit.dart | 2 +- .../presentation/pages/about_plan_screen.dart | 92 ++- .../bloc/profile_cubit/profile_cubit.dart | 4 +- .../presentation/pages/profile_screen.dart | 46 +- .../presentation/about_recipe_screen.dart | 89 ++- .../cubit/about_recipe_cubit.dart | 13 +- .../presentation/pages/settings_screen.dart | 12 +- .../presentation/user_details_screen.dart | 17 +- lib/generated/intl/messages_en.dart | 110 +++ lib/generated/l10n.dart | 630 ++++++++++++++++++ lib/l10n/intl_en.arb | 66 +- 86 files changed, 3853 insertions(+), 279 deletions(-) create mode 100644 assets/icons/cloud.svg create mode 100644 assets/icons/no_internet.svg create mode 100644 assets/icons/right_arrow.svg rename lib/core/{utils/enums.dart => enums/base_state.dart} (54%) create mode 100644 lib/core/enums/complained_content.dart create mode 100644 lib/core/enums/product_type.dart create mode 100644 lib/core/widgets/data_error_widget.dart create mode 100644 lib/core/widgets/show_error_dialog.dart create mode 100644 lib/core/widgets/success_published_recipe_bottom_sheet.dart create mode 100644 lib/features/complaining/models/complain_model.dart create mode 100644 lib/features/complaining/models/complain_model.freezed.dart create mode 100644 lib/features/complaining/models/complain_model.g.dart create mode 100644 lib/features/complaining/presentation/complaining_bottom_sheet.dart delete mode 100644 lib/features/complaining/presentation/complaining_screen.dart create mode 100644 lib/features/complaining/presentation/cubit/complaining_cubit.dart create mode 100644 lib/features/complaining/presentation/cubit/complaining_state.dart create mode 100644 lib/features/complaining/presentation/cubit/complaining_state.freezed.dart create mode 100644 lib/features/complaining/repositories/complaining_repository.dart create mode 100644 lib/features/complaining/services/complaining_service.dart create mode 100644 lib/features/complaining/services/complaining_service.g.dart create mode 100644 lib/features/consultant_rating/models/rating_model.dart create mode 100644 lib/features/consultant_rating/models/rating_model.freezed.dart create mode 100644 lib/features/consultant_rating/models/rating_model.g.dart create mode 100644 lib/features/consultant_rating/presentation/consultant_rating_screen.dart create mode 100644 lib/features/consultant_rating/presentation/widgets/period_filter_widget.dart create mode 100644 lib/features/consultant_rating/presentation/widgets/rating_history_item.dart create mode 100644 lib/features/consultant_rating/presentation/widgets/rating_history_widget.dart create mode 100644 lib/features/consultant_rating/presentation/widgets/rating_widget.dart create mode 100644 lib/features/consultant_rating/presentation/widgets/statistic_bouble.dart create mode 100644 lib/features/consultant_rating/presentation/widgets/statistic_widget.dart diff --git a/assets/icons/cloud.svg b/assets/icons/cloud.svg new file mode 100644 index 00000000..422941f0 --- /dev/null +++ b/assets/icons/cloud.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/no_internet.svg b/assets/icons/no_internet.svg new file mode 100644 index 00000000..e2b89a16 --- /dev/null +++ b/assets/icons/no_internet.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/icons/right_arrow.svg b/assets/icons/right_arrow.svg new file mode 100644 index 00000000..06f74177 --- /dev/null +++ b/assets/icons/right_arrow.svg @@ -0,0 +1,3 @@ + + + diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index b335af4e..e7e549aa 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -361,6 +361,8 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 16; DEVELOPMENT_TEAM = VL68C36HYS; ENABLE_BITCODE = NO; @@ -382,6 +384,7 @@ MARKETING_VERSION = 2.0.0; PRODUCT_BUNDLE_IDENTIFIER = com.eatvioapp.app; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; @@ -507,6 +510,8 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 16; DEVELOPMENT_TEAM = VL68C36HYS; ENABLE_BITCODE = NO; @@ -528,6 +533,7 @@ MARKETING_VERSION = 2.0.0; PRODUCT_BUNDLE_IDENTIFIER = com.eatvioapp.app; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; @@ -541,6 +547,8 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 16; DEVELOPMENT_TEAM = VL68C36HYS; ENABLE_BITCODE = NO; @@ -562,6 +570,7 @@ MARKETING_VERSION = 2.0.0; PRODUCT_BUNDLE_IDENTIFIER = com.eatvioapp.app; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; diff --git a/lib/core/app_router.dart b/lib/core/app_router.dart index eef0cb3a..135b465e 100644 --- a/lib/core/app_router.dart +++ b/lib/core/app_router.dart @@ -12,13 +12,13 @@ import 'package:eatvio_app/features/chat/presentation/bloc/chats_cubit/chats_cub import 'package:eatvio_app/features/chat/presentation/pages/chat_screen.dart'; import 'package:eatvio_app/features/chat/presentation/pages/chats_screen.dart'; import 'package:eatvio_app/features/chat/presentation/pages/photo_view_screen.dart'; -import 'package:eatvio_app/features/complaining/presentation/complaining_screen.dart'; import 'package:eatvio_app/features/consultant/become_consultant/presentation/pages/become_consultant_screen.dart'; import 'package:eatvio_app/features/consultant/become_consultant/presentation/pages/stepper_become_consultant_screen.dart'; import 'package:eatvio_app/features/consultations/presentation/pages/consultations_screen.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/create_plan/presentation/create_plan_screen.dart'; +import 'package:eatvio_app/features/consultant_rating/presentation/consultant_rating_screen.dart'; import 'package:eatvio_app/features/dev_tools/presentation/dev_tools_screen.dart'; import 'package:eatvio_app/features/diary/presentation/pages/diary_screen.dart'; import 'package:eatvio_app/features/diets/presentation/pages/about_diet_screen.dart'; @@ -66,7 +66,7 @@ class AppRouter { name: 'authScreen', redirect: (context, state) async { // TokenStorage.removeTokenInStorage(); - // await TokenStorage.saveTokenInStorage('1|laravel_sanctum_zaU2133RQ0wbxDYdH7IQxTpThmilWtKhzxbSt5X1065fcae3'); + // await TokenStorage.saveTokenInStorage('Bearer 3|laravel_sanctum_zoubsO9NPXAqgLZEdHV9R3XbuDUHJJMJyXcd77mhbb649f04'); if (await TokenStorage.haveTokenInStorage()) { return '/my-profile-screen'; } @@ -224,7 +224,10 @@ class AppRouter { final data = state.extra as Map; final chatsCubit = data['chatsCubit'] as ChatsCubit; final chatId = data['chatId'] as String; - return ChatScreen(chatId: chatId, chatsCubit: chatsCubit,); + return ChatScreen( + chatId: chatId, + chatsCubit: chatsCubit, + ); }, ), GoRoute( @@ -311,11 +314,6 @@ class AppRouter { index: int.parse(state.pathParameters['index']!), ), ), - GoRoute( - path: '/complaining-screen', - name: 'complainingScreen', - builder: (context, state) => ComplainingScreen(), - ), GoRoute( path: CreateDietScreen.path, name: CreateDietScreen.name, @@ -361,6 +359,11 @@ class AppRouter { name: DevToolsScreen.name, builder: (context, state) => DevToolsScreen(), ), + GoRoute( + path: ConsultantRatingScreen.path, + name: ConsultantRatingScreen.name, + builder: (context, state) => ConsultantRatingScreen(), + ), ], ); } diff --git a/lib/core/utils/enums.dart b/lib/core/enums/base_state.dart similarity index 54% rename from lib/core/utils/enums.dart rename to lib/core/enums/base_state.dart index 97413565..9a65b9e5 100644 --- a/lib/core/utils/enums.dart +++ b/lib/core/enums/base_state.dart @@ -1,9 +1,3 @@ -enum ProductType { - diet, - plan, - recipe -} - enum BaseState { loading, error, diff --git a/lib/core/enums/complained_content.dart b/lib/core/enums/complained_content.dart new file mode 100644 index 00000000..66fe8ee1 --- /dev/null +++ b/lib/core/enums/complained_content.dart @@ -0,0 +1,7 @@ +enum ComplainedContent { + diet, + plan, + recipe, + profile, + posts +} \ No newline at end of file diff --git a/lib/core/enums/product_type.dart b/lib/core/enums/product_type.dart new file mode 100644 index 00000000..415487f3 --- /dev/null +++ b/lib/core/enums/product_type.dart @@ -0,0 +1,5 @@ +enum ProductType { + diet, + plan, + recipe +} \ No newline at end of file diff --git a/lib/core/locator_service.dart b/lib/core/locator_service.dart index 9265ee7d..f5caadfb 100644 --- a/lib/core/locator_service.dart +++ b/lib/core/locator_service.dart @@ -5,6 +5,9 @@ import 'package:eatvio_app/core/websockets/ws_service.dart'; import 'package:eatvio_app/features/auth/presentation/bloc/auth_cubit.dart'; import 'package:eatvio_app/features/auth/repositories/auth_repository.dart'; import 'package:eatvio_app/features/auth/services/auth_service.dart'; +import 'package:eatvio_app/features/complaining/presentation/cubit/complaining_cubit.dart'; +import 'package:eatvio_app/features/complaining/repositories/complaining_repository.dart'; +import 'package:eatvio_app/features/complaining/services/complaining_service.dart'; import 'package:eatvio_app/features/create_diet/presentation/cubit/create_diet_cubit.dart'; import 'package:eatvio_app/features/create_diet/repositories/create_diet_repository.dart'; import 'package:eatvio_app/features/create_diet/services/create_diet_service.dart'; @@ -112,6 +115,7 @@ Future init() async { sl.registerFactory(() => ConsultantsCubit(feedRepository: sl())); sl.registerFactory(() => AboutRecipeCubit(recipeRepository: sl(), profileRepository: sl())); sl.registerFactory(() => AuthCubit(authRepository: sl())); + sl.registerFactory(() => ComplainingCubit(repository: sl())); // Repository sl.registerLazySingleton(() => ChatRepository(chatService: sl())); @@ -132,6 +136,7 @@ Future init() async { sl.registerLazySingleton(() => MyDietsRepository(myDietsService: sl())); sl.registerLazySingleton(() => MyPlansRepository(myPlansService: sl())); sl.registerLazySingleton(() => AboutRecipeRepository(recipeService: sl())); + sl.registerLazySingleton(() => ComplainingRepository(service: sl())); sl.registerLazySingleton(() => AuthRepository(authService: sl())); // Services @@ -154,6 +159,7 @@ Future init() async { sl.registerLazySingleton(() => MyPlansService(sl())); sl.registerLazySingleton(() => WsService(sl())); sl.registerLazySingleton(() => AboutRecipeService(sl())); + sl.registerLazySingleton(() => ComplainingService(sl())); sl.registerLazySingleton(() => AuthService(sl())); // External diff --git a/lib/core/theme/custom_text_style.dart b/lib/core/theme/custom_text_style.dart index 4ee16600..2e39baf8 100644 --- a/lib/core/theme/custom_text_style.dart +++ b/lib/core/theme/custom_text_style.dart @@ -184,6 +184,12 @@ abstract class TextStyles { height: 1.255, ); + static const headingFS40FW600 = TextStyle( + fontSize: 40, + fontWeight: FontWeight.w600, + height: 1.255, + ); + static const textLSemibold = TextStyle( fontSize: 18, fontWeight: FontWeight.w600, diff --git a/lib/core/theme/theme_helper.dart b/lib/core/theme/theme_helper.dart index 31fbd60b..2c319dd8 100644 --- a/lib/core/theme/theme_helper.dart +++ b/lib/core/theme/theme_helper.dart @@ -190,6 +190,10 @@ class PrimaryColors { Color get transparentBlack => Color(0xFF1313264).withValues(alpha: 0.3); Color get gray60004 => Color(0xFFD4DAE2).withValues(alpha: 0.2); + + Color get blueGray => Color(0xFF809CB9); + + Color get dialogShadow => Color.fromARGB(125, 0, 0, 0); } PrimaryColors get appTheme => ThemeHelper().themeColor(); diff --git a/lib/core/widgets/data_error_widget.dart b/lib/core/widgets/data_error_widget.dart new file mode 100644 index 00000000..1ce1c110 --- /dev/null +++ b/lib/core/widgets/data_error_widget.dart @@ -0,0 +1,68 @@ +import 'package:eatvio_app/core/theme/custom_text_style.dart'; +import 'package:eatvio_app/core/theme/theme_helper.dart'; +import 'package:eatvio_app/core/widgets/custom_outlined_button.dart'; +import 'package:eatvio_app/core/widgets/loader.dart'; +import 'package:eatvio_app/generated/l10n.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; + +class DataErrorWidget extends StatefulWidget { + const DataErrorWidget({super.key, required this.onReload}); + + final Future Function() onReload; + + @override + State createState() => _DataErrorWidgetState(); +} + +class _DataErrorWidgetState extends State { + bool isLoading = false; + @override + Widget build(BuildContext context) { + final s = S.of(context); + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SvgPicture.asset('assets/icons/cloud.svg'), + SizedBox(height: 30), + Text( + s.something_went_wrong, + style: TextStyles.textLSemibold.copyWith(color: appTheme.black), + ), + SizedBox(height: 12), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 32), + child: Text( + s.can_not_load_data_please_try_again, + style: TextStyles.textSMedium.copyWith(color: appTheme.black), + textAlign: TextAlign.center, + ), + ), + SizedBox(height: 20), + isLoading + ? Loader() + : Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: CustomOutlinedButton( + text: s.try_again, + height: 52, + buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700), + buttonStyle: ButtonStyle( + side: WidgetStatePropertyAll(BorderSide.none), + backgroundColor: WidgetStatePropertyAll(appTheme.blueA400), + ), + onPressed: () async { + setState(() { + isLoading = true; + }); + await widget.onReload(); + setState(() { + isLoading = false; + }); + }, + ), + ), + ], + ); + } +} diff --git a/lib/core/widgets/info_row.dart b/lib/core/widgets/info_row.dart index 7dc1ad73..9f535d91 100644 --- a/lib/core/widgets/info_row.dart +++ b/lib/core/widgets/info_row.dart @@ -1,5 +1,5 @@ import 'package:eatvio_app/core/theme/custom_text_style.dart'; -import 'package:eatvio_app/core/utils/enums.dart'; +import 'package:eatvio_app/core/enums/product_type.dart'; import 'package:eatvio_app/core/utils/localization_utils.dart'; import 'package:eatvio_app/features/diets/presentation/widgets/info_bouble.dart'; import 'package:eatvio_app/features/diets/presentation/widgets/type_badges.dart'; diff --git a/lib/core/widgets/show_error_dialog.dart b/lib/core/widgets/show_error_dialog.dart new file mode 100644 index 00000000..317f3321 --- /dev/null +++ b/lib/core/widgets/show_error_dialog.dart @@ -0,0 +1,59 @@ +import 'package:eatvio_app/core/app_router.dart'; +import 'package:eatvio_app/core/theme/custom_text_style.dart'; +import 'package:eatvio_app/core/theme/theme_helper.dart'; +import 'package:eatvio_app/generated/l10n.dart'; +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; + +void showErrorDialog(BuildContext context, {String? title, String? description}) { + showDialog( + context: context, + barrierColor: Colors.transparent, + builder: (BuildContext context) { + final s = S.of(context); + return Center( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Container( + decoration: BoxDecoration( + color: appTheme.whiteA700, + borderRadius: BorderRadius.all(Radius.circular(20)), + boxShadow: [BoxShadow(color: appTheme.dialogShadow, blurRadius: 32)], + ), + width: MediaQuery.sizeOf(context).width, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 20), + Text( + title ?? s.some_error_occurred, + style: TextStyles.textLSemibold.copyWith(color: appTheme.black, decoration: TextDecoration.none), + ), + SizedBox(height: 12), + Padding( + padding: const EdgeInsets.only(right: 72), + child: Text( + description ?? s.some_error_occurred_description, + style: TextStyles.textSMedium.copyWith(color: appTheme.black, decoration: TextDecoration.none), + ), + ), + Align( + alignment: Alignment.centerRight, + child: TextButton( + onPressed: () => AppRouter.navigatorKey.currentContext?.pop(), + child: Text(s.ok, style: TextStyles.textLSemibold.copyWith(color: appTheme.blueA400)), + ), + ), + SizedBox(height: 12), + ], + ), + ), + ), + ), + ); + }, + ); +} diff --git a/lib/core/widgets/success_published_diet_bottom_sheet.dart b/lib/core/widgets/success_published_diet_bottom_sheet.dart index a3857bf4..606aad41 100644 --- a/lib/core/widgets/success_published_diet_bottom_sheet.dart +++ b/lib/core/widgets/success_published_diet_bottom_sheet.dart @@ -3,6 +3,7 @@ import 'package:eatvio_app/core/theme/custom_text_style.dart'; import 'package:eatvio_app/core/theme/theme_helper.dart'; import 'package:eatvio_app/core/widgets/bottom_sheet_handler_bar.dart'; import 'package:eatvio_app/core/widgets/custom_outlined_button.dart'; +import 'package:eatvio_app/generated/l10n.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; @@ -11,6 +12,7 @@ class SuccessPublishedDietBottomSheet extends StatelessWidget { final String title; @override Widget build(BuildContext context) { + final s = S.of(context); return Column( children: [ SizedBox( @@ -37,7 +39,7 @@ class SuccessPublishedDietBottomSheet extends StatelessWidget { onPressed: () { AppRouter.navigatorKey.currentContext?.pop(); }, - text: 'Хорошо', + text: s.ok, height: 52, buttonTextStyle: TextStyles.textSSemibold.copyWith(color: appTheme.whiteA700), buttonStyle: ButtonStyle( diff --git a/lib/core/widgets/success_published_recipe_bottom_sheet.dart b/lib/core/widgets/success_published_recipe_bottom_sheet.dart new file mode 100644 index 00000000..c97736ae --- /dev/null +++ b/lib/core/widgets/success_published_recipe_bottom_sheet.dart @@ -0,0 +1,57 @@ +import 'package:eatvio_app/core/app_router.dart'; +import 'package:eatvio_app/core/theme/custom_text_style.dart'; +import 'package:eatvio_app/core/theme/theme_helper.dart'; +import 'package:eatvio_app/core/widgets/bottom_sheet_handler_bar.dart'; +import 'package:eatvio_app/core/widgets/custom_outlined_button.dart'; +import 'package:eatvio_app/generated/l10n.dart'; +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; + +class SuccessPublishedRecipeBottomSheet extends StatelessWidget { + const SuccessPublishedRecipeBottomSheet({super.key, required this.title}); + final String title; + @override + Widget build(BuildContext context) { + final s = S.of(context); + return Column( + children: [ + SizedBox( + height: 20, + ), + BottomSheetHandlerBar(), + SizedBox( + height: 20, + ), + Text( + s.recipe_publishing, + style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black), + ), + Text( + '"$title" ${s.successfully_published}', + style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black), + ), + SizedBox( + height: 20, + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: CustomOutlinedButton( + onPressed: () { + AppRouter.navigatorKey.currentContext?.pop(); + }, + text: s.ok, + height: 52, + buttonTextStyle: TextStyles.textSSemibold.copyWith(color: appTheme.whiteA700), + buttonStyle: ButtonStyle( + side: WidgetStatePropertyAll(BorderSide.none), + backgroundColor: WidgetStatePropertyAll(appTheme.blueA400), + ), + ), + ), + SizedBox( + height: 20, + ), + ], + ); + } +} diff --git a/lib/features/blog_post/presentation/pages/blog_post_screen.dart b/lib/features/blog_post/presentation/pages/blog_post_screen.dart index 1696928a..637a2a56 100644 --- a/lib/features/blog_post/presentation/pages/blog_post_screen.dart +++ b/lib/features/blog_post/presentation/pages/blog_post_screen.dart @@ -1,7 +1,10 @@ import 'dart:io'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:eatvio_app/core/const.dart'; +import 'package:eatvio_app/core/enums/complained_content.dart'; import 'package:eatvio_app/core/utils/date_time_utils.dart'; +import 'package:eatvio_app/core/widgets/show_error_dialog.dart'; +import 'package:eatvio_app/features/complaining/presentation/complaining_bottom_sheet.dart'; import 'package:eatvio_app/core/widgets/loader.dart'; import 'package:eatvio_app/features/blog_post_create/bloc/blog_post_create_cubit/blog_post_create_cubit.dart'; import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart'; @@ -11,6 +14,7 @@ import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_cubit import 'package:eatvio_app/features/feed/presentation/widgets/comments_widget.dart'; import 'package:eatvio_app/core/locator_service.dart'; import 'package:eatvio_app/core/theme/theme_helper.dart'; +import 'package:eatvio_app/generated/l10n.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_html/flutter_html.dart'; @@ -191,9 +195,13 @@ class _BlogPostScreenState extends State { title: Text(widget.blogPost.title), actions: [ PopupMenuButton( - onSelected: (value) { + onSelected: (value) async { if (value == 'delete') { - sl().deleteBlogPost(widget.blogPost.id); + final result = await sl().deleteBlogPost(widget.blogPost.id); + if (!result) { + showErrorDialog(context, title: S.current.could_not_delete_blog_post); + return; + } context.read().loadFeedBlogPostList(); Navigator.of(context).pop(); } @@ -219,6 +227,16 @@ class _BlogPostScreenState extends State { PopupMenuItem( value: 'report', child: Text('Пожаловаться'), + onTap: () { + showModalBottomSheet( + context: context, + isScrollControlled: true, + builder: (context) => ComplainingBottomSheet( + complainedContentType: ComplainedContent.posts, + id: widget.blogPost.id, + ), + ); + }, ), ], ), diff --git a/lib/features/blog_post_create/bloc/blog_post_create_cubit/blog_post_create_cubit.dart b/lib/features/blog_post_create/bloc/blog_post_create_cubit/blog_post_create_cubit.dart index 90ee1e88..8d2d1bf0 100644 --- a/lib/features/blog_post_create/bloc/blog_post_create_cubit/blog_post_create_cubit.dart +++ b/lib/features/blog_post_create/bloc/blog_post_create_cubit/blog_post_create_cubit.dart @@ -1,3 +1,5 @@ +import 'dart:developer'; + import 'package:eatvio_app/features/blog_post_create/bloc/blog_post_create_cubit/blog_post_create_state.dart'; import 'package:eatvio_app/features/blog_post_create/repositories/blog_post_repository.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -10,23 +12,29 @@ class BlogPostCreateCubit extends Cubit { required this.blogPostRepository, }) : super(BlogPostCreateState()); - createBlogPost({ + Future createBlogPost({ String? title, String? videoUrl, String? description, XFile? image, }) async { - emit(BlogPostCreateState.loading()); - await blogPostRepository.createBlogPost( - title, - videoUrl, - description, - image, - ); - emit(BlogPostCreateState.loaded()); + try { + emit(BlogPostCreateState.loading()); + await blogPostRepository.createBlogPost( + title, + videoUrl, + description, + image, + ); + return true; + } catch (e, st) { + log(e.toString(), stackTrace: st); + emit(BlogPostCreateState.loaded()); + return false; + } } - updateBlogPost({ + Future updateBlogPost({ required int id, String? title, String? videoUrl, @@ -34,19 +42,32 @@ class BlogPostCreateCubit extends Cubit { XFile? image, String? oldImageUrl, }) async { - emit(BlogPostCreateState.loading()); - await blogPostRepository.updateBlogPost( - id, - title, - videoUrl, - description, - image, - oldImageUrl, - ); - emit(BlogPostCreateState.loaded()); + try { + emit(BlogPostCreateState.loading()); + await blogPostRepository.updateBlogPost( + id, + title, + videoUrl, + description, + image, + oldImageUrl, + ); + emit(BlogPostCreateState.loaded()); + return true; + } catch (e, st) { + log(e.toString(), stackTrace: st); + emit(BlogPostCreateState.loaded()); + return false; + } } - deleteBlogPost(int id) async { - await blogPostRepository.deleteBlogPost(id); + Future deleteBlogPost(int id) async { + try { + await blogPostRepository.deleteBlogPost(id); + return true; + } catch (e, st) { + log(e.toString(), stackTrace: st); + return false; + } } } 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 9e7a85d3..04102415 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 @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:io'; import 'package:eatvio_app/core/const.dart'; import 'package:eatvio_app/core/theme/app_decoration.dart'; +import 'package:eatvio_app/core/widgets/show_error_dialog.dart'; import 'package:eatvio_app/features/blog_post_create/bloc/blog_post_create_cubit/blog_post_create_cubit.dart'; import 'package:eatvio_app/features/blog_post_create/bloc/blog_post_create_cubit/blog_post_create_state.dart'; import 'package:eatvio_app/core/locator_service.dart'; @@ -9,10 +10,12 @@ import 'package:eatvio_app/core/theme/theme_helper.dart'; import 'package:eatvio_app/core/widgets/custom_text_form_field.dart'; import 'package:eatvio_app/features/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/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart'; +import 'package:go_router/go_router.dart'; import 'package:image_picker/image_picker.dart'; class BlogPostCreateScreen extends StatefulWidget { @@ -65,7 +68,6 @@ class _BlogPostCreateScreenState extends State { listener: (BuildContext context, BlogPostCreateState state) { if (state is BlogPostCreateStateLoaded) { context.read().loadFeedBlogPostList(); - Navigator.of(context).pop(); } }, child: _buildBlogPostCreate(), @@ -148,8 +150,9 @@ class _BlogPostCreateScreenState extends State { setState(() { isLoading = true; }); + if (widget.feedBlogPostModel != null) { - await blogPostCubit.updateBlogPost( + final result = await blogPostCubit.updateBlogPost( id: widget.feedBlogPostModel!.id, title: titleController.text, description: descriptionController.text, @@ -157,13 +160,29 @@ class _BlogPostCreateScreenState extends State { image: image, oldImageUrl: widget.feedBlogPostModel!.image, ); + setState(() { + isLoading = false; + }); + if (!result) { + showErrorDialog(context, title: S.current.could_not_update_blog_post); + } else { + context.pop(); + } } else { - await blogPostCubit.createBlogPost( + final result = await blogPostCubit.createBlogPost( title: titleController.text, description: descriptionController.text, videoUrl: videoUrlController.text, image: image, ); + setState(() { + isLoading = false; + }); + if (!result) { + showErrorDialog(context, title: S.current.could_not_publish_blog_post); + } else { + context.pop(); + } } }, ), diff --git a/lib/features/chat/presentation/bloc/chat_room_cubit/chat_room_cubit.dart b/lib/features/chat/presentation/bloc/chat_room_cubit/chat_room_cubit.dart index 980b233f..a7f94802 100644 --- a/lib/features/chat/presentation/bloc/chat_room_cubit/chat_room_cubit.dart +++ b/lib/features/chat/presentation/bloc/chat_room_cubit/chat_room_cubit.dart @@ -1,8 +1,10 @@ +import 'dart:async'; import 'dart:convert'; import 'dart:developer'; import 'dart:io'; import 'package:audioplayers/audioplayers.dart'; +import 'package:dart_pusher_channels/dart_pusher_channels.dart'; import 'package:easy_debounce/easy_debounce.dart'; import 'package:eatvio_app/core/image_compressing/image_compress_utility.dart'; import 'package:eatvio_app/core/websockets/ws_manager.dart'; @@ -15,6 +17,8 @@ class ChatRoomCubit extends Cubit { final ChatRepository chatRepository; final WsManager wsManager; + StreamSubscription? typingSubscription; + StreamSubscription? messagesSubscription; int currentPage = 1; late final int lastPage; bool isLoading = false; @@ -75,6 +79,9 @@ class ChatRoomCubit extends Cubit { ), ); } catch (e) { + typingSubscription?.cancel(); + messagesSubscription?.cancel(); + wsManager.unsubscribeDirectChatChannel(); emit(ChatRoomState.error(errorMessage: 'Ошибка получеия данных')); } } diff --git a/lib/features/chat/presentation/bloc/chats_cubit/chats_cubit.dart b/lib/features/chat/presentation/bloc/chats_cubit/chats_cubit.dart index f4425e7e..f165ca83 100644 --- a/lib/features/chat/presentation/bloc/chats_cubit/chats_cubit.dart +++ b/lib/features/chat/presentation/bloc/chats_cubit/chats_cubit.dart @@ -1,7 +1,7 @@ import 'dart:async'; import 'dart:convert'; -import 'package:eatvio_app/core/utils/enums.dart'; +import 'package:eatvio_app/core/enums/base_state.dart'; import 'package:eatvio_app/core/websockets/ws_manager.dart'; import 'package:eatvio_app/features/chat/models/chat_message_model.dart'; import 'package:eatvio_app/features/chat/presentation/bloc/chats_cubit/chats_state.dart'; diff --git a/lib/features/chat/presentation/bloc/chats_cubit/chats_state.dart b/lib/features/chat/presentation/bloc/chats_cubit/chats_state.dart index f7f74987..24bde4fa 100644 --- a/lib/features/chat/presentation/bloc/chats_cubit/chats_state.dart +++ b/lib/features/chat/presentation/bloc/chats_cubit/chats_state.dart @@ -1,4 +1,4 @@ -import 'package:eatvio_app/core/utils/enums.dart'; +import 'package:eatvio_app/core/enums/base_state.dart'; import 'package:eatvio_app/features/chat/models/chat_room_preview_model.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; diff --git a/lib/features/chat/presentation/pages/chat_screen.dart b/lib/features/chat/presentation/pages/chat_screen.dart index 6d12e27d..4144fbd5 100644 --- a/lib/features/chat/presentation/pages/chat_screen.dart +++ b/lib/features/chat/presentation/pages/chat_screen.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'dart:math'; import 'package:easy_debounce/easy_debounce.dart'; +import 'package:eatvio_app/core/widgets/data_error_widget.dart'; import 'package:eatvio_app/core/widgets/loader.dart'; import 'package:eatvio_app/features/chat/presentation/bloc/chat_room_cubit/chat_room_cubit.dart'; import 'package:eatvio_app/features/chat/presentation/bloc/chat_room_cubit/chat_room_state.dart'; @@ -66,9 +67,7 @@ class _ChatScreenState extends State { } else if (state is ChatRoomStateError) { return Scaffold( appBar: AppBar(), - body: Center( - child: Text(state.errorMessage), - ), + body: DataErrorWidget(onReload: () => chatRoomCubit.initChat(widget.chatId)), ); } else { return Scaffold( diff --git a/lib/features/chat/presentation/pages/chats_screen.dart b/lib/features/chat/presentation/pages/chats_screen.dart index 186dc890..18f2b581 100644 --- a/lib/features/chat/presentation/pages/chats_screen.dart +++ b/lib/features/chat/presentation/pages/chats_screen.dart @@ -1,5 +1,6 @@ import 'package:easy_debounce/easy_debounce.dart'; -import 'package:eatvio_app/core/utils/enums.dart'; +import 'package:eatvio_app/core/enums/base_state.dart'; +import 'package:eatvio_app/core/widgets/data_error_widget.dart'; import 'package:eatvio_app/core/widgets/loader.dart'; import 'package:eatvio_app/features/chat/presentation/bloc/chats_cubit/chats_cubit.dart'; import 'package:eatvio_app/features/chat/presentation/bloc/chats_cubit/chats_state.dart'; @@ -79,9 +80,8 @@ class _ChatsScreenState extends State { } else if (state.state == BaseState.success) { return _buildChatList(context, state); } else if (state.state == BaseState.error) { - return Padding( - padding: const EdgeInsets.all(16), - child: Center(child: Text(state.errorMessage ?? '')), + return Scaffold( + body: DataErrorWidget(onReload: () => context.read().getChatRooms()), ); } else { return Scaffold( diff --git a/lib/features/chat/presentation/widgets/chat_entry_widget.dart b/lib/features/chat/presentation/widgets/chat_entry_widget.dart index 0fe691c5..61e6ea17 100644 --- a/lib/features/chat/presentation/widgets/chat_entry_widget.dart +++ b/lib/features/chat/presentation/widgets/chat_entry_widget.dart @@ -20,7 +20,6 @@ class ChatEntryWidget extends StatefulWidget { } class _ChatEntryWidgetState extends State { - @override Widget build(BuildContext context) { return InkWell( @@ -61,11 +60,14 @@ class _ChatEntryWidgetState extends State { ), SizedBox(height: 8), if (widget.chatRoom.lastMessage != null) - Text( - widget.chatRoom.lastMessage!.data.toString().trim(), - maxLines: 2, - overflow: TextOverflow.ellipsis, - style: CustomTextStyles.bodyMediumBluegray800, + SizedBox( + width: MediaQuery.sizeOf(context).width * 0.5, + child: Text( + widget.chatRoom.lastMessage!.data.toString().trim(), + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: CustomTextStyles.bodyMediumBluegray800, + ), ), ], ), diff --git a/lib/features/complaining/models/complain_model.dart b/lib/features/complaining/models/complain_model.dart new file mode 100644 index 00000000..aa7be75f --- /dev/null +++ b/lib/features/complaining/models/complain_model.dart @@ -0,0 +1,15 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'complain_model.freezed.dart'; +part 'complain_model.g.dart'; + +@freezed +class ComplainModel with _$ComplainModel { + + const factory ComplainModel({ + @Default(null) String? description, + @Default([]) List tags, + }) = _ComplainModel; + + factory ComplainModel.fromJson(Map json) => _$ComplainModelFromJson(json); +} diff --git a/lib/features/complaining/models/complain_model.freezed.dart b/lib/features/complaining/models/complain_model.freezed.dart new file mode 100644 index 00000000..464a38e4 --- /dev/null +++ b/lib/features/complaining/models/complain_model.freezed.dart @@ -0,0 +1,193 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'complain_model.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +ComplainModel _$ComplainModelFromJson(Map json) { + return _ComplainModel.fromJson(json); +} + +/// @nodoc +mixin _$ComplainModel { + String? get description => throw _privateConstructorUsedError; + List get tags => throw _privateConstructorUsedError; + + /// Serializes this ComplainModel to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of ComplainModel + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ComplainModelCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ComplainModelCopyWith<$Res> { + factory $ComplainModelCopyWith( + ComplainModel value, $Res Function(ComplainModel) then) = + _$ComplainModelCopyWithImpl<$Res, ComplainModel>; + @useResult + $Res call({String? description, List tags}); +} + +/// @nodoc +class _$ComplainModelCopyWithImpl<$Res, $Val extends ComplainModel> + implements $ComplainModelCopyWith<$Res> { + _$ComplainModelCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ComplainModel + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? description = freezed, + Object? tags = null, + }) { + return _then(_value.copyWith( + description: freezed == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String?, + tags: null == tags + ? _value.tags + : tags // ignore: cast_nullable_to_non_nullable + as List, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$ComplainModelImplCopyWith<$Res> + implements $ComplainModelCopyWith<$Res> { + factory _$$ComplainModelImplCopyWith( + _$ComplainModelImpl value, $Res Function(_$ComplainModelImpl) then) = + __$$ComplainModelImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String? description, List tags}); +} + +/// @nodoc +class __$$ComplainModelImplCopyWithImpl<$Res> + extends _$ComplainModelCopyWithImpl<$Res, _$ComplainModelImpl> + implements _$$ComplainModelImplCopyWith<$Res> { + __$$ComplainModelImplCopyWithImpl( + _$ComplainModelImpl _value, $Res Function(_$ComplainModelImpl) _then) + : super(_value, _then); + + /// Create a copy of ComplainModel + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? description = freezed, + Object? tags = null, + }) { + return _then(_$ComplainModelImpl( + description: freezed == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String?, + tags: null == tags + ? _value._tags + : tags // ignore: cast_nullable_to_non_nullable + as List, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$ComplainModelImpl implements _ComplainModel { + const _$ComplainModelImpl( + {this.description = null, final List tags = const []}) + : _tags = tags; + + factory _$ComplainModelImpl.fromJson(Map json) => + _$$ComplainModelImplFromJson(json); + + @override + @JsonKey() + final String? description; + final List _tags; + @override + @JsonKey() + List get tags { + if (_tags is EqualUnmodifiableListView) return _tags; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_tags); + } + + @override + String toString() { + return 'ComplainModel(description: $description, tags: $tags)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ComplainModelImpl && + (identical(other.description, description) || + other.description == description) && + const DeepCollectionEquality().equals(other._tags, _tags)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, description, const DeepCollectionEquality().hash(_tags)); + + /// Create a copy of ComplainModel + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ComplainModelImplCopyWith<_$ComplainModelImpl> get copyWith => + __$$ComplainModelImplCopyWithImpl<_$ComplainModelImpl>(this, _$identity); + + @override + Map toJson() { + return _$$ComplainModelImplToJson( + this, + ); + } +} + +abstract class _ComplainModel implements ComplainModel { + const factory _ComplainModel( + {final String? description, + final List tags}) = _$ComplainModelImpl; + + factory _ComplainModel.fromJson(Map json) = + _$ComplainModelImpl.fromJson; + + @override + String? get description; + @override + List get tags; + + /// Create a copy of ComplainModel + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ComplainModelImplCopyWith<_$ComplainModelImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/features/complaining/models/complain_model.g.dart b/lib/features/complaining/models/complain_model.g.dart new file mode 100644 index 00000000..d9d3226f --- /dev/null +++ b/lib/features/complaining/models/complain_model.g.dart @@ -0,0 +1,21 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'complain_model.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$ComplainModelImpl _$$ComplainModelImplFromJson(Map json) => + _$ComplainModelImpl( + description: json['description'] as String? ?? null, + tags: + (json['tags'] as List?)?.map((e) => e as String).toList() ?? + const [], + ); + +Map _$$ComplainModelImplToJson(_$ComplainModelImpl instance) => + { + 'description': instance.description, + 'tags': instance.tags, + }; diff --git a/lib/features/complaining/presentation/complaining_bottom_sheet.dart b/lib/features/complaining/presentation/complaining_bottom_sheet.dart new file mode 100644 index 00000000..68770acc --- /dev/null +++ b/lib/features/complaining/presentation/complaining_bottom_sheet.dart @@ -0,0 +1,590 @@ +import 'package:eatvio_app/core/app_router.dart'; +import 'package:eatvio_app/core/enums/complained_content.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'; +import 'package:eatvio_app/core/widgets/bottom_sheet_handler_bar.dart'; +import 'package:eatvio_app/core/widgets/custom_outlined_button.dart'; +import 'package:eatvio_app/core/widgets/custom_text_form_field.dart'; +import 'package:eatvio_app/core/widgets/loader.dart'; +import 'package:eatvio_app/features/complaining/presentation/cubit/complaining_cubit.dart'; +import 'package:eatvio_app/features/complaining/presentation/cubit/complaining_state.dart'; +import 'package:eatvio_app/generated/l10n.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:go_router/go_router.dart'; +import 'package:collection/collection.dart'; + +class ComplainingBottomSheet extends StatefulWidget { + const ComplainingBottomSheet({super.key, required this.complainedContentType, this.slug, this.id}); + final ComplainedContent complainedContentType; + final String? slug; + final int? id; + @override + State createState() => _ComplainingBottomSheetState(); +} + +class _ComplainingBottomSheetState extends State { + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => sl()..setContentTypeAndIdentifier(contentType: widget.complainedContentType, slug: widget.slug, id: widget.id), + child: Container( + padding: EdgeInsets.only(top: 8, bottom: 24, left: 16, right: 16), + decoration: BoxDecoration( + borderRadius: BorderRadius.vertical(top: Radius.circular(12)), + ), + child: BlocBuilder( + builder: (context, state) { + final category = state.category; + if (state.wasSentSuccessfully == true) { + return _ComplaintSentWidget(); + } else if (state.wasSentSuccessfully == false) { + return _ComplaintFailedWidget(); + } + if (category != null) { + return switch (category) { + ComplainType.spam => _SpamWidget(), + ComplainType.lawViolation => _LawViolationWidget(), + ComplainType.deception => _DeceptionWidget(), + ComplainType.violenceDndHostility => _ViolenceAndHostility(), + ComplainType.explicitImage => _ExplicitImageWidget(), + }; + } + return _CategoryWidget(); + }, + ), + ), + ); + } +} + +class _CategoryWidget extends StatelessWidget { + const _CategoryWidget(); + final categories = ComplainType.values; + @override + Widget build(BuildContext context) { + final s = S.of(context); + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + BottomSheetHandlerBar(), + SizedBox(height: 20), + Text( + s.complaint, + style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black), + ), + SizedBox(height: 8), + Text( + s.complaint_question, + textAlign: TextAlign.center, + style: TextStyles.textMMedium.copyWith(color: appTheme.black), + ), + SizedBox(height: 24), + ...List.generate( + categories.length, + (index) => Column( + children: [ + _CategoryButton( + category: categories[index], + onTap: () { + context.read().setCategory(categories[index]); + }, + ), + if (index != categories.length - 1) SizedBox(height: 20), + ], + ), + ), + SizedBox(height: 24), + ], + ); + } +} + +class _SpamWidget extends StatelessWidget { + const _SpamWidget(); + + @override + Widget build(BuildContext context) { + final s = S.of(context); + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + BottomSheetHandlerBar(), + SizedBox(height: 20), + Text( + s.spam, + style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black), + ), + SizedBox(height: 8), + SizedBox( + width: MediaQuery.sizeOf(context).width * 0.7, + child: Text( + s.spam_description, + textAlign: TextAlign.center, + style: TextStyles.textMMedium.copyWith(color: appTheme.black), + ), + ), + SizedBox(height: 24), + _ComplainButton(), + ], + ); + } +} + +class _ExplicitImageWidget extends StatelessWidget { + const _ExplicitImageWidget(); + + @override + Widget build(BuildContext context) { + final s = S.of(context); + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + BottomSheetHandlerBar(), + SizedBox(height: 20), + Text( + s.explicitImages, + style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black), + ), + SizedBox(height: 8), + SizedBox( + width: MediaQuery.sizeOf(context).width * 0.7, + child: Text( + s.explicitImagesDescription, + textAlign: TextAlign.center, + style: TextStyles.textMMedium.copyWith(color: appTheme.black), + ), + ), + SizedBox(height: 24), + _ComplainButton(), + ], + ); + } +} + +class _LawViolationWidget extends StatefulWidget { + const _LawViolationWidget(); + + @override + State<_LawViolationWidget> createState() => _LawViolationWidgetState(); +} + +class _LawViolationWidgetState extends State<_LawViolationWidget> { + final List<(bool, String)> tags = []; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + final s = S.of(context); + if (tags.isEmpty) { + tags.addAll([ + (false, s.alcohol), + (false, s.drugs), + (false, s.nicotine), + (false, s.weapon), + (false, s.fake_documents), + (false, s.prostitution), + (false, s.other), + ]); + } + } + + @override + Widget build(BuildContext context) { + final s = S.of(context); + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + BottomSheetHandlerBar(), + SizedBox(height: 20), + Text( + s.lawViolation, + style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black), + ), + SizedBox(height: 8), + SizedBox( + width: MediaQuery.sizeOf(context).width * 0.7, + child: Text( + s.what_do_you_want_to_complaint_about, + textAlign: TextAlign.center, + style: TextStyles.textMMedium.copyWith(color: appTheme.black), + ), + ), + SizedBox(height: 24), + ...tags.mapIndexed((index, element) { + return Row( + children: [ + Checkbox( + activeColor: appTheme.blueA400, + side: BorderSide(width: 2, color: tags[index].$1 ? appTheme.blueA400 : appTheme.blueGray), + value: element.$1, + onChanged: (isChecked) { + setState(() { + if (isChecked == null) return; + if (index == tags.length - 1 && !isChecked) { + context.read().toggleDescription(); + } + tags[index] = (isChecked, element.$2); + final activeTags = tags.where((elem) => elem.$1).map((e) => e.$2).toList(); + context.read().setTags(activeTags); + }); + }, + ), + Text( + element.$2, + style: TextStyles.textLSemibold.copyWith(color: appTheme.black), + ), + ], + ); + }).toList(), + if (tags.last.$1) ...[ + SizedBox(height: 12), + CustomTextFormField( + hintText: s.type_here_your_complaint, + onChanged: (value) { + context.read().setDescription(value); + }, + ), + ], + SizedBox(height: 20), + _ComplainButton(), + ], + ); + } +} + +class _ViolenceAndHostility extends StatefulWidget { + const _ViolenceAndHostility(); + + @override + State<_ViolenceAndHostility> createState() => _ViolenceAndHostilityState(); +} + +class _ViolenceAndHostilityState extends State<_ViolenceAndHostility> { + final List<(bool, String)> tags = []; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + final s = S.of(context); + if (tags.isEmpty) { + tags.addAll([ + (false, s.violence_against_people_or_animal), + (false, s.hatred), + (false, s.suicide), + (false, s.hatred_description), + (false, s.extremism), + (false, s.calls_for_bullying), + ]); + } + } + + @override + Widget build(BuildContext context) { + final s = S.of(context); + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + BottomSheetHandlerBar(), + SizedBox(height: 20), + Text( + s.lawViolation, + style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black), + ), + SizedBox(height: 8), + SizedBox( + width: MediaQuery.sizeOf(context).width * 0.7, + child: Text( + s.what_do_you_want_to_complaint_about, + textAlign: TextAlign.center, + style: TextStyles.textMMedium.copyWith(color: appTheme.black), + ), + ), + SizedBox(height: 24), + ...tags.mapIndexed((index, element) { + return Row( + children: [ + Checkbox( + activeColor: appTheme.blueA400, + side: BorderSide(width: 2, color: tags[index].$1 ? appTheme.blueA400 : appTheme.blueGray), + value: element.$1, + onChanged: (isChecked) { + setState(() { + if (isChecked == null) return; + tags[index] = (isChecked, element.$2); + final activeTags = tags.where((elem) => elem.$1).map((e) => e.$2).toList(); + context.read().setTags(activeTags); + }); + }, + ), + Text( + element.$2, + style: TextStyles.textLSemibold.copyWith(color: appTheme.black), + ), + ], + ); + }).toList(), + SizedBox(height: 20), + _ComplainButton(), + ], + ); + } +} + +class _DeceptionWidget extends StatefulWidget { + const _DeceptionWidget(); + + @override + State<_DeceptionWidget> createState() => _DeceptionWidgetState(); +} + +class _DeceptionWidgetState extends State<_DeceptionWidget> { + final List<(bool, String)> tags = []; + final List descriptions = []; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + final s = S.of(context); + if (tags.isEmpty && descriptions.isEmpty) { + tags.addAll([ + (false, s.misrepresentation), + (false, s.fraud), + ]); + + descriptions.addAll([ + s.misrepresentation_description, + s.fraud_description, + ]); + } + } + + @override + Widget build(BuildContext context) { + final s = S.of(context); + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + BottomSheetHandlerBar(), + SizedBox(height: 20), + Text( + s.lawViolation, + style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black), + ), + SizedBox(height: 8), + SizedBox( + width: MediaQuery.sizeOf(context).width * 0.7, + child: Text( + s.what_do_you_want_to_complaint_about, + textAlign: TextAlign.center, + style: TextStyles.textMMedium.copyWith(color: appTheme.black), + ), + ), + SizedBox(height: 24), + ...tags.mapIndexed((index, element) { + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Checkbox( + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + activeColor: appTheme.blueA400, + side: BorderSide(width: 2, color: tags[index].$1 ? appTheme.blueA400 : appTheme.blueGray), + value: element.$1, + onChanged: (isChecked) { + setState(() { + if (isChecked == null) return; + tags[index] = (isChecked, element.$2); + final activeTags = tags.where((elem) => elem.$1).map((e) => e.$2).toList(); + context.read().setTags(activeTags); + }); + }, + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 8), + Text( + element.$2, + style: TextStyles.textLSemibold.copyWith(color: appTheme.black), + ), + SizedBox( + width: MediaQuery.sizeOf(context).width * 0.7, + child: Text( + descriptions[index], + style: TextStyles.textSSemibold.copyWith(color: appTheme.lightGray600), + ), + ), + ], + ), + ], + ); + }).toList(), + SizedBox(height: 20), + _ComplainButton(), + ], + ); + } +} + +class _ComplaintSentWidget extends StatelessWidget { + const _ComplaintSentWidget(); + + @override + Widget build(BuildContext context) { + final s = S.of(context); + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + BottomSheetHandlerBar(), + SizedBox(height: 20), + Text( + s.complaint_sent, + style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black), + ), + SizedBox(height: 8), + SizedBox( + width: MediaQuery.sizeOf(context).width * 0.7, + child: Text( + s.complaint_sent_description, + textAlign: TextAlign.center, + style: TextStyles.textMMedium.copyWith(color: appTheme.black), + ), + ), + SizedBox(height: 24), + CustomOutlinedButton( + text: s.ok, + height: 52, + buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700), + buttonStyle: ButtonStyle( + side: WidgetStatePropertyAll(BorderSide.none), + backgroundColor: WidgetStatePropertyAll(appTheme.blueA400), + ), + onPressed: () { + AppRouter.navigatorKey.currentContext?.pop(); + }, + ), + ], + ); + } +} + +class _ComplaintFailedWidget extends StatelessWidget { + const _ComplaintFailedWidget(); + + @override + Widget build(BuildContext context) { + final s = S.of(context); + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + BottomSheetHandlerBar(), + SizedBox(height: 20), + Text( + s.could_not_send_complain, + style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black), + ), + SizedBox(height: 8), + SizedBox( + width: MediaQuery.sizeOf(context).width * 0.7, + child: Text( + s.please_try_again_later, + textAlign: TextAlign.center, + style: TextStyles.textMMedium.copyWith(color: appTheme.black), + ), + ), + SizedBox(height: 24), + CustomOutlinedButton( + text: s.ok, + height: 52, + buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700), + buttonStyle: ButtonStyle( + side: WidgetStatePropertyAll(BorderSide.none), + backgroundColor: WidgetStatePropertyAll(appTheme.blueA400), + ), + onPressed: () { + AppRouter.navigatorKey.currentContext?.pop(); + }, + ), + ], + ); + } +} + +class _ComplainButton extends StatelessWidget { + const _ComplainButton(); + + @override + Widget build(BuildContext context) { + final s = S.of(context); + return BlocBuilder( + builder: (context, state) { + if (state.isLoading) { + return Loader(); + } + return CustomOutlinedButton( + text: s.send_complain, + height: 52, + buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700), + buttonStyle: ButtonStyle( + side: WidgetStatePropertyAll(BorderSide.none), + backgroundColor: WidgetStatePropertyAll(appTheme.blueA400), + ), + onPressed: () async { + await context.read().complain(); + }, + ); + }, + ); + } +} + +class _CategoryButton extends StatelessWidget { + const _CategoryButton({required this.category, required this.onTap}); + final ComplainType category; + final void Function() onTap; + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: onTap, + child: Ink( + child: Row( + children: [ + Text( + category.getTranslatedText(), + style: TextStyles.textLSemibold.copyWith(color: appTheme.black), + ), + Spacer(), + SvgPicture.asset('assets/icons/right_arrow.svg'), + ], + ), + ), + ); + } +} + +enum ComplainType { + spam, + lawViolation, + deception, + violenceDndHostility, + explicitImage; + + String getTranslatedText() => switch (this) { + ComplainType.spam => S.current.spam, + ComplainType.lawViolation => S.current.lawViolation, + ComplainType.deception => S.current.deception, + ComplainType.violenceDndHostility => S.current.violenceAndHostility, + ComplainType.explicitImage => S.current.explicitImage, + }; + + String getValue() => switch (this) { + ComplainType.spam => 'spam', + ComplainType.lawViolation => 'legalViolation', + ComplainType.deception => 'fraud', + ComplainType.violenceDndHostility => 'violenceAndHatred', + ComplainType.explicitImage => 'explicitImage', + }; +} diff --git a/lib/features/complaining/presentation/complaining_screen.dart b/lib/features/complaining/presentation/complaining_screen.dart deleted file mode 100644 index 2c3e0fd3..00000000 --- a/lib/features/complaining/presentation/complaining_screen.dart +++ /dev/null @@ -1,10 +0,0 @@ -import 'package:flutter/material.dart'; - -class ComplainingScreen extends StatelessWidget { - const ComplainingScreen({super.key}); - - @override - Widget build(BuildContext context) { - return const Placeholder(); - } -} \ No newline at end of file diff --git a/lib/features/complaining/presentation/cubit/complaining_cubit.dart b/lib/features/complaining/presentation/cubit/complaining_cubit.dart new file mode 100644 index 00000000..61843c04 --- /dev/null +++ b/lib/features/complaining/presentation/cubit/complaining_cubit.dart @@ -0,0 +1,79 @@ +import 'dart:developer'; + +import 'package:eatvio_app/core/enums/complained_content.dart'; +import 'package:eatvio_app/features/complaining/presentation/complaining_bottom_sheet.dart'; +import 'package:eatvio_app/features/complaining/presentation/cubit/complaining_state.dart'; +import 'package:eatvio_app/features/complaining/repositories/complaining_repository.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class ComplainingCubit extends Cubit { + final ComplainingRepository repository; + ComplainingCubit({required this.repository}) : super(ComplainingState()); + + void setContentTypeAndIdentifier({required ComplainedContent contentType, int? id, String? slug}) { + emit(state.copyWith(contentType: contentType, contentId: id, contentSlug: slug)); + } + + void setCategory(ComplainType category) { + emit(state.copyWith(category: category)); + } + + void setTags(List tags) { + emit(state.copyWith.complainInfo(tags: tags)); + } + + void toggleDescription() { + if (state.complainInfo.description != null) { + emit(state.copyWith.complainInfo(description: null)); + } else { + emit(state.copyWith.complainInfo(description: '')); + } + } + + void setDescription(String description) { + emit(state.copyWith.complainInfo(description: description)); + } + + Future complain() async { + try { + final category = state.category; + final id = state.contentId; + final slug = state.contentSlug; + final contentType = state.contentType; + if (category != null && contentType != null) { + switch (contentType) { + case ComplainedContent.diet: + emit(state.copyWith(wasSentSuccessfully: true)); + return; + case ComplainedContent.plan: + if (slug != null) { + await repository.sendPlanComplain(slug: slug, category: category.getValue(), body: state.complainInfo); + emit(state.copyWith(wasSentSuccessfully: true)); + return; + } + case ComplainedContent.recipe: + if (slug != null) { + await repository.sendRecipeComplain(slug: slug, category: category.getValue(), body: state.complainInfo); + emit(state.copyWith(wasSentSuccessfully: true)); + return; + } + case ComplainedContent.profile: + if (slug != null) { + await repository.sendProfileComplain(slug: slug, category: category.getValue(), body: state.complainInfo); + emit(state.copyWith(wasSentSuccessfully: true)); + return; + } + case ComplainedContent.posts: + if (id != null) { + await repository.sendBlogPostsComplain(id: id, category: category.getValue(), body: state.complainInfo); + emit(state.copyWith(wasSentSuccessfully: true)); + return; + } + } + } + } catch (e, st) { + log(e.toString(), stackTrace: st); + } + emit(state.copyWith(wasSentSuccessfully: false)); + } +} diff --git a/lib/features/complaining/presentation/cubit/complaining_state.dart b/lib/features/complaining/presentation/cubit/complaining_state.dart new file mode 100644 index 00000000..1f3e69a2 --- /dev/null +++ b/lib/features/complaining/presentation/cubit/complaining_state.dart @@ -0,0 +1,20 @@ +import 'package:eatvio_app/core/enums/complained_content.dart'; +import 'package:eatvio_app/features/complaining/models/complain_model.dart'; +import 'package:eatvio_app/features/complaining/presentation/complaining_bottom_sheet.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'complaining_state.freezed.dart'; + +@freezed +class ComplainingState with _$ComplainingState { + + factory ComplainingState({ + @Default(false) bool isLoading, + @Default(null) bool? wasSentSuccessfully, + @Default(null) ComplainType? category, + @Default(null) ComplainedContent? contentType, + @Default(null) int? contentId, + @Default(null) String? contentSlug, + @Default(ComplainModel()) ComplainModel complainInfo, + }) = _ComplainingState; +} \ No newline at end of file diff --git a/lib/features/complaining/presentation/cubit/complaining_state.freezed.dart b/lib/features/complaining/presentation/cubit/complaining_state.freezed.dart new file mode 100644 index 00000000..d2719d8e --- /dev/null +++ b/lib/features/complaining/presentation/cubit/complaining_state.freezed.dart @@ -0,0 +1,298 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'complaining_state.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +/// @nodoc +mixin _$ComplainingState { + bool get isLoading => throw _privateConstructorUsedError; + bool? get wasSentSuccessfully => throw _privateConstructorUsedError; + ComplainType? get category => throw _privateConstructorUsedError; + ComplainedContent? get contentType => throw _privateConstructorUsedError; + int? get contentId => throw _privateConstructorUsedError; + String? get contentSlug => throw _privateConstructorUsedError; + ComplainModel get complainInfo => throw _privateConstructorUsedError; + + /// Create a copy of ComplainingState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ComplainingStateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ComplainingStateCopyWith<$Res> { + factory $ComplainingStateCopyWith( + ComplainingState value, $Res Function(ComplainingState) then) = + _$ComplainingStateCopyWithImpl<$Res, ComplainingState>; + @useResult + $Res call( + {bool isLoading, + bool? wasSentSuccessfully, + ComplainType? category, + ComplainedContent? contentType, + int? contentId, + String? contentSlug, + ComplainModel complainInfo}); + + $ComplainModelCopyWith<$Res> get complainInfo; +} + +/// @nodoc +class _$ComplainingStateCopyWithImpl<$Res, $Val extends ComplainingState> + implements $ComplainingStateCopyWith<$Res> { + _$ComplainingStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ComplainingState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? isLoading = null, + Object? wasSentSuccessfully = freezed, + Object? category = freezed, + Object? contentType = freezed, + Object? contentId = freezed, + Object? contentSlug = freezed, + Object? complainInfo = null, + }) { + return _then(_value.copyWith( + isLoading: null == isLoading + ? _value.isLoading + : isLoading // ignore: cast_nullable_to_non_nullable + as bool, + wasSentSuccessfully: freezed == wasSentSuccessfully + ? _value.wasSentSuccessfully + : wasSentSuccessfully // ignore: cast_nullable_to_non_nullable + as bool?, + category: freezed == category + ? _value.category + : category // ignore: cast_nullable_to_non_nullable + as ComplainType?, + contentType: freezed == contentType + ? _value.contentType + : contentType // ignore: cast_nullable_to_non_nullable + as ComplainedContent?, + contentId: freezed == contentId + ? _value.contentId + : contentId // ignore: cast_nullable_to_non_nullable + as int?, + contentSlug: freezed == contentSlug + ? _value.contentSlug + : contentSlug // ignore: cast_nullable_to_non_nullable + as String?, + complainInfo: null == complainInfo + ? _value.complainInfo + : complainInfo // ignore: cast_nullable_to_non_nullable + as ComplainModel, + ) as $Val); + } + + /// Create a copy of ComplainingState + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $ComplainModelCopyWith<$Res> get complainInfo { + return $ComplainModelCopyWith<$Res>(_value.complainInfo, (value) { + return _then(_value.copyWith(complainInfo: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$ComplainingStateImplCopyWith<$Res> + implements $ComplainingStateCopyWith<$Res> { + factory _$$ComplainingStateImplCopyWith(_$ComplainingStateImpl value, + $Res Function(_$ComplainingStateImpl) then) = + __$$ComplainingStateImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {bool isLoading, + bool? wasSentSuccessfully, + ComplainType? category, + ComplainedContent? contentType, + int? contentId, + String? contentSlug, + ComplainModel complainInfo}); + + @override + $ComplainModelCopyWith<$Res> get complainInfo; +} + +/// @nodoc +class __$$ComplainingStateImplCopyWithImpl<$Res> + extends _$ComplainingStateCopyWithImpl<$Res, _$ComplainingStateImpl> + implements _$$ComplainingStateImplCopyWith<$Res> { + __$$ComplainingStateImplCopyWithImpl(_$ComplainingStateImpl _value, + $Res Function(_$ComplainingStateImpl) _then) + : super(_value, _then); + + /// Create a copy of ComplainingState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? isLoading = null, + Object? wasSentSuccessfully = freezed, + Object? category = freezed, + Object? contentType = freezed, + Object? contentId = freezed, + Object? contentSlug = freezed, + Object? complainInfo = null, + }) { + return _then(_$ComplainingStateImpl( + isLoading: null == isLoading + ? _value.isLoading + : isLoading // ignore: cast_nullable_to_non_nullable + as bool, + wasSentSuccessfully: freezed == wasSentSuccessfully + ? _value.wasSentSuccessfully + : wasSentSuccessfully // ignore: cast_nullable_to_non_nullable + as bool?, + category: freezed == category + ? _value.category + : category // ignore: cast_nullable_to_non_nullable + as ComplainType?, + contentType: freezed == contentType + ? _value.contentType + : contentType // ignore: cast_nullable_to_non_nullable + as ComplainedContent?, + contentId: freezed == contentId + ? _value.contentId + : contentId // ignore: cast_nullable_to_non_nullable + as int?, + contentSlug: freezed == contentSlug + ? _value.contentSlug + : contentSlug // ignore: cast_nullable_to_non_nullable + as String?, + complainInfo: null == complainInfo + ? _value.complainInfo + : complainInfo // ignore: cast_nullable_to_non_nullable + as ComplainModel, + )); + } +} + +/// @nodoc + +class _$ComplainingStateImpl implements _ComplainingState { + _$ComplainingStateImpl( + {this.isLoading = false, + this.wasSentSuccessfully = null, + this.category = null, + this.contentType = null, + this.contentId = null, + this.contentSlug = null, + this.complainInfo = const ComplainModel()}); + + @override + @JsonKey() + final bool isLoading; + @override + @JsonKey() + final bool? wasSentSuccessfully; + @override + @JsonKey() + final ComplainType? category; + @override + @JsonKey() + final ComplainedContent? contentType; + @override + @JsonKey() + final int? contentId; + @override + @JsonKey() + final String? contentSlug; + @override + @JsonKey() + final ComplainModel complainInfo; + + @override + String toString() { + return 'ComplainingState(isLoading: $isLoading, wasSentSuccessfully: $wasSentSuccessfully, category: $category, contentType: $contentType, contentId: $contentId, contentSlug: $contentSlug, complainInfo: $complainInfo)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ComplainingStateImpl && + (identical(other.isLoading, isLoading) || + other.isLoading == isLoading) && + (identical(other.wasSentSuccessfully, wasSentSuccessfully) || + other.wasSentSuccessfully == wasSentSuccessfully) && + (identical(other.category, category) || + other.category == category) && + (identical(other.contentType, contentType) || + other.contentType == contentType) && + (identical(other.contentId, contentId) || + other.contentId == contentId) && + (identical(other.contentSlug, contentSlug) || + other.contentSlug == contentSlug) && + (identical(other.complainInfo, complainInfo) || + other.complainInfo == complainInfo)); + } + + @override + int get hashCode => Object.hash(runtimeType, isLoading, wasSentSuccessfully, + category, contentType, contentId, contentSlug, complainInfo); + + /// Create a copy of ComplainingState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ComplainingStateImplCopyWith<_$ComplainingStateImpl> get copyWith => + __$$ComplainingStateImplCopyWithImpl<_$ComplainingStateImpl>( + this, _$identity); +} + +abstract class _ComplainingState implements ComplainingState { + factory _ComplainingState( + {final bool isLoading, + final bool? wasSentSuccessfully, + final ComplainType? category, + final ComplainedContent? contentType, + final int? contentId, + final String? contentSlug, + final ComplainModel complainInfo}) = _$ComplainingStateImpl; + + @override + bool get isLoading; + @override + bool? get wasSentSuccessfully; + @override + ComplainType? get category; + @override + ComplainedContent? get contentType; + @override + int? get contentId; + @override + String? get contentSlug; + @override + ComplainModel get complainInfo; + + /// Create a copy of ComplainingState + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ComplainingStateImplCopyWith<_$ComplainingStateImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/features/complaining/repositories/complaining_repository.dart b/lib/features/complaining/repositories/complaining_repository.dart new file mode 100644 index 00000000..af3a10b6 --- /dev/null +++ b/lib/features/complaining/repositories/complaining_repository.dart @@ -0,0 +1,55 @@ +import 'package:eatvio_app/features/complaining/models/complain_model.dart'; +import 'package:eatvio_app/features/complaining/services/complaining_service.dart'; + +class ComplainingRepository { + final ComplainingService service; + ComplainingRepository({required this.service}); + + Future sendPlanComplain({ + required String slug, + required String category, + required ComplainModel body, + }) async { + try { + service.sendPlanComplain(slug: slug, category: category, body: body); + } catch (e) { + rethrow; + } + } + + Future sendRecipeComplain({ + required String slug, + required String category, + required ComplainModel body, + }) async { + try { + service.sendRecipeComplain(slug: slug, category: category, body: body); + } catch (e) { + rethrow; + } + } + + Future sendBlogPostsComplain({ + required int id, + required String category, + required ComplainModel body, + }) async { + try { + service.sendBlogPostsComplain(id: id, category: category, body: body); + } catch (e) { + rethrow; + } + } + + Future sendProfileComplain({ + required String slug, + required String category, + required ComplainModel body, + }) async { + try { + service.sendProfileComplain(slug: slug, category: category, body: body); + } catch (e) { + rethrow; + } + } +} diff --git a/lib/features/complaining/services/complaining_service.dart b/lib/features/complaining/services/complaining_service.dart new file mode 100644 index 00000000..cf214149 --- /dev/null +++ b/lib/features/complaining/services/complaining_service.dart @@ -0,0 +1,39 @@ +import 'package:dio/dio.dart'; +import 'package:eatvio_app/features/complaining/models/complain_model.dart'; +import 'package:retrofit/retrofit.dart'; + +part 'complaining_service.g.dart'; + +@RestApi() +abstract class ComplainingService { + factory ComplainingService(Dio dio, {String baseUrl}) = _ComplainingService; + + @POST('/api/plans/{slug}/complain/{category}') + Future sendPlanComplain({ + @Path() required String slug, + @Path() required String category, + @Body() required ComplainModel body, + }); + + @POST('/api/recipes/{slug}/complain/{category}') + Future sendRecipeComplain({ + @Path() required String slug, + @Path() required String category, + @Body() required ComplainModel body, + }); + + @POST('/api/blogPosts/{id}/complain/{category}') + Future sendBlogPostsComplain({ + @Path() required int id, + @Path() required String category, + @Body() required ComplainModel body, + }); + + @POST('/api/profile/{slug}/complain/{category}') + Future sendProfileComplain({ + @Path() required String slug, + @Path() required String category, + @Body() required ComplainModel body, + }); +} + diff --git a/lib/features/complaining/services/complaining_service.g.dart b/lib/features/complaining/services/complaining_service.g.dart new file mode 100644 index 00000000..8b3d6595 --- /dev/null +++ b/lib/features/complaining/services/complaining_service.g.dart @@ -0,0 +1,138 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'complaining_service.dart'; + +// ************************************************************************** +// RetrofitGenerator +// ************************************************************************** + +// ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers,unused_element,unnecessary_string_interpolations + +class _ComplainingService implements ComplainingService { + _ComplainingService(this._dio, {this.baseUrl, this.errorLogger}); + + final Dio _dio; + + String? baseUrl; + + final ParseErrorLogger? errorLogger; + + @override + Future sendPlanComplain({ + required String slug, + required String category, + required ComplainModel body, + }) async { + final _extra = {}; + final queryParameters = {}; + final _headers = {}; + final _data = body; + final _options = _setStreamType( + Options(method: 'POST', headers: _headers, extra: _extra) + .compose( + _dio.options, + '/api/plans/${slug}/complain/${category}', + queryParameters: queryParameters, + data: _data, + ) + .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)), + ); + await _dio.fetch(_options); + } + + @override + Future sendRecipeComplain({ + required String slug, + required String category, + required ComplainModel body, + }) async { + final _extra = {}; + final queryParameters = {}; + final _headers = {}; + final _data = body; + final _options = _setStreamType( + Options(method: 'POST', headers: _headers, extra: _extra) + .compose( + _dio.options, + '/api/recipes/${slug}/complain/${category}', + queryParameters: queryParameters, + data: _data, + ) + .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)), + ); + await _dio.fetch(_options); + } + + @override + Future sendBlogPostsComplain({ + required int id, + required String category, + required ComplainModel body, + }) async { + final _extra = {}; + final queryParameters = {}; + final _headers = {}; + final _data = body; + final _options = _setStreamType( + Options(method: 'POST', headers: _headers, extra: _extra) + .compose( + _dio.options, + '/api/blogPosts/${id}/complain/${category}', + queryParameters: queryParameters, + data: _data, + ) + .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)), + ); + await _dio.fetch(_options); + } + + @override + Future sendProfileComplain({ + required String slug, + required String category, + required ComplainModel body, + }) async { + final _extra = {}; + final queryParameters = {}; + final _headers = {}; + final _data = body; + final _options = _setStreamType( + Options(method: 'POST', headers: _headers, extra: _extra) + .compose( + _dio.options, + '/api/profile/${slug}/complain/${category}', + queryParameters: queryParameters, + data: _data, + ) + .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)), + ); + await _dio.fetch(_options); + } + + RequestOptions _setStreamType(RequestOptions requestOptions) { + if (T != dynamic && + !(requestOptions.responseType == ResponseType.bytes || + requestOptions.responseType == ResponseType.stream)) { + if (T == String) { + requestOptions.responseType = ResponseType.plain; + } else { + requestOptions.responseType = ResponseType.json; + } + } + return requestOptions; + } + + String _combineBaseUrls(String dioBaseUrl, String? baseUrl) { + if (baseUrl == null || baseUrl.trim().isEmpty) { + return dioBaseUrl; + } + + final url = Uri.parse(baseUrl); + + if (url.isAbsolute) { + return url.toString(); + } + + return Uri.parse(dioBaseUrl).resolveUri(url).toString(); + } +} diff --git a/lib/features/consultant_rating/models/rating_model.dart b/lib/features/consultant_rating/models/rating_model.dart new file mode 100644 index 00000000..fbd4fe74 --- /dev/null +++ b/lib/features/consultant_rating/models/rating_model.dart @@ -0,0 +1,16 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'rating_model.freezed.dart'; +part 'rating_model.g.dart'; + +@freezed +class RatingModel with _$RatingModel { + + factory RatingModel({ + required DateTime date, + required String title, + required int amount, + }) = _RatingModel; + + factory RatingModel.fromJson(Map json) => _$RatingModelFromJson(json); +} \ No newline at end of file diff --git a/lib/features/consultant_rating/models/rating_model.freezed.dart b/lib/features/consultant_rating/models/rating_model.freezed.dart new file mode 100644 index 00000000..74e1f0c7 --- /dev/null +++ b/lib/features/consultant_rating/models/rating_model.freezed.dart @@ -0,0 +1,200 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'rating_model.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +RatingModel _$RatingModelFromJson(Map json) { + return _RatingModel.fromJson(json); +} + +/// @nodoc +mixin _$RatingModel { + DateTime get date => throw _privateConstructorUsedError; + String get title => throw _privateConstructorUsedError; + int get amount => throw _privateConstructorUsedError; + + /// Serializes this RatingModel to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of RatingModel + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $RatingModelCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $RatingModelCopyWith<$Res> { + factory $RatingModelCopyWith( + RatingModel value, $Res Function(RatingModel) then) = + _$RatingModelCopyWithImpl<$Res, RatingModel>; + @useResult + $Res call({DateTime date, String title, int amount}); +} + +/// @nodoc +class _$RatingModelCopyWithImpl<$Res, $Val extends RatingModel> + implements $RatingModelCopyWith<$Res> { + _$RatingModelCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of RatingModel + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? date = null, + Object? title = null, + Object? amount = null, + }) { + return _then(_value.copyWith( + date: null == date + ? _value.date + : date // ignore: cast_nullable_to_non_nullable + as DateTime, + title: null == title + ? _value.title + : title // ignore: cast_nullable_to_non_nullable + as String, + amount: null == amount + ? _value.amount + : amount // ignore: cast_nullable_to_non_nullable + as int, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$RatingModelImplCopyWith<$Res> + implements $RatingModelCopyWith<$Res> { + factory _$$RatingModelImplCopyWith( + _$RatingModelImpl value, $Res Function(_$RatingModelImpl) then) = + __$$RatingModelImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({DateTime date, String title, int amount}); +} + +/// @nodoc +class __$$RatingModelImplCopyWithImpl<$Res> + extends _$RatingModelCopyWithImpl<$Res, _$RatingModelImpl> + implements _$$RatingModelImplCopyWith<$Res> { + __$$RatingModelImplCopyWithImpl( + _$RatingModelImpl _value, $Res Function(_$RatingModelImpl) _then) + : super(_value, _then); + + /// Create a copy of RatingModel + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? date = null, + Object? title = null, + Object? amount = null, + }) { + return _then(_$RatingModelImpl( + date: null == date + ? _value.date + : date // ignore: cast_nullable_to_non_nullable + as DateTime, + title: null == title + ? _value.title + : title // ignore: cast_nullable_to_non_nullable + as String, + amount: null == amount + ? _value.amount + : amount // ignore: cast_nullable_to_non_nullable + as int, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$RatingModelImpl implements _RatingModel { + _$RatingModelImpl( + {required this.date, required this.title, required this.amount}); + + factory _$RatingModelImpl.fromJson(Map json) => + _$$RatingModelImplFromJson(json); + + @override + final DateTime date; + @override + final String title; + @override + final int amount; + + @override + String toString() { + return 'RatingModel(date: $date, title: $title, amount: $amount)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$RatingModelImpl && + (identical(other.date, date) || other.date == date) && + (identical(other.title, title) || other.title == title) && + (identical(other.amount, amount) || other.amount == amount)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, date, title, amount); + + /// Create a copy of RatingModel + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$RatingModelImplCopyWith<_$RatingModelImpl> get copyWith => + __$$RatingModelImplCopyWithImpl<_$RatingModelImpl>(this, _$identity); + + @override + Map toJson() { + return _$$RatingModelImplToJson( + this, + ); + } +} + +abstract class _RatingModel implements RatingModel { + factory _RatingModel( + {required final DateTime date, + required final String title, + required final int amount}) = _$RatingModelImpl; + + factory _RatingModel.fromJson(Map json) = + _$RatingModelImpl.fromJson; + + @override + DateTime get date; + @override + String get title; + @override + int get amount; + + /// Create a copy of RatingModel + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$RatingModelImplCopyWith<_$RatingModelImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/features/consultant_rating/models/rating_model.g.dart b/lib/features/consultant_rating/models/rating_model.g.dart new file mode 100644 index 00000000..f3458ac6 --- /dev/null +++ b/lib/features/consultant_rating/models/rating_model.g.dart @@ -0,0 +1,21 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'rating_model.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$RatingModelImpl _$$RatingModelImplFromJson(Map json) => + _$RatingModelImpl( + date: DateTime.parse(json['date'] as String), + title: json['title'] as String, + amount: (json['amount'] as num).toInt(), + ); + +Map _$$RatingModelImplToJson(_$RatingModelImpl instance) => + { + 'date': instance.date.toIso8601String(), + 'title': instance.title, + 'amount': instance.amount, + }; diff --git a/lib/features/consultant_rating/presentation/consultant_rating_screen.dart b/lib/features/consultant_rating/presentation/consultant_rating_screen.dart new file mode 100644 index 00000000..75434b3e --- /dev/null +++ b/lib/features/consultant_rating/presentation/consultant_rating_screen.dart @@ -0,0 +1,76 @@ +import 'package:eatvio_app/core/theme/custom_text_style.dart'; +import 'package:eatvio_app/core/theme/theme_helper.dart'; +import 'package:eatvio_app/core/widgets/custom_outlined_button.dart'; +import 'package:eatvio_app/features/consultant_rating/models/rating_model.dart'; +import 'package:eatvio_app/features/consultant_rating/presentation/widgets/rating_history_widget.dart'; +import 'package:eatvio_app/features/consultant_rating/presentation/widgets/rating_widget.dart'; +import 'package:eatvio_app/features/consultant_rating/presentation/widgets/statistic_widget.dart'; +import 'package:eatvio_app/generated/l10n.dart'; +import 'package:flutter/material.dart'; + +class ConsultantRatingScreen extends StatelessWidget { + static const name = 'consultantRatingScreen'; + static const path = '/consultant-rating-screen'; + const ConsultantRatingScreen({super.key}); + + @override + Widget build(BuildContext context) { + return const ConsultantRatingPageContent(); + } +} + +class ConsultantRatingPageContent extends StatelessWidget { + const ConsultantRatingPageContent({super.key}); + + @override + Widget build(BuildContext context) { + final s = S.of(context); + return Scaffold( + backgroundColor: appTheme.gray100, + appBar: AppBar( + centerTitle: true, + title: Text( + s.rating_and_statistics, + style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black), + ), + ), + body: SingleChildScrollView( + child: Column( + children: [ + SizedBox(height: 16), + RatingWidget( + rating: 1058, + ), + SizedBox(height: 16), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: CustomOutlinedButton( + text: s.copy_referral_link, + height: 52, + buttonTextStyle: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700), + buttonStyle: ButtonStyle( + side: WidgetStatePropertyAll(BorderSide.none), + backgroundColor: WidgetStatePropertyAll(appTheme.blueA400), + ), + onPressed: () {}, + ), + ), + SizedBox(height: 16), + StatisticWidget(), + SizedBox(height: 16), + RatingHistoryWidget( + ratingHistory: [ + RatingModel(date: DateTime.now(), title: ' djkasjdasjkdkjasjkdjkaskjdjklasjkld', amount: 201300), + RatingModel(date: DateTime.now(), title: 'djkasjdasjkdkjasjkdjkaskjdjklasjkld', amount: 89323), + RatingModel(date: DateTime.now(), title: 'djkasjdasjkdkjasjkdjkaskjdjklasjkld', amount: 20), + RatingModel(date: DateTime.now(), title: 'djkasjdasjkdkjasjkdjkaskjdjklasjkld', amount: 1212), + RatingModel(date: DateTime.now(), title: 'qweqweeq', amount: 323), + RatingModel(date: DateTime.now(), title: 'ee', amount: 1), + ], + ), + ], + ), + ), + ); + } +} diff --git a/lib/features/consultant_rating/presentation/widgets/period_filter_widget.dart b/lib/features/consultant_rating/presentation/widgets/period_filter_widget.dart new file mode 100644 index 00000000..e78c7b26 --- /dev/null +++ b/lib/features/consultant_rating/presentation/widgets/period_filter_widget.dart @@ -0,0 +1,67 @@ +import 'package:eatvio_app/core/theme/custom_text_style.dart'; +import 'package:eatvio_app/core/theme/theme_helper.dart'; +import 'package:eatvio_app/core/widgets/custom_drop_down_button.dart'; +import 'package:eatvio_app/generated/l10n.dart'; +import 'package:flutter/material.dart'; +import 'package:collection/collection.dart'; + +class PeriodFilterWidget extends StatefulWidget { + const PeriodFilterWidget({super.key}); + + @override + State createState() => _PeriodFilterWidgetState(); +} + +class _PeriodFilterWidgetState extends State { + final List periodDropDownTitles = []; + int periodDropDownIndex = 0; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + final s = S.of(context); + if (periodDropDownTitles.isEmpty) { + periodDropDownTitles.addAll([ + s.for_whole_period_of_time, + s.for_a_year, + s.for_a_month, + s.for_a_week, + ]); + } + } + + @override + Widget build(BuildContext context) { + final s = S.of(context); + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CustomDropDownButton( + hint: Text( + s.choose_diet_duration, + style: TextStyles.textSMedium.copyWith(color: appTheme.black), + ), + onChanged: (value) { + if (value != null) { + setState(() { + periodDropDownIndex = periodDropDownTitles.indexOf(value); + }); + } + }, + items: periodDropDownTitles + .mapIndexed( + (index, value) => DropdownMenuItem( + child: Text( + periodDropDownTitles[index], + style: TextStyles.textMMedium.copyWith(color: appTheme.black), + ), + value: periodDropDownTitles[index], + ), + ) + .toList(), + value: periodDropDownTitles[periodDropDownIndex], + ), + ], + ); + } +} diff --git a/lib/features/consultant_rating/presentation/widgets/rating_history_item.dart b/lib/features/consultant_rating/presentation/widgets/rating_history_item.dart new file mode 100644 index 00000000..0a4c052c --- /dev/null +++ b/lib/features/consultant_rating/presentation/widgets/rating_history_item.dart @@ -0,0 +1,49 @@ +import 'package:eatvio_app/core/theme/custom_text_style.dart'; +import 'package:eatvio_app/core/theme/theme_helper.dart'; +import 'package:eatvio_app/core/utils/date_time_utils.dart'; +import 'package:flutter/material.dart'; + +class RatingHistoryItem extends StatelessWidget { + const RatingHistoryItem({super.key, required this.title, required this.amount, required this.date}); + final String title; + final int amount; + final DateTime date; + @override + Widget build(BuildContext context) { + return Container( + height: 80, + padding: EdgeInsets.all(12), + decoration: BoxDecoration( + color: appTheme.gray100, + borderRadius: BorderRadius.circular(20), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + date.format('dd.MM.yyyy'), + style: TextStyles.textXSSemibold.copyWith(color: appTheme.gray), + ), + Spacer(), + Text( + title, + style: TextStyles.textMMedium.copyWith(color: appTheme.black), + ), + ], + ), + ), + Text( + '+$amount', + textAlign: TextAlign.center, + style: TextStyles.headingXLSemibold.copyWith(color: appTheme.greenA700), + ), + ], + ), + ); + } +} diff --git a/lib/features/consultant_rating/presentation/widgets/rating_history_widget.dart b/lib/features/consultant_rating/presentation/widgets/rating_history_widget.dart new file mode 100644 index 00000000..835cc216 --- /dev/null +++ b/lib/features/consultant_rating/presentation/widgets/rating_history_widget.dart @@ -0,0 +1,69 @@ +import 'package:eatvio_app/core/theme/custom_text_style.dart'; +import 'package:eatvio_app/core/theme/theme_helper.dart'; +import 'package:eatvio_app/core/widgets/main_background_bouble.dart'; +import 'package:eatvio_app/features/consultant_rating/models/rating_model.dart'; +import 'package:eatvio_app/features/consultant_rating/presentation/widgets/period_filter_widget.dart'; +import 'package:eatvio_app/features/consultant_rating/presentation/widgets/rating_history_item.dart'; +import 'package:eatvio_app/generated/l10n.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; + +class RatingHistoryWidget extends StatelessWidget { + const RatingHistoryWidget({super.key, required this.ratingHistory}); + final List ratingHistory; + @override + Widget build(BuildContext context) { + final s = S.of(context); + return MainBackgroundBouble( + padding: EdgeInsets.only( + left: 16, + right: 16, + top: 16, + ), + child: Column( + children: [ + Row( + children: [ + Text( + s.rating_history, + style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black), + ), + Spacer(), + SvgPicture.asset( + 'assets/icons/question_mark.svg', + height: 22, + width: 22, + colorFilter: ColorFilter.mode( + appTheme.blueA400, + BlendMode.srcIn, + ), + ), + ], + ), + SizedBox( + height: 16, + ), + PeriodFilterWidget(), + SizedBox( + height: 12, + ), + ListView.builder( + physics: NeverScrollableScrollPhysics(), + itemBuilder: (context, index) { + return Padding( + padding: index == ratingHistory.length - 1 ? EdgeInsets.zero : const EdgeInsets.only(bottom: 12), + child: RatingHistoryItem( + title: ratingHistory[index].title, + amount: ratingHistory[index].amount, + date: ratingHistory[index].date, + ), + ); + }, + itemCount: ratingHistory.length, + shrinkWrap: true, + ), + ], + ), + ); + } +} diff --git a/lib/features/consultant_rating/presentation/widgets/rating_widget.dart b/lib/features/consultant_rating/presentation/widgets/rating_widget.dart new file mode 100644 index 00000000..bffdb6ff --- /dev/null +++ b/lib/features/consultant_rating/presentation/widgets/rating_widget.dart @@ -0,0 +1,56 @@ +import 'package:eatvio_app/core/theme/custom_text_style.dart'; +import 'package:eatvio_app/core/theme/theme_helper.dart'; +import 'package:eatvio_app/core/widgets/main_background_bouble.dart'; +import 'package:eatvio_app/generated/l10n.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; + +class RatingWidget extends StatelessWidget { + const RatingWidget({super.key, required this.rating}); + + final int rating; + + @override + Widget build(BuildContext context) { + final s = S.of(context); + return MainBackgroundBouble( + child: SizedBox( + height: 130, + child: Stack( + children: [ + Positioned( + right: 12, + top: 12, + child: SvgPicture.asset( + 'assets/icons/question_mark.svg', + height: 22, + width: 22, + colorFilter: ColorFilter.mode( + appTheme.blueA400, + BlendMode.srcIn, + ), + ), + ), + Align( + alignment: Alignment.center, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + rating.toString(), + style: TextStyles.headingFS40FW600.copyWith(color: appTheme.blueA400), + ), + SizedBox(height: 4), + Text( + s.your_rating, + style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black), + ), + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/features/consultant_rating/presentation/widgets/statistic_bouble.dart b/lib/features/consultant_rating/presentation/widgets/statistic_bouble.dart new file mode 100644 index 00000000..d306eb51 --- /dev/null +++ b/lib/features/consultant_rating/presentation/widgets/statistic_bouble.dart @@ -0,0 +1,37 @@ +import 'package:eatvio_app/core/theme/custom_text_style.dart'; +import 'package:eatvio_app/core/theme/theme_helper.dart'; +import 'package:flutter/material.dart'; + +class StatisticBouble extends StatelessWidget { + const StatisticBouble({super.key, required this.number, required this.title}); + final int number; + final String title; + @override + Widget build(BuildContext context) { + return Container( + height: 80, + padding: EdgeInsets.symmetric(horizontal: 12), + decoration: BoxDecoration( + color: appTheme.gray100, + borderRadius: BorderRadius.circular(20), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + number.toString(), + style: TextStyles.headingXLSemibold.copyWith(color: appTheme.blueA400), + ), + SizedBox( + height: 4, + ), + Text( + title, + textAlign: TextAlign.center, + style: TextStyles.textXSSemibold.copyWith(color: appTheme.black), + ), + ], + ), + ); + } +} diff --git a/lib/features/consultant_rating/presentation/widgets/statistic_widget.dart b/lib/features/consultant_rating/presentation/widgets/statistic_widget.dart new file mode 100644 index 00000000..911007c1 --- /dev/null +++ b/lib/features/consultant_rating/presentation/widgets/statistic_widget.dart @@ -0,0 +1,79 @@ +import 'package:eatvio_app/core/theme/custom_text_style.dart'; +import 'package:eatvio_app/core/theme/theme_helper.dart'; +import 'package:eatvio_app/core/widgets/main_background_bouble.dart'; +import 'package:eatvio_app/features/consultant_rating/presentation/widgets/period_filter_widget.dart'; +import 'package:eatvio_app/features/consultant_rating/presentation/widgets/statistic_bouble.dart'; +import 'package:eatvio_app/generated/l10n.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; + +class StatisticWidget extends StatelessWidget { + const StatisticWidget({super.key}); + + @override + Widget build(BuildContext context) { + final s = S.of(context); + return MainBackgroundBouble( + padding: EdgeInsets.all(16), + child: Column( + children: [ + Row( + children: [ + Text( + s.statistic, + style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black), + ), + Spacer(), + SvgPicture.asset( + 'assets/icons/question_mark.svg', + height: 22, + width: 22, + colorFilter: ColorFilter.mode( + appTheme.blueA400, + BlendMode.srcIn, + ), + ), + ], + ), + SizedBox( + height: 16, + ), + PeriodFilterWidget(), + SizedBox( + height: 12, + ), + Row( + children: [ + Expanded(child: StatisticBouble(number: 12, title: s.invited_people_count)), + SizedBox(width: 8), + Expanded(child: StatisticBouble(number: 12, title: s.published_posts_count)), + ], + ), + SizedBox( + height: 8, + ), + Row( + children: [ + Expanded(child: StatisticBouble(number: 12, title: s.published_recipes_count)), + SizedBox(width: 8), + Expanded(child: StatisticBouble(number: 12, title: s.published_diets_count)), + ], + ), + SizedBox( + height: 8, + ), + Row( + children: [ + Expanded(child: StatisticBouble(number: 12, title: s.consultations_count)), + SizedBox(width: 8), + Expanded(child: StatisticBouble(number: 12, title: s.active_consultations_count)), + ], + ), + SizedBox( + height: 8, + ), + ], + ), + ); + } +} diff --git a/lib/features/create_diet/presentation/create_diet_screen.dart b/lib/features/create_diet/presentation/create_diet_screen.dart index 3afc21d0..632fb5f4 100644 --- a/lib/features/create_diet/presentation/create_diet_screen.dart +++ b/lib/features/create_diet/presentation/create_diet_screen.dart @@ -7,6 +7,7 @@ import 'package:eatvio_app/core/widgets/custom_outlined_button.dart'; import 'package:eatvio_app/core/widgets/custom_text_form_field.dart'; import 'package:eatvio_app/core/widgets/loader.dart'; import 'package:eatvio_app/core/widgets/main_background_bouble.dart'; +import 'package:eatvio_app/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/cubit/create_diet_cubit.dart'; import 'package:eatvio_app/features/create_diet/presentation/cubit/create_diet_state.dart'; @@ -206,13 +207,19 @@ class _CreateDietScreenContentState extends State { Loader() else CustomOutlinedButton( - onPressed: () { + onPressed: () async { if (widget.diet != null) { - context.read().updateDiet( + final result = await context.read().updateDiet( widget.diet!.id, ); + if (result == 'error') { + showErrorDialog(context, title: '${s.could_not_update_diet} ${state.dietData.name}'); + } } else { - context.read().createDiet(); + final result = await context.read().createDiet(); + if (result == 'error') { + showErrorDialog(context, title: '${s.could_not_publish_diet} ${state.dietData.name}'); + } } }, text: widget.diet != null ? s.save : s.publish, 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 759cfae6..80f1850a 100644 --- a/lib/features/create_diet/presentation/cubit/create_diet_cubit.dart +++ b/lib/features/create_diet/presentation/cubit/create_diet_cubit.dart @@ -323,7 +323,7 @@ class CreateDietCubit extends Cubit { Future createDiet() async { emit(state.copyWith(isDietPublishing: true)); - if (!_isFromValid()) return null; + if (!_isFromValid()) return 'validation'; try { final image = await ImageCompressUtility.compressFile(File(state.dietData.image!.path)); final dietState = state.dietData.state!; @@ -342,16 +342,16 @@ class CreateDietCubit extends Cubit { ); emit(state.copyWith(published: dietState != DietState.draft, drafted: dietState == DietState.draft)); - return state.dietData.name; + return null; } on DioException catch (e, stackTrace) { log(e.toString(), stackTrace: stackTrace); } emit(state.copyWith(isDietPublishing: false)); - return null; + return 'error'; } Future updateDiet(int dietId) async { - if (!_isFromValid()) return null; + if (!_isFromValid()) return 'error'; try { File? image; if (state.dietData.image != null) { @@ -373,7 +373,7 @@ class CreateDietCubit extends Cubit { return null; } catch (e, st) { log(e.toString(), stackTrace: st); - return 'Произошла непредвиденная ошибка'; + return 'error'; } } diff --git a/lib/features/create_diet/presentation/diet_preview_screen.dart b/lib/features/create_diet/presentation/diet_preview_screen.dart index 5a385a16..066ac06e 100644 --- a/lib/features/create_diet/presentation/diet_preview_screen.dart +++ b/lib/features/create_diet/presentation/diet_preview_screen.dart @@ -1,7 +1,7 @@ import 'dart:convert'; import 'dart:io'; import 'dart:typed_data'; -import 'package:eatvio_app/core/utils/enums.dart'; +import 'package:eatvio_app/core/enums/product_type.dart'; import 'package:eatvio_app/core/utils/localization_utils.dart'; import 'package:eatvio_app/core/utils/string_extentions.dart'; import 'package:eatvio_app/core/widgets/bookmark_button.dart'; diff --git a/lib/features/create_diet/repositories/create_diet_repository.dart b/lib/features/create_diet/repositories/create_diet_repository.dart index 19e14ea6..3661dfd9 100644 --- a/lib/features/create_diet/repositories/create_diet_repository.dart +++ b/lib/features/create_diet/repositories/create_diet_repository.dart @@ -48,7 +48,7 @@ class CreateDietRepository { ), }); - final formData = FormData.fromMap(dietMap); + final formData = FormData.fromMap(dietMap); switch (state) { case DietState.active: return createDietService.createPublishedDiet(formData: formData); diff --git a/lib/features/create_diet/services/create_diet_service.dart b/lib/features/create_diet/services/create_diet_service.dart index b6537ea9..3c0d8923 100644 --- a/lib/features/create_diet/services/create_diet_service.dart +++ b/lib/features/create_diet/services/create_diet_service.dart @@ -26,21 +26,21 @@ abstract class CreateDietService { @Body() required FormData formData, }); - @PUT('/api/diets/update/published/{diet}') + @POST('/api/diets/update/published/{diet}') @MultiPart() Future updatePublished({ @Path() required int diet, @Body() required FormData formData, }); - @PUT('/api/diets/update/drafted/{diet}') + @POST('/api/diets/update/drafted/{diet}') @MultiPart() Future updateDrafted({ @Path() required int diet, @Body() required FormData formData, }); - @PUT('/api/diets/update/personalized/{diet}') + @POST('/api/diets/update/personalized/{diet}') @MultiPart() Future updatePersonal({ @Path() required int diet, diff --git a/lib/features/create_diet/services/create_diet_service.g.dart b/lib/features/create_diet/services/create_diet_service.g.dart index 73d5ef59..61af7e06 100644 --- a/lib/features/create_diet/services/create_diet_service.g.dart +++ b/lib/features/create_diet/services/create_diet_service.g.dart @@ -124,7 +124,7 @@ class _CreateDietService implements CreateDietService { final _data = formData; final _options = _setStreamType( Options( - method: 'PUT', + method: 'POST', headers: _headers, extra: _extra, contentType: 'multipart/form-data', @@ -151,7 +151,7 @@ class _CreateDietService implements CreateDietService { final _data = formData; final _options = _setStreamType( Options( - method: 'PUT', + method: 'POST', headers: _headers, extra: _extra, contentType: 'multipart/form-data', @@ -178,7 +178,7 @@ class _CreateDietService implements CreateDietService { final _data = formData; final _options = _setStreamType( Options( - method: 'PUT', + method: 'POST', headers: _headers, extra: _extra, contentType: 'multipart/form-data', 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 da31746c..3647ebdd 100644 --- a/lib/features/create_recipe/presentation/pages/recipe_create_screen.dart +++ b/lib/features/create_recipe/presentation/pages/recipe_create_screen.dart @@ -1,13 +1,17 @@ import 'package:eatvio_app/core/locator_service.dart'; import 'package:eatvio_app/core/theme/theme_helper.dart'; -import 'package:eatvio_app/core/utils/toasts.dart'; +import 'package:eatvio_app/core/widgets/bottom_sheet_with_blur.dart'; +import 'package:eatvio_app/core/widgets/show_error_dialog.dart'; +import 'package:eatvio_app/core/widgets/success_published_recipe_bottom_sheet.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/bloc/create_recipe_cubit/create_recipe_state.dart'; import 'package:eatvio_app/features/create_recipe/presentation/widgets/info_about_recipe_widget.dart'; import 'package:eatvio_app/features/create_recipe/presentation/widgets/ingredients_widget.dart'; import 'package:eatvio_app/features/create_recipe/presentation/widgets/steps_widget.dart'; +import 'package:eatvio_app/generated/l10n.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:go_router/go_router.dart'; class RecipeCreateScreen extends StatefulWidget { const RecipeCreateScreen({super.key}); @@ -22,6 +26,7 @@ class RecipeCreateScreen extends StatefulWidget { class _RecipeCreateScreenState extends State { @override Widget build(BuildContext context) { + final s = S.of(context); return BlocProvider( create: (BuildContext context) => sl(), child: Scaffold( @@ -53,10 +58,18 @@ class _RecipeCreateScreenState extends State { if (context.read().validateAll()) { final isSuccess = await context.read().publishRecipe(); if (isSuccess) { - showSuccessToast('Рецепт успешно опубликован'); - Navigator.pop(context); + Future.delayed(Duration(milliseconds: 500), () { + context.pop(); + showBottomSheetWithBlur( + userRootNavigator: true, + context: context, + child: SuccessPublishedRecipeBottomSheet( + title: state.entity.title ?? 'Рецепт', + ), + ); + }); } else { - showErrorToast('Не удалось опубликовать рецепт'); + showErrorDialog(context, title: '${s.could_not_publish_recipe} ${state.entity.title}'); } } }, diff --git a/lib/features/diary/presentation/pages/diary_screen.dart b/lib/features/diary/presentation/pages/diary_screen.dart index d0ef5bcb..250f3007 100644 --- a/lib/features/diary/presentation/pages/diary_screen.dart +++ b/lib/features/diary/presentation/pages/diary_screen.dart @@ -1,5 +1,6 @@ import 'dart:developer'; +import 'package:eatvio_app/core/widgets/data_error_widget.dart'; import 'package:eatvio_app/core/widgets/loader.dart'; import 'package:eatvio_app/features/diary/presentation/bloc/diary_cubit.dart'; import 'package:eatvio_app/features/diary/presentation/bloc/diary_state.dart'; @@ -27,6 +28,11 @@ class _DiaryScreenState extends State { if (state is DiaryStateLoaded) { log(state.diary.id.toString()); } + if (state is DiaryStateError) { + return Scaffold( + body: DataErrorWidget(onReload: context.read().fetchDiary()), + ); + } return Scaffold( backgroundColor: Colors.grey.shade200, appBar: AppBar( diff --git a/lib/features/diets/presentation/cubit/about_diet_cubit.dart b/lib/features/diets/presentation/cubit/about_diet_cubit.dart index ea57351d..07532799 100644 --- a/lib/features/diets/presentation/cubit/about_diet_cubit.dart +++ b/lib/features/diets/presentation/cubit/about_diet_cubit.dart @@ -26,6 +26,7 @@ class AboutDietCubit extends Cubit { emit(AboutDietState.loaded(diet: diet, isStarted: isCurrentDietStarted, hasStartedDiet: hasStartedDiet)); } catch (e) { log(e.toString()); + emit(AboutDietState.error(message: e.toString())); } } @@ -49,6 +50,7 @@ class AboutDietCubit extends Cubit { emit(AboutDietState.loaded(diet: diet, isStarted: isCurrentDietStarted, hasStartedDiet: hasStartedDiet)); } catch (e) { log(e.toString()); + emit(AboutDietState.error(message: e.toString())); } } diff --git a/lib/features/diets/presentation/pages/about_diet_screen.dart b/lib/features/diets/presentation/pages/about_diet_screen.dart index b3397495..93668286 100644 --- a/lib/features/diets/presentation/pages/about_diet_screen.dart +++ b/lib/features/diets/presentation/pages/about_diet_screen.dart @@ -2,11 +2,13 @@ import 'dart:io'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:eatvio_app/core/const.dart'; -import 'package:eatvio_app/core/utils/enums.dart'; +import 'package:eatvio_app/core/enums/complained_content.dart'; +import 'package:eatvio_app/core/enums/product_type.dart'; import 'package:eatvio_app/core/utils/localization_utils.dart'; import 'package:eatvio_app/core/utils/string_extentions.dart'; import 'package:eatvio_app/core/widgets/bookmark_button.dart'; import 'package:eatvio_app/core/widgets/custom_divider.dart'; +import 'package:eatvio_app/core/widgets/data_error_widget.dart'; import 'package:eatvio_app/core/widgets/info_row.dart'; import 'package:eatvio_app/core/widgets/loader.dart'; import 'package:eatvio_app/features/diets/presentation/cubit/about_diet_cubit.dart'; @@ -314,6 +316,8 @@ class _AboutDietScreenState extends State { visible: isMoreOptionsVisible, portalFollower: MoreOptions( path: 'diet/${widget.diet.id}', + complainedContentType: ComplainedContent.diet, + id: widget.diet.id, ), anchor: Aligned( shiftToWithinBound: AxisFlag(x: true, y: true), @@ -420,7 +424,10 @@ class _AboutDietScreenState extends State { ), ), SizedBox(height: 12), - InfoRow(diet: widget.diet, type: ProductType.diet,), + InfoRow( + diet: widget.diet, + type: ProductType.diet, + ), ExpandableText( text: widget.diet.details, ), @@ -490,7 +497,90 @@ class _AboutDietScreenState extends State { ), ); } + if (dietState is DietStateError) { + return Scaffold( + appBar: AppBar( + centerTitle: true, + title: Text( + widget.diet.name, + maxLines: 2, + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + softWrap: true, + style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black), + ), + actions: [ + PortalTarget( + visible: isMoreOptionsVisible, + portalFollower: MoreOptions( + path: 'diet/${widget.diet.id}', + complainedContentType: ComplainedContent.diet, + id: widget.diet.id, + ), + anchor: Aligned( + shiftToWithinBound: AxisFlag(x: true, y: true), + follower: Alignment.topRight, + target: Alignment.bottomRight, + ), + child: IconButton( + onPressed: () { + setState(() { + isMoreOptionsVisible = !isMoreOptionsVisible; + }); + }, + icon: SvgPicture.asset( + 'assets/icons/more_button.svg', + width: 20, + height: 20, + ), + ), + ), + ], + ), + body: DataErrorWidget( + onReload: () => context.read().getDiet(widget.diet.id, index: widget.index), + ), + ); + } return Scaffold( + appBar: AppBar( + centerTitle: true, + title: Text( + widget.diet.name, + maxLines: 2, + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + softWrap: true, + style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black), + ), + actions: [ + PortalTarget( + visible: isMoreOptionsVisible, + portalFollower: MoreOptions( + path: 'diet/${widget.diet.id}', + complainedContentType: ComplainedContent.diet, + id: widget.diet.id, + ), + anchor: Aligned( + shiftToWithinBound: AxisFlag(x: true, y: true), + follower: Alignment.topRight, + target: Alignment.bottomRight, + ), + child: IconButton( + onPressed: () { + setState(() { + isMoreOptionsVisible = !isMoreOptionsVisible; + }); + }, + icon: SvgPicture.asset( + 'assets/icons/more_button.svg', + width: 20, + height: 20, + ), + ), + ), + ], + ), body: const Loader(), ); }, diff --git a/lib/features/diets/presentation/widgets/more_options.dart b/lib/features/diets/presentation/widgets/more_options.dart index 01da9a87..3347144c 100644 --- a/lib/features/diets/presentation/widgets/more_options.dart +++ b/lib/features/diets/presentation/widgets/more_options.dart @@ -1,5 +1,7 @@ import 'package:eatvio_app/core/const.dart'; +import 'package:eatvio_app/core/enums/complained_content.dart'; import 'package:eatvio_app/core/utils/toasts.dart'; +import 'package:eatvio_app/features/complaining/presentation/complaining_bottom_sheet.dart'; import 'package:eatvio_app/core/widgets/custom_divider.dart'; import 'package:eatvio_app/core/widgets/main_background_bouble.dart'; import 'package:eatvio_app/generated/l10n.dart'; @@ -8,12 +10,13 @@ import 'package:eatvio_app/core/theme/theme_helper.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:go_router/go_router.dart'; import 'package:share_plus/share_plus.dart'; class MoreOptions extends StatelessWidget { - const MoreOptions({super.key, this.disableActions, required this.path}); - + const MoreOptions({super.key, this.disableActions, required this.path, this.complainedContentType, this.slug, this.id}); + final ComplainedContent? complainedContentType; + final int? id; + final String? slug; final String path; final bool? disableActions; @@ -84,7 +87,16 @@ class MoreOptions extends StatelessWidget { onTap: disableActions == true ? null : () { - context.push('complainingScreen'); + if (complainedContentType != null) { + showModalBottomSheet( + context: context, + builder: (context) => ComplainingBottomSheet( + complainedContentType: complainedContentType!, + id: id, + slug: slug, + ), + ); + } }, child: Row( children: [ diff --git a/lib/features/feed/models/diets_pagination_model.g.dart b/lib/features/feed/models/diets_pagination_model.g.dart index 3e7099a5..3832c00d 100644 --- a/lib/features/feed/models/diets_pagination_model.g.dart +++ b/lib/features/feed/models/diets_pagination_model.g.dart @@ -6,57 +6,49 @@ part of 'diets_pagination_model.dart'; // JsonSerializableGenerator // ************************************************************************** -_$DietsPaginationImpl _$$DietsPaginationImplFromJson( - Map json) => - _$DietsPaginationImpl( - data: (json['data'] as List) - .map((e) => Diet.fromJson(e as Map)) - .toList(), +_$DietsPaginationImpl _$$DietsPaginationImplFromJson(Map json) => _$DietsPaginationImpl( + data: (json['data'] as List).map((e) => Diet.fromJson(e as Map)).toList(), links: Links.fromJson(json['links'] as Map), meta: Meta.fromJson(json['meta'] as Map), ); -Map _$$DietsPaginationImplToJson( - _$DietsPaginationImpl instance) => - { +Map _$$DietsPaginationImplToJson(_$DietsPaginationImpl instance) => { 'data': instance.data, 'links': instance.links, 'meta': instance.meta, }; -_$DietImpl _$$DietImplFromJson(Map json) => _$DietImpl( - id: (json['id'] as num).toInt(), - name: json['name'] as String, - image: json['image'] as String, - duration: (json['duration'] as num).toInt(), - state: $enumDecode(_$DietStateEnumMap, json['state']), - type: DietType.fromJson(json['type'] as Map), - details: json['details'] as String, - dailySchedule: (json['daily_schedule'] as Map).map( - (k, e) => MapEntry( - int.parse(k), - (e as Map).map( - (k, e) => MapEntry( - int.parse(k), - (e as Map).map( - (k, e) => MapEntry(int.parse(k), - DailySchedule.fromJson(e as Map)), - )), - )), - ), - author: Author.fromJson(json['author'] as Map), - totalLikes: (json['total_likes'] as num).toInt(), - totalViews: (json['total_views'] as num).toInt(), - totalComments: (json['total_comments'] as num).toInt(), - wasLikedByMe: json['was_liked_by_me'] as bool, - isFavorite: json['is_favorite'] as bool, - dailyNutrition: (json['daily_nutrition'] as List) - .map((e) => DailyNutrition.fromJson(e as Map)) - .toList(), - ); +_$DietImpl _$$DietImplFromJson(Map json) { + return _$DietImpl( + id: (json['id'] as num).toInt(), + name: json['name'] as String, + image: json['image'] as String, + duration: (json['duration'] as num).toInt(), + state: $enumDecode(_$DietStateEnumMap, json['state']), + type: DietType.fromJson(json['type'] as Map), + details: json['details'] as String, + dailySchedule: (json['daily_schedule'] as Map).map( + (k, e) => MapEntry( + int.parse(k), + (e as Map).map( + (k, e) => MapEntry( + int.parse(k), + (e as Map).map( + (k, e) => MapEntry(int.parse(k), DailySchedule.fromJson(e as Map)), + )), + )), + ), + author: Author.fromJson(json['author'] as Map), + totalLikes: (json['total_likes'] as num).toInt(), + totalViews: (json['total_views'] as num).toInt(), + totalComments: (json['total_comments'] as num).toInt(), + wasLikedByMe: json['was_liked_by_me'] as bool, + isFavorite: json['is_favorite'] as bool, + dailyNutrition: (json['daily_nutrition'] as List).map((e) => DailyNutrition.fromJson(e as Map)).toList(), + ); +} -Map _$$DietImplToJson(_$DietImpl instance) => - { +Map _$$DietImplToJson(_$DietImpl instance) => { 'id': instance.id, 'name': instance.name, 'image': instance.image, @@ -64,10 +56,8 @@ Map _$$DietImplToJson(_$DietImpl instance) => 'state': _$DietStateEnumMap[instance.state]!, 'type': instance.type, 'details': instance.details, - 'daily_schedule': instance.dailySchedule.map((k, e) => MapEntry( - k.toString(), - e.map((k, e) => MapEntry( - k.toString(), e.map((k, e) => MapEntry(k.toString(), e)))))), + 'daily_schedule': + instance.dailySchedule.map((k, e) => MapEntry(k.toString(), e.map((k, e) => MapEntry(k.toString(), e.map((k, e) => MapEntry(k.toString(), e)))))), 'author': instance.author, 'total_likes': instance.totalLikes, 'total_views': instance.totalViews, @@ -83,13 +73,11 @@ const _$DietStateEnumMap = { DietState.personal: 'personal', }; -_$CreatedDietImpl _$$CreatedDietImplFromJson(Map json) => - _$CreatedDietImpl( +_$CreatedDietImpl _$$CreatedDietImplFromJson(Map json) => _$CreatedDietImpl( id: (json['id'] as num).toInt(), ); -Map _$$CreatedDietImplToJson(_$CreatedDietImpl instance) => - { +Map _$$CreatedDietImplToJson(_$CreatedDietImpl instance) => { 'id': instance.id, }; @@ -102,8 +90,7 @@ _$AuthorImpl _$$AuthorImplFromJson(Map json) => _$AuthorImpl( authorPlaceInRating: (json['author_place_in_rating'] as num?)?.toInt(), ); -Map _$$AuthorImplToJson(_$AuthorImpl instance) => - { +Map _$$AuthorImplToJson(_$AuthorImpl instance) => { 'author_slug': instance.authorSlug, 'author_name': instance.authorName, 'author_profile_icon': instance.authorProfileIcon, @@ -112,23 +99,18 @@ Map _$$AuthorImplToJson(_$AuthorImpl instance) => 'author_place_in_rating': instance.authorPlaceInRating, }; -_$DailyScheduleImpl _$$DailyScheduleImplFromJson(Map json) => - _$DailyScheduleImpl( +_$DailyScheduleImpl _$$DailyScheduleImplFromJson(Map json) => _$DailyScheduleImpl( name: json['name'] as String, portion: $enumDecode(_$PortionEnumMap, json['portion']), quantity: _parseQuantity(json['quantity']), - dailyNutrition: json['nutrition'] == null - ? null - : DailyNutrition.fromJson(json['nutrition'] as Map), + dailyNutrition: json['nutrition'] == null ? null : DailyNutrition.fromJson(json['nutrition'] as Map), ); -Map _$$DailyScheduleImplToJson(_$DailyScheduleImpl instance) => - { +Map _$$DailyScheduleImplToJson(_$DailyScheduleImpl instance) => { 'name': instance.name, 'portion': _$PortionEnumMap[instance.portion]!, 'quantity': instance.quantity, - if (instance.productNutritionalValueId case final value?) - 'product_nutritional_value_id': value, + if (instance.productNutritionalValueId case final value?) 'product_nutritional_value_id': value, }; const _$PortionEnumMap = { @@ -137,33 +119,28 @@ const _$PortionEnumMap = { Portion.PCS: 'pcs', }; -_$DietTypeImpl _$$DietTypeImplFromJson(Map json) => - _$DietTypeImpl( +_$DietTypeImpl _$$DietTypeImplFromJson(Map json) => _$DietTypeImpl( light: json['light'] as bool?, veg: json['veg'] as bool?, carb: json['carb'] as bool?, meat: json['meat'] as bool?, ); -Map _$$DietTypeImplToJson(_$DietTypeImpl instance) => - { +Map _$$DietTypeImplToJson(_$DietTypeImpl instance) => { 'light': instance.light, 'veg': instance.veg, 'carb': instance.carb, 'meat': instance.meat, }; -_$DailyNutritionImpl _$$DailyNutritionImplFromJson(Map json) => - _$DailyNutritionImpl( +_$DailyNutritionImpl _$$DailyNutritionImplFromJson(Map json) => _$DailyNutritionImpl( energy: (json['energy'] as num).toDouble(), proteins: (json['proteins'] as num).toDouble(), fats: (json['fats'] as num).toDouble(), carbohydrates: (json['carbohydrates'] as num).toDouble(), ); -Map _$$DailyNutritionImplToJson( - _$DailyNutritionImpl instance) => - { +Map _$$DailyNutritionImplToJson(_$DailyNutritionImpl instance) => { 'energy': instance.energy, 'proteins': instance.proteins, 'fats': instance.fats, diff --git a/lib/features/feed/presentation/bloc/diets_cubit/diets_cubit.dart b/lib/features/feed/presentation/bloc/diets_cubit/diets_cubit.dart index de59de7e..1770e23d 100644 --- a/lib/features/feed/presentation/bloc/diets_cubit/diets_cubit.dart +++ b/lib/features/feed/presentation/bloc/diets_cubit/diets_cubit.dart @@ -110,7 +110,7 @@ class DietsCubit extends Cubit { } catch (e, st) { log(e.toString(), stackTrace: st); emit( - state.copyWith(isLoading: false), + state.copyWith(isLoading: false, errorMessage: e.toString()), ); } } @@ -144,13 +144,15 @@ class DietsCubit extends Cubit { ); } } catch (e) { - emit(state.copyWith(isLoading: false)); + emit( + state.copyWith(isLoading: false, errorMessage: e.toString()), + ); } } } Future likeDiet(int id, int index) async { - final currentDiets = state.dietsList; + final currentDiets = state.dietsList; final dietsForModification = [...currentDiets]; final currentDiet = currentDiets[index]; if (currentDiet.wasLikedByMe) { diff --git a/lib/features/feed/presentation/pages/feed_screen.dart b/lib/features/feed/presentation/pages/feed_screen.dart index a42bab2f..faf90370 100644 --- a/lib/features/feed/presentation/pages/feed_screen.dart +++ b/lib/features/feed/presentation/pages/feed_screen.dart @@ -22,7 +22,6 @@ import 'package:eatvio_app/features/create_recipe/presentation/pages/recipe_crea import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; - import '../widgets/recipes.dart'; class FeedScreen extends StatelessWidget { diff --git a/lib/features/feed/presentation/widgets/blog_posts.dart b/lib/features/feed/presentation/widgets/blog_posts.dart index f76e35ce..da5363fe 100644 --- a/lib/features/feed/presentation/widgets/blog_posts.dart +++ b/lib/features/feed/presentation/widgets/blog_posts.dart @@ -1,3 +1,4 @@ +import 'package:eatvio_app/core/widgets/data_error_widget.dart'; import 'package:eatvio_app/core/widgets/loader.dart'; import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart'; import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_state.dart'; @@ -31,14 +32,8 @@ class BlogPosts extends StatelessWidget { itemCount: state.isLoading ? state.feedBlogPostList.length + 1 : state.feedBlogPostList.length, ); } else if (state is FeedStateError) { - return Scaffold( - backgroundColor: Colors.grey.shade200, - body: Padding( - padding: const EdgeInsets.all(16), - child: Center( - child: Text(state.errorMessage), - ), - ), + return DataErrorWidget( + onReload: () => context.read().loadFeedBlogPostList(), ); } else { return Scaffold( @@ -50,4 +45,4 @@ class BlogPosts extends StatelessWidget { ), ); } -} \ No newline at end of file +} diff --git a/lib/features/feed/presentation/widgets/consultants.dart b/lib/features/feed/presentation/widgets/consultants.dart index 01c4e816..4e505e40 100644 --- a/lib/features/feed/presentation/widgets/consultants.dart +++ b/lib/features/feed/presentation/widgets/consultants.dart @@ -1,4 +1,5 @@ - import 'package:eatvio_app/core/widgets/loader.dart'; +import 'package:eatvio_app/core/widgets/data_error_widget.dart'; +import 'package:eatvio_app/core/widgets/loader.dart'; import 'package:eatvio_app/features/feed/presentation/bloc/consultants_cubit/consultants_cubit.dart'; import 'package:eatvio_app/features/feed/presentation/bloc/consultants_cubit/consultants_state.dart'; import 'package:eatvio_app/features/feed/presentation/widgets/consultant_widget.dart'; @@ -12,7 +13,7 @@ class Consultants extends StatelessWidget { Widget build(BuildContext context) { return RefreshIndicator( onRefresh: () => context.read().getConsultants(), - child: BlocBuilder( + child: BlocBuilder( builder: (BuildContext context, ConsultantsState state) { if (state is ConsultantsStateLoaded) { return Padding( @@ -38,15 +39,7 @@ class Consultants extends StatelessWidget { ), ); } else if (state is ConsultantsStateError) { - return Scaffold( - backgroundColor: Colors.grey.shade200, - body: Padding( - padding: const EdgeInsets.all(16), - child: Center( - child: Text(state.errorMessage), - ), - ), - ); + return DataErrorWidget(onReload: () => context.read().getConsultants()); } else { return Scaffold( backgroundColor: Colors.grey.shade200, @@ -58,5 +51,3 @@ class Consultants extends StatelessWidget { ); } } - - \ No newline at end of file diff --git a/lib/features/feed/presentation/widgets/diets.dart b/lib/features/feed/presentation/widgets/diets.dart index b698065b..254a9567 100644 --- a/lib/features/feed/presentation/widgets/diets.dart +++ b/lib/features/feed/presentation/widgets/diets.dart @@ -1,3 +1,4 @@ +import 'package:eatvio_app/core/widgets/data_error_widget.dart'; import 'package:eatvio_app/core/widgets/loader.dart'; import 'package:eatvio_app/features/feed/presentation/bloc/diets_cubit/diets_cubit.dart'; import 'package:eatvio_app/features/feed/presentation/bloc/diets_cubit/diets_state.dart'; @@ -22,15 +23,7 @@ class Diets extends StatelessWidget { body: const Loader(), ); } else if (state.errorMessage != null) { - return Scaffold( - backgroundColor: Colors.grey.shade200, - body: Padding( - padding: const EdgeInsets.all(16), - child: Center( - child: Text(state.errorMessage ?? ''), - ), - ), - ); + return DataErrorWidget(onReload: () => context.read().loadDiets()); } else { return Stack( children: [ diff --git a/lib/features/feed/presentation/widgets/diets_widget.dart b/lib/features/feed/presentation/widgets/diets_widget.dart index c02809cb..46b93d18 100644 --- a/lib/features/feed/presentation/widgets/diets_widget.dart +++ b/lib/features/feed/presentation/widgets/diets_widget.dart @@ -1,6 +1,6 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:eatvio_app/core/const.dart'; -import 'package:eatvio_app/core/utils/enums.dart'; +import 'package:eatvio_app/core/enums/product_type.dart'; import 'package:eatvio_app/core/widgets/bookmark_button.dart'; import 'package:eatvio_app/core/widgets/info_row.dart'; import 'package:eatvio_app/core/widgets/feed_icon_button.dart'; diff --git a/lib/features/feed/presentation/widgets/plan_widget.dart b/lib/features/feed/presentation/widgets/plan_widget.dart index 0c966dd4..3166aa96 100644 --- a/lib/features/feed/presentation/widgets/plan_widget.dart +++ b/lib/features/feed/presentation/widgets/plan_widget.dart @@ -1,6 +1,6 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:eatvio_app/core/const.dart'; -import 'package:eatvio_app/core/utils/enums.dart'; +import 'package:eatvio_app/core/enums/product_type.dart'; import 'package:eatvio_app/core/widgets/bookmark_button.dart'; import 'package:eatvio_app/core/widgets/info_row.dart'; import 'package:eatvio_app/core/widgets/feed_icon_button.dart'; diff --git a/lib/features/feed/presentation/widgets/plans.dart b/lib/features/feed/presentation/widgets/plans.dart index 940e677b..cc3a1a63 100644 --- a/lib/features/feed/presentation/widgets/plans.dart +++ b/lib/features/feed/presentation/widgets/plans.dart @@ -1,3 +1,4 @@ +import 'package:eatvio_app/core/widgets/data_error_widget.dart'; import 'package:eatvio_app/core/widgets/loader.dart'; import 'package:eatvio_app/features/feed/presentation/bloc/plans_cubit/plans_cubit.dart'; import 'package:eatvio_app/features/feed/presentation/bloc/plans_cubit/plans_state.dart'; @@ -22,15 +23,7 @@ class Plans extends StatelessWidget { body: const Loader(), ); } else if (state.errorMessage != null) { - return Scaffold( - backgroundColor: Colors.grey.shade200, - body: Padding( - padding: const EdgeInsets.all(16), - child: Center( - child: Text(state.errorMessage ?? ''), - ), - ), - ); + return DataErrorWidget(onReload: () => context.read().loadPlans()); } return Stack( children: [ @@ -65,4 +58,4 @@ class Plans extends StatelessWidget { ), ); } -} \ No newline at end of file +} diff --git a/lib/features/feed/presentation/widgets/recipes.dart b/lib/features/feed/presentation/widgets/recipes.dart index 78ad76e6..b75d50ee 100644 --- a/lib/features/feed/presentation/widgets/recipes.dart +++ b/lib/features/feed/presentation/widgets/recipes.dart @@ -1,3 +1,4 @@ +import 'package:eatvio_app/core/widgets/data_error_widget.dart'; import 'package:eatvio_app/core/widgets/loader.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'; @@ -21,14 +22,10 @@ class Recipes extends StatelessWidget { ); } if (state.errorMessage != null) { - return Scaffold( - backgroundColor: Colors.grey.shade200, - body: Padding( - padding: const EdgeInsets.all(16), - child: Center( - child: Text(state.errorMessage ?? ''), - ), - ), + return DataErrorWidget( + onReload: () async { + context.read().loadRecipesList(); + }, ); } diff --git a/lib/features/internet_connection_checker/internet_connection_checker_screen.dart b/lib/features/internet_connection_checker/internet_connection_checker_screen.dart index f8d258b1..dc6ed06b 100644 --- a/lib/features/internet_connection_checker/internet_connection_checker_screen.dart +++ b/lib/features/internet_connection_checker/internet_connection_checker_screen.dart @@ -9,6 +9,7 @@ import 'package:eatvio_app/core/theme/theme_helper.dart'; import 'package:eatvio_app/core/widgets/custom_outlined_button.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_svg/svg.dart'; import 'package:http/http.dart' as http; class InternetConnectionCheckerScreen extends StatefulWidget { @@ -128,11 +129,12 @@ class NoInternetScreen extends StatelessWidget { body: Column( children: [ Spacer(), + SvgPicture.asset('assets/icons/no_internet.svg'), SizedBox(height: 28), Padding( padding: const EdgeInsets.symmetric(horizontal: 52), child: Text( - 'Отсутствует подключение к интернету', + 'Отсутствует подключение \nк интернету', textAlign: TextAlign.center, style: TextStyles.headingXLSemibold.copyWith( color: appTheme.black, 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 4e17fcce..73f3459a 100644 --- a/lib/features/my_diets/presentation/cubit/my_diets_cubit.dart +++ b/lib/features/my_diets/presentation/cubit/my_diets_cubit.dart @@ -207,13 +207,14 @@ class MyDietsCubit extends Cubit { state.copyWith( activeDiets: dietsPagination.data, isLoading: isLoadingActive, + activeDietsError: null, error: null, ), ); } catch (e, st) { log(e.toString(), stackTrace: st); emit( - state.copyWith(error: e.toString()), + state.copyWith(activeDietsError: e.toString()), ); } } @@ -237,12 +238,13 @@ class MyDietsCubit extends Cubit { state.copyWith( draftDiets: dietsPagination.data, isLoading: isLoadingDraft, + draftDietsError: null, error: null, ), ); } catch (e) { emit( - state.copyWith(error: e.toString()), + state.copyWith(draftDietsError: e.toString()), ); } } @@ -266,19 +268,20 @@ class MyDietsCubit extends Cubit { state.copyWith( personalDiets: dietsPagination.data, isLoading: isLoadingPersonal, + personalDietsError: null, error: null, ), ); } catch (e) { emit( - state.copyWith(error: e.toString()), + state.copyWith(personalDietsError: e.toString()), ); } } Future deleteDiet(int dietId) async { try { - await myDietsRepository.deleteDiet(dietId); + await myDietsRepository.deleteDiet(dietId); return true; } catch (e) { emit( @@ -293,7 +296,7 @@ class MyDietsCubit extends Cubit { await createDietRepository.updateDiet( dietId: dietId, state: DietState.active, - ); + ); return true; } catch (e, st) { log(e.toString(), stackTrace: st); diff --git a/lib/features/my_diets/presentation/cubit/my_diets_state.dart b/lib/features/my_diets/presentation/cubit/my_diets_state.dart index 36b0ec38..b4cf5492 100644 --- a/lib/features/my_diets/presentation/cubit/my_diets_state.dart +++ b/lib/features/my_diets/presentation/cubit/my_diets_state.dart @@ -11,6 +11,9 @@ class MyDietsState with _$MyDietsState { @Default([]) List draftDiets, @Default([]) List personalDiets, @Default(false) bool isLoading, + @Default(null) String? activeDietsError, + @Default(null) String? draftDietsError, + @Default(null) String? personalDietsError, @Default(null) String? error, @Default(0) int selectedStateIndex, @Default( diff --git a/lib/features/my_diets/presentation/cubit/my_diets_state.freezed.dart b/lib/features/my_diets/presentation/cubit/my_diets_state.freezed.dart index b15a73ae..91782100 100644 --- a/lib/features/my_diets/presentation/cubit/my_diets_state.freezed.dart +++ b/lib/features/my_diets/presentation/cubit/my_diets_state.freezed.dart @@ -20,6 +20,9 @@ mixin _$MyDietsState { List get draftDiets => throw _privateConstructorUsedError; List get personalDiets => throw _privateConstructorUsedError; bool get isLoading => throw _privateConstructorUsedError; + String? get activeDietsError => throw _privateConstructorUsedError; + String? get draftDietsError => throw _privateConstructorUsedError; + String? get personalDietsError => throw _privateConstructorUsedError; String? get error => throw _privateConstructorUsedError; int get selectedStateIndex => throw _privateConstructorUsedError; DietsFilterState get activeFilterState => throw _privateConstructorUsedError; @@ -45,6 +48,9 @@ abstract class $MyDietsStateCopyWith<$Res> { List draftDiets, List personalDiets, bool isLoading, + String? activeDietsError, + String? draftDietsError, + String? personalDietsError, String? error, int selectedStateIndex, DietsFilterState activeFilterState, @@ -75,6 +81,9 @@ class _$MyDietsStateCopyWithImpl<$Res, $Val extends MyDietsState> Object? draftDiets = null, Object? personalDiets = null, Object? isLoading = null, + Object? activeDietsError = freezed, + Object? draftDietsError = freezed, + Object? personalDietsError = freezed, Object? error = freezed, Object? selectedStateIndex = null, Object? activeFilterState = null, @@ -98,6 +107,18 @@ class _$MyDietsStateCopyWithImpl<$Res, $Val extends MyDietsState> ? _value.isLoading : isLoading // ignore: cast_nullable_to_non_nullable as bool, + activeDietsError: freezed == activeDietsError + ? _value.activeDietsError + : activeDietsError // ignore: cast_nullable_to_non_nullable + as String?, + draftDietsError: freezed == draftDietsError + ? _value.draftDietsError + : draftDietsError // ignore: cast_nullable_to_non_nullable + as String?, + personalDietsError: freezed == personalDietsError + ? _value.personalDietsError + : personalDietsError // ignore: cast_nullable_to_non_nullable + as String?, error: freezed == error ? _value.error : error // ignore: cast_nullable_to_non_nullable @@ -165,6 +186,9 @@ abstract class _$$MyDietsStateImplCopyWith<$Res> List draftDiets, List personalDiets, bool isLoading, + String? activeDietsError, + String? draftDietsError, + String? personalDietsError, String? error, int selectedStateIndex, DietsFilterState activeFilterState, @@ -196,6 +220,9 @@ class __$$MyDietsStateImplCopyWithImpl<$Res> Object? draftDiets = null, Object? personalDiets = null, Object? isLoading = null, + Object? activeDietsError = freezed, + Object? draftDietsError = freezed, + Object? personalDietsError = freezed, Object? error = freezed, Object? selectedStateIndex = null, Object? activeFilterState = null, @@ -219,6 +246,18 @@ class __$$MyDietsStateImplCopyWithImpl<$Res> ? _value.isLoading : isLoading // ignore: cast_nullable_to_non_nullable as bool, + activeDietsError: freezed == activeDietsError + ? _value.activeDietsError + : activeDietsError // ignore: cast_nullable_to_non_nullable + as String?, + draftDietsError: freezed == draftDietsError + ? _value.draftDietsError + : draftDietsError // ignore: cast_nullable_to_non_nullable + as String?, + personalDietsError: freezed == personalDietsError + ? _value.personalDietsError + : personalDietsError // ignore: cast_nullable_to_non_nullable + as String?, error: freezed == error ? _value.error : error // ignore: cast_nullable_to_non_nullable @@ -251,6 +290,9 @@ class _$MyDietsStateImpl implements _MyDietsState { final List draftDiets = const [], final List personalDiets = const [], this.isLoading = false, + this.activeDietsError = null, + this.draftDietsError = null, + this.personalDietsError = null, this.error = null, this.selectedStateIndex = 0, this.activeFilterState = const DietsFilterState( @@ -295,6 +337,15 @@ class _$MyDietsStateImpl implements _MyDietsState { final bool isLoading; @override @JsonKey() + final String? activeDietsError; + @override + @JsonKey() + final String? draftDietsError; + @override + @JsonKey() + final String? personalDietsError; + @override + @JsonKey() final String? error; @override @JsonKey() @@ -311,7 +362,7 @@ class _$MyDietsStateImpl implements _MyDietsState { @override String toString() { - return 'MyDietsState(activeDiets: $activeDiets, draftDiets: $draftDiets, personalDiets: $personalDiets, isLoading: $isLoading, error: $error, selectedStateIndex: $selectedStateIndex, activeFilterState: $activeFilterState, draftFilterState: $draftFilterState, personalFilterState: $personalFilterState)'; + return 'MyDietsState(activeDiets: $activeDiets, draftDiets: $draftDiets, personalDiets: $personalDiets, isLoading: $isLoading, activeDietsError: $activeDietsError, draftDietsError: $draftDietsError, personalDietsError: $personalDietsError, error: $error, selectedStateIndex: $selectedStateIndex, activeFilterState: $activeFilterState, draftFilterState: $draftFilterState, personalFilterState: $personalFilterState)'; } @override @@ -327,6 +378,12 @@ class _$MyDietsStateImpl implements _MyDietsState { .equals(other._personalDiets, _personalDiets) && (identical(other.isLoading, isLoading) || other.isLoading == isLoading) && + (identical(other.activeDietsError, activeDietsError) || + other.activeDietsError == activeDietsError) && + (identical(other.draftDietsError, draftDietsError) || + other.draftDietsError == draftDietsError) && + (identical(other.personalDietsError, personalDietsError) || + other.personalDietsError == personalDietsError) && (identical(other.error, error) || other.error == error) && (identical(other.selectedStateIndex, selectedStateIndex) || other.selectedStateIndex == selectedStateIndex) && @@ -345,6 +402,9 @@ class _$MyDietsStateImpl implements _MyDietsState { const DeepCollectionEquality().hash(_draftDiets), const DeepCollectionEquality().hash(_personalDiets), isLoading, + activeDietsError, + draftDietsError, + personalDietsError, error, selectedStateIndex, activeFilterState, @@ -366,6 +426,9 @@ abstract class _MyDietsState implements MyDietsState { final List draftDiets, final List personalDiets, final bool isLoading, + final String? activeDietsError, + final String? draftDietsError, + final String? personalDietsError, final String? error, final int selectedStateIndex, final DietsFilterState activeFilterState, @@ -381,6 +444,12 @@ abstract class _MyDietsState implements MyDietsState { @override bool get isLoading; @override + String? get activeDietsError; + @override + String? get draftDietsError; + @override + String? get personalDietsError; + @override String? get error; @override int get selectedStateIndex; diff --git a/lib/features/my_diets/presentation/my_diets_screen.dart b/lib/features/my_diets/presentation/my_diets_screen.dart index 40c77356..36fe1d31 100644 --- a/lib/features/my_diets/presentation/my_diets_screen.dart +++ b/lib/features/my_diets/presentation/my_diets_screen.dart @@ -4,6 +4,7 @@ import 'package:eatvio_app/core/theme/custom_text_style.dart'; import 'package:eatvio_app/core/theme/theme_helper.dart'; import 'package:eatvio_app/core/widgets/add_item_button.dart'; import 'package:eatvio_app/core/widgets/custom_search_bar.dart'; +import 'package:eatvio_app/core/widgets/data_error_widget.dart'; import 'package:eatvio_app/core/widgets/loader.dart'; import 'package:eatvio_app/features/create_diet/presentation/create_diet_screen.dart'; import 'package:eatvio_app/features/feed/presentation/widgets/diet_filter_bottom_sheet.dart'; @@ -142,21 +143,26 @@ class MyDietsScreenContent extends StatelessWidget { body: const Loader(), ); } else if (state.error != null) { - return Scaffold( - backgroundColor: Colors.grey.shade200, - body: Padding( - padding: const EdgeInsets.all(16), - child: Center( - child: Text(state.error ?? ''), - ), - ), - ); + return DataErrorWidget(onReload: () => context.read().loadAllDiets()); } else { + final gotDietLoadingError = state.activeDietsError != null && state.selectedStateIndex == 0 || + state.draftDietsError != null && state.selectedStateIndex == 1 || + state.personalDietsError != null && state.selectedStateIndex == 2; + + if (gotDietLoadingError) { + return DataErrorWidget(onReload: () => context.read().loadCurrentDiets()); + } + + if (state.isLoading) { + return Loader(); + } + final diets = switch (state.selectedStateIndex) { 1 => state.draftDiets, 2 => state.personalDiets, _ => state.activeDiets, }; + return Stack( children: [ SizedBox.expand(), 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 3b5ecc54..ec36cc6b 100644 --- a/lib/features/my_diets/presentation/widgets/my_diet_widget.dart +++ b/lib/features/my_diets/presentation/widgets/my_diet_widget.dart @@ -2,7 +2,7 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:eatvio_app/core/const.dart'; import 'package:eatvio_app/core/theme/custom_text_style.dart'; import 'package:eatvio_app/core/theme/theme_helper.dart'; -import 'package:eatvio_app/core/utils/enums.dart'; +import 'package:eatvio_app/core/enums/product_type.dart'; import 'package:eatvio_app/core/utils/toasts.dart'; import 'package:eatvio_app/core/widgets/bottom_sheet_with_blur.dart'; import 'package:eatvio_app/core/widgets/custom_outlined_button.dart'; diff --git a/lib/features/my_plans/presentation/my_plans_screen.dart b/lib/features/my_plans/presentation/my_plans_screen.dart index efa423e0..29bf9587 100644 --- a/lib/features/my_plans/presentation/my_plans_screen.dart +++ b/lib/features/my_plans/presentation/my_plans_screen.dart @@ -4,6 +4,7 @@ import 'package:eatvio_app/core/theme/custom_text_style.dart'; import 'package:eatvio_app/core/theme/theme_helper.dart'; import 'package:eatvio_app/core/widgets/add_item_button.dart'; import 'package:eatvio_app/core/widgets/custom_search_bar.dart'; +import 'package:eatvio_app/core/widgets/data_error_widget.dart'; import 'package:eatvio_app/core/widgets/loader.dart'; import 'package:eatvio_app/features/create_plan/presentation/create_plan_screen.dart'; import 'package:eatvio_app/features/feed/presentation/widgets/filter_badge.dart'; @@ -138,15 +139,7 @@ class MyPlansScreenContent extends StatelessWidget { body: const Loader(), ); } else if (state.error != null) { - return Scaffold( - backgroundColor: Colors.grey.shade200, - body: Padding( - padding: const EdgeInsets.all(16), - child: Center( - child: Text(state.error ?? ''), - ), - ), - ); + return DataErrorWidget(onReload: () => context.read().loadPlans()); } else { final plans = switch (state.selectedStateIndex) { 1 => state.draftPlans, diff --git a/lib/features/my_plans/presentation/widgets/my_plan_widget.dart b/lib/features/my_plans/presentation/widgets/my_plan_widget.dart index 6b8bee6f..5aa7e63a 100644 --- a/lib/features/my_plans/presentation/widgets/my_plan_widget.dart +++ b/lib/features/my_plans/presentation/widgets/my_plan_widget.dart @@ -2,7 +2,7 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:eatvio_app/core/const.dart'; import 'package:eatvio_app/core/theme/custom_text_style.dart'; import 'package:eatvio_app/core/theme/theme_helper.dart'; -import 'package:eatvio_app/core/utils/enums.dart'; +import 'package:eatvio_app/core/enums/product_type.dart'; import 'package:eatvio_app/core/widgets/bottom_sheet_handler_bar.dart'; import 'package:eatvio_app/core/widgets/bottom_sheet_with_blur.dart'; import 'package:eatvio_app/core/widgets/custom_outlined_button.dart'; diff --git a/lib/features/plans/presentation/cubit/about_plan_cubit.dart b/lib/features/plans/presentation/cubit/about_plan_cubit.dart index ad60723a..20ca860b 100644 --- a/lib/features/plans/presentation/cubit/about_plan_cubit.dart +++ b/lib/features/plans/presentation/cubit/about_plan_cubit.dart @@ -17,7 +17,7 @@ class AboutPlanCubit extends Cubit { emit(state.copyWith(planModel: plan, error: null)); } catch (e, stackTrace) { log(e.toString(), stackTrace: stackTrace); - emit(state.copyWith(error: e.toString())); + emit(state.copyWith(error: e.toString(), isLoading: false)); } } diff --git a/lib/features/plans/presentation/pages/about_plan_screen.dart b/lib/features/plans/presentation/pages/about_plan_screen.dart index 5922b0ff..0022a4f2 100644 --- a/lib/features/plans/presentation/pages/about_plan_screen.dart +++ b/lib/features/plans/presentation/pages/about_plan_screen.dart @@ -2,10 +2,12 @@ import 'dart:io'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:eatvio_app/core/const.dart'; -import 'package:eatvio_app/core/utils/enums.dart'; +import 'package:eatvio_app/core/enums/complained_content.dart'; +import 'package:eatvio_app/core/enums/product_type.dart'; import 'package:eatvio_app/core/utils/localization_utils.dart'; import 'package:eatvio_app/core/utils/string_extentions.dart'; import 'package:eatvio_app/core/widgets/bookmark_button.dart'; +import 'package:eatvio_app/core/widgets/data_error_widget.dart'; import 'package:eatvio_app/core/widgets/info_row.dart'; import 'package:eatvio_app/core/widgets/loader.dart'; import 'package:eatvio_app/features/diets/presentation/widgets/author_card.dart'; @@ -94,12 +96,96 @@ class _AboutPlanScreenState extends State { builder: (BuildContext context, CommentsState commentsState) { return BlocBuilder( builder: (context, state) { - if (state.isLoading) return Scaffold(body: Loader()); + if (state.isLoading) + return Scaffold( + appBar: AppBar( + centerTitle: true, + title: Text( + widget.plan.name, + maxLines: 2, + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + softWrap: true, + style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black), + ), + actions: [ + PortalTarget( + visible: isMoreOptionsVisible, + portalFollower: MoreOptions( + complainedContentType: ComplainedContent.plan, + path: 'plan/${widget.plan.id}', + slug: widget.plan.slug, + ), + anchor: Aligned( + shiftToWithinBound: AxisFlag(x: true, y: true), + follower: Alignment.topRight, + target: Alignment.bottomRight, + ), + child: IconButton( + onPressed: () { + setState(() { + isMoreOptionsVisible = !isMoreOptionsVisible; + }); + }, + icon: SvgPicture.asset( + 'assets/icons/more_button.svg', + width: 20, + height: 20, + ), + ), + ), + ], + ), + body: Loader(), + ); WidgetsBinding.instance.addPostFrameCallback((_) { if (widget.navigateToComments && !_jumped) { _scrollToBottom(); } }); + if (state.error != null) { + return Scaffold( + appBar: AppBar( + centerTitle: true, + title: Text( + widget.plan.name, + maxLines: 2, + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + softWrap: true, + style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black), + ), + actions: [ + PortalTarget( + visible: isMoreOptionsVisible, + portalFollower: MoreOptions( + complainedContentType: ComplainedContent.plan, + path: 'plan/${widget.plan.id}', + slug: widget.plan.slug, + ), + anchor: Aligned( + shiftToWithinBound: AxisFlag(x: true, y: true), + follower: Alignment.topRight, + target: Alignment.bottomRight, + ), + child: IconButton( + onPressed: () { + setState(() { + isMoreOptionsVisible = !isMoreOptionsVisible; + }); + }, + icon: SvgPicture.asset( + 'assets/icons/more_button.svg', + width: 20, + height: 20, + ), + ), + ), + ], + ), + body: DataErrorWidget(onReload: () => context.read().getPlan(widget.plan.slug)), + ); + } return Scaffold( backgroundColor: appTheme.gray100, resizeToAvoidBottomInset: true, @@ -294,7 +380,9 @@ class _AboutPlanScreenState extends State { PortalTarget( visible: isMoreOptionsVisible, portalFollower: MoreOptions( + complainedContentType: ComplainedContent.plan, path: 'plan/${widget.plan.id}', + slug: widget.plan.slug, ), anchor: Aligned( shiftToWithinBound: AxisFlag(x: true, y: true), diff --git a/lib/features/profile/presentation/bloc/profile_cubit/profile_cubit.dart b/lib/features/profile/presentation/bloc/profile_cubit/profile_cubit.dart index ce0065c8..6b89e051 100644 --- a/lib/features/profile/presentation/bloc/profile_cubit/profile_cubit.dart +++ b/lib/features/profile/presentation/bloc/profile_cubit/profile_cubit.dart @@ -17,7 +17,7 @@ class ProfileCubit extends Cubit { required this.chatRepository, }) : super(const ProfileState.loading()); - getProfileBySlug(String slug) async { + Future getProfileBySlug(String slug) async { try { emit( ProfileState.loaded( @@ -31,7 +31,7 @@ class ProfileCubit extends Cubit { } } - getMyProfile({ + Future getMyProfile({ String period = '1', }) async { _currentPeriod = period; diff --git a/lib/features/profile/presentation/pages/profile_screen.dart b/lib/features/profile/presentation/pages/profile_screen.dart index eee9645b..7aba6e23 100644 --- a/lib/features/profile/presentation/pages/profile_screen.dart +++ b/lib/features/profile/presentation/pages/profile_screen.dart @@ -3,10 +3,13 @@ import 'dart:io'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:dismissible_page/dismissible_page.dart'; import 'package:eatvio_app/core/const.dart'; +import 'package:eatvio_app/core/enums/complained_content.dart'; import 'package:eatvio_app/core/utils/date_time_utils.dart'; +import 'package:eatvio_app/core/widgets/data_error_widget.dart'; import 'package:eatvio_app/core/widgets/loader.dart'; import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart'; import 'package:eatvio_app/features/chat/presentation/pages/photo_view_screen.dart'; +import 'package:eatvio_app/features/complaining/presentation/complaining_bottom_sheet.dart'; import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart'; import 'package:eatvio_app/features/feed/presentation/widgets/plan_widget.dart'; import 'package:eatvio_app/features/profile/models/circumference_model.dart'; @@ -88,14 +91,10 @@ class _ProfileScreenState extends State { } return widget.author == null ? _myProfileScreen(state) : _profileScreen(state); } else if (state is ProfileStateError) { - return Scaffold( - appBar: AppBar(), - body: Center( - child: Padding( - padding: const EdgeInsets.all(16), - child: Text(state.errorMessage), - ), - ), + return DataErrorWidget( + onReload: () async { + widget.author == null ? await profileCubit.getMyProfile() : await profileCubit.getProfileBySlug(widget.author!.authorSlug); + }, ); } else { return Scaffold( @@ -578,7 +577,7 @@ class _ProfileScreenState extends State { () { launchUrl( Uri.parse( - state.profile.educationResource?.vkLink ?? '', + state.profile.educationResource?.vkLink ?? '', ), mode: LaunchMode.externalApplication, ); @@ -1011,19 +1010,34 @@ class _ProfileScreenState extends State { const Spacer(), PopupMenuButton( onSelected: (value) { - final box = context.findRenderObject() as RenderBox?; - Share.shareUri( - Uri.parse( - baseUrl + '/ru/profile/' + state.profile.slug, - ), - sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size, - ); + if (value == 'share') { + final box = context.findRenderObject() as RenderBox?; + Share.shareUri( + Uri.parse( + baseUrl + '/ru/profile/' + state.profile.slug, + ), + sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size, + ); + } else { + showModalBottomSheet( + context: context, + builder: (context) => ComplainingBottomSheet( + complainedContentType: ComplainedContent.profile, + slug: widget.author?.authorSlug, + ), + ); + } }, itemBuilder: (context) => [ PopupMenuItem( value: 'share', child: Text('Поделиться'), ), + if (widget.author != null) + PopupMenuItem( + value: 'complain', + child: Text('Пожаловаться'), + ), ], child: Icon(Icons.more_horiz_rounded), ), diff --git a/lib/features/recipe/presentation/about_recipe_screen.dart b/lib/features/recipe/presentation/about_recipe_screen.dart index 64f201d9..029b9c0f 100644 --- a/lib/features/recipe/presentation/about_recipe_screen.dart +++ b/lib/features/recipe/presentation/about_recipe_screen.dart @@ -3,11 +3,13 @@ import 'dart:io'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:eatvio_app/core/const.dart'; -import 'package:eatvio_app/core/utils/enums.dart'; +import 'package:eatvio_app/core/enums/complained_content.dart'; +import 'package:eatvio_app/core/enums/product_type.dart'; import 'package:eatvio_app/core/utils/localization_utils.dart'; import 'package:eatvio_app/core/utils/string_extentions.dart'; import 'package:eatvio_app/core/utils/toasts.dart'; import 'package:eatvio_app/core/widgets/bookmark_button.dart'; +import 'package:eatvio_app/core/widgets/data_error_widget.dart'; import 'package:eatvio_app/core/widgets/info_row.dart'; import 'package:eatvio_app/core/widgets/loader.dart'; import 'package:eatvio_app/features/diets/presentation/widgets/author_card.dart'; @@ -243,7 +245,9 @@ class _AboutRecipeScreenState extends State { PortalTarget( visible: isMoreOptionsVisible, portalFollower: MoreOptions( + complainedContentType: ComplainedContent.recipe, path: 'recipe/${widget.recipe.slug}', + slug: widget.recipe.slug, ), anchor: Aligned( shiftToWithinBound: AxisFlag(x: true, y: true), @@ -460,7 +464,90 @@ class _AboutRecipeScreenState extends State { ), ); } + if (recipeState is RecipeStateError) { + return Scaffold( + appBar: AppBar( + centerTitle: true, + title: Text( + widget.recipe.title ?? '', + maxLines: 2, + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + softWrap: true, + style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black), + ), + actions: [ + PortalTarget( + visible: isMoreOptionsVisible, + portalFollower: MoreOptions( + complainedContentType: ComplainedContent.recipe, + path: 'recipe/${widget.recipe.slug}', + slug: widget.recipe.slug, + ), + anchor: Aligned( + shiftToWithinBound: AxisFlag(x: true, y: true), + follower: Alignment.topRight, + target: Alignment.bottomRight, + ), + child: IconButton( + onPressed: () { + setState(() { + isMoreOptionsVisible = !isMoreOptionsVisible; + }); + }, + icon: SvgPicture.asset( + 'assets/icons/more_button.svg', + width: 20, + height: 20, + ), + ), + ), + ], + ), + body: DataErrorWidget( + onReload: () => context.read().getRecipe(widget.recipe.slug, index: widget.index), + ), + ); + } return Scaffold( + appBar: AppBar( + centerTitle: true, + title: Text( + widget.recipe.title ?? '', + maxLines: 2, + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + softWrap: true, + style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black), + ), + actions: [ + PortalTarget( + visible: isMoreOptionsVisible, + portalFollower: MoreOptions( + complainedContentType: ComplainedContent.recipe, + path: 'recipe/${widget.recipe.slug}', + slug: widget.recipe.slug, + ), + anchor: Aligned( + shiftToWithinBound: AxisFlag(x: true, y: true), + follower: Alignment.topRight, + target: Alignment.bottomRight, + ), + child: IconButton( + onPressed: () { + setState(() { + isMoreOptionsVisible = !isMoreOptionsVisible; + }); + }, + icon: SvgPicture.asset( + 'assets/icons/more_button.svg', + width: 20, + height: 20, + ), + ), + ), + ], + ), body: const Loader(), ); }, diff --git a/lib/features/recipe/presentation/cubit/about_recipe_cubit.dart b/lib/features/recipe/presentation/cubit/about_recipe_cubit.dart index 87d12c38..24480a29 100644 --- a/lib/features/recipe/presentation/cubit/about_recipe_cubit.dart +++ b/lib/features/recipe/presentation/cubit/about_recipe_cubit.dart @@ -9,8 +9,7 @@ import 'package:eatvio_app/features/recipe/presentation/cubit/about_recipe_state import 'package:flutter_bloc/flutter_bloc.dart'; class AboutRecipeCubit extends Cubit { - AboutRecipeCubit({required this.recipeRepository, required this.profileRepository }) - : super(AboutRecipeState.initial()); + AboutRecipeCubit({required this.recipeRepository, required this.profileRepository}) : super(AboutRecipeState.initial()); final AboutRecipeRepository recipeRepository; final ProfileRepository profileRepository; bool isCurrentDietStarted = false; @@ -26,6 +25,7 @@ class AboutRecipeCubit extends Cubit { emit(AboutRecipeState.loaded(recipe: recipe)); } catch (e) { log(e.toString()); + emit(AboutRecipeState.error(message: e.toString())); } } @@ -39,6 +39,7 @@ class AboutRecipeCubit extends Cubit { emit(AboutRecipeState.loaded(recipe: recipe)); } catch (e) { log(e.toString()); + emit(AboutRecipeState.error(message: e.toString())); } } @@ -53,7 +54,8 @@ class AboutRecipeCubit extends Cubit { wasLikedByMe: false, ); if (recipeIndexInFeed != null) { - recipeRepository.emitLikesStateToFeed(LikesState(wasLikedByMe: updatedRecipe.wasLikedByMe, totalLikes: updatedRecipe.likesCount, index: recipeIndexInFeed)); + recipeRepository + .emitLikesStateToFeed(LikesState(wasLikedByMe: updatedRecipe.wasLikedByMe, totalLikes: updatedRecipe.likesCount, index: recipeIndexInFeed)); } emit(AboutRecipeState.loaded(recipe: updatedRecipe)); } else { @@ -62,7 +64,8 @@ class AboutRecipeCubit extends Cubit { wasLikedByMe: true, ); if (recipeIndexInFeed != null) { - recipeRepository.emitLikesStateToFeed(LikesState(wasLikedByMe: updatedRecipe.wasLikedByMe, totalLikes: updatedRecipe.likesCount, index: recipeIndexInFeed)); + recipeRepository + .emitLikesStateToFeed(LikesState(wasLikedByMe: updatedRecipe.wasLikedByMe, totalLikes: updatedRecipe.likesCount, index: recipeIndexInFeed)); } emit(AboutRecipeState.loaded(recipe: updatedRecipe)); } @@ -145,5 +148,5 @@ class AboutRecipeCubit extends Cubit { } }, ); - } + } } diff --git a/lib/features/settings/presentation/pages/settings_screen.dart b/lib/features/settings/presentation/pages/settings_screen.dart index bd28f36a..c7ac0a30 100644 --- a/lib/features/settings/presentation/pages/settings_screen.dart +++ b/lib/features/settings/presentation/pages/settings_screen.dart @@ -1,4 +1,5 @@ import 'package:eatvio_app/core/const.dart'; +import 'package:eatvio_app/core/widgets/data_error_widget.dart'; import 'package:eatvio_app/core/widgets/loader.dart'; import 'package:eatvio_app/features/settings/entities/settings_entity.dart'; import 'package:eatvio_app/features/settings/presentation/bloc/settings_cubit/settings_cubit.dart'; @@ -69,14 +70,17 @@ class _SettingsScreenState extends State { } if (state is SettingsStateError) { return Scaffold( - body: Padding( - padding: const EdgeInsets.all(16), - child: Center(child: Text(state.errorMessage)), + appBar: AppBar( + title: Text('Настройки'), ), + body: DataErrorWidget(onReload: () => settingsCubit.init()), ); } return Scaffold( - body: const Loader(), + appBar: AppBar( + title: Text('Настройки'), + ), + body: Loader(), ); }, ), diff --git a/lib/features/user_details/presentation/user_details_screen.dart b/lib/features/user_details/presentation/user_details_screen.dart index 2aa03504..345bc895 100644 --- a/lib/features/user_details/presentation/user_details_screen.dart +++ b/lib/features/user_details/presentation/user_details_screen.dart @@ -1,9 +1,11 @@ +import 'package:eatvio_app/core/enums/complained_content.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'; import 'package:eatvio_app/core/utils/date_time_utils.dart'; import 'package:eatvio_app/core/utils/string_extentions.dart'; import 'package:eatvio_app/core/widgets/custom_text_form_field.dart'; +import 'package:eatvio_app/core/widgets/data_error_widget.dart'; import 'package:eatvio_app/core/widgets/loader.dart'; import 'package:eatvio_app/features/diets/presentation/widgets/more_options.dart'; import 'package:eatvio_app/features/profile/models/circumference_model.dart'; @@ -79,6 +81,8 @@ class _UserDetailsContentState extends State { visible: isMoreOptionsVisible, portalFollower: MoreOptions( path: 'profile/${widget.slug}', + complainedContentType: ComplainedContent.profile, + slug: widget.slug, ), anchor: Aligned( shiftToWithinBound: AxisFlag(x: true, y: true), @@ -395,11 +399,20 @@ class _UserDetailsContentState extends State { ), ), ); - } else { + } else if (state.error != null) { return Scaffold( - body: Loader(), + appBar: AppBar(), + body: DataErrorWidget( + onReload: () async { + context.read().getUserDetails(widget.slug); + }, + ), ); } + return Scaffold( + appBar: AppBar(), + body: Loader(), + ); }, ); } diff --git a/lib/generated/intl/messages_en.dart b/lib/generated/intl/messages_en.dart index 4f013f2a..9b5fef07 100644 --- a/lib/generated/intl/messages_en.dart +++ b/lib/generated/intl/messages_en.dart @@ -28,6 +28,8 @@ class MessageLookup extends MessageLookupByLibrary { "about_plan": MessageLookupByLibrary.simpleMessage("Об этом плане"), "about_recipe": MessageLookupByLibrary.simpleMessage("Об этом рецепте"), "active": MessageLookupByLibrary.simpleMessage("Активен"), + "active_consultations_count": MessageLookupByLibrary.simpleMessage( + "Кол-во активных консультаций"), "active_diet_state": MessageLookupByLibrary.simpleMessage("Активная"), "add": MessageLookupByLibrary.simpleMessage("Добавить"), "add_cover": MessageLookupByLibrary.simpleMessage("Добавьте обложку"), @@ -36,6 +38,7 @@ class MessageLookup extends MessageLookupByLibrary { "additional_information": MessageLookupByLibrary.simpleMessage("Дополнительная информация"), "admin": MessageLookupByLibrary.simpleMessage("Админ"), + "alcohol": MessageLookupByLibrary.simpleMessage("Алкоголь"), "all": MessageLookupByLibrary.simpleMessage("Все"), "allergies_presence": MessageLookupByLibrary.simpleMessage("Наличие аллергии"), @@ -52,7 +55,12 @@ class MessageLookup extends MessageLookupByLibrary { "Средние показатели давления:"), "bodymass_chart": MessageLookupByLibrary.simpleMessage("График ИМТ"), "by_default": MessageLookupByLibrary.simpleMessage("По умолчянию"), + "calls_for_bullying": + MessageLookupByLibrary.simpleMessage("Призывы к травле"), "calories": MessageLookupByLibrary.simpleMessage("Калории"), + "can_not_load_data_please_try_again": + MessageLookupByLibrary.simpleMessage( + "Не удалось загрузить данные. Попробуйте обновить страницу"), "cancel_diet": MessageLookupByLibrary.simpleMessage("Отменить диету?"), "carb_type": MessageLookupByLibrary.simpleMessage("Мучное"), "carb_type_desc": MessageLookupByLibrary.simpleMessage( @@ -84,15 +92,40 @@ class MessageLookup extends MessageLookupByLibrary { "comments": MessageLookupByLibrary.simpleMessage("Комментарии"), "comments_plural": MessageLookupByLibrary.simpleMessage("комментариев"), "complain": MessageLookupByLibrary.simpleMessage("Пожаловаться"), + "complaint": MessageLookupByLibrary.simpleMessage("Жалоба"), + "complaint_question": MessageLookupByLibrary.simpleMessage( + "Что именно в этом материале Вы считаете неприемлемым и нарушающим правила?"), + "complaint_sent": + MessageLookupByLibrary.simpleMessage("Жалоба успешно отправлена"), + "complaint_sent_description": MessageLookupByLibrary.simpleMessage( + "Ваше обращение принято! Мы рассмотрим его в ближайшее время и примем необходимые меры. Спасибо за вашу бдительность и помощь в улучшении нашего сервиса."), "confirm": MessageLookupByLibrary.simpleMessage("Подтвердить"), + "consultations_count": MessageLookupByLibrary.simpleMessage( + "Кол-во проведенных консулдьтаций"), "continueButton": MessageLookupByLibrary.simpleMessage("Продолжить"), "continue_": MessageLookupByLibrary.simpleMessage("Продолжить"), "copied": MessageLookupByLibrary.simpleMessage("cкопирован"), "copy_link": MessageLookupByLibrary.simpleMessage("Скопировать ссылку"), + "copy_referral_link": MessageLookupByLibrary.simpleMessage( + "Скопировать пригласительную ссылку"), "could_not_cancel_diet": MessageLookupByLibrary.simpleMessage("Не удалось отменить диету"), + "could_not_delete_blog_post": + MessageLookupByLibrary.simpleMessage("Не удалось удалить пост"), "could_not_launch_video": MessageLookupByLibrary.simpleMessage("Не удалось запустить видео"), + "could_not_publish_blog_post": MessageLookupByLibrary.simpleMessage( + "Не удалось опубликовать пост"), + "could_not_publish_diet": MessageLookupByLibrary.simpleMessage( + "Не удалось опубликовать диету"), + "could_not_publish_recipe": MessageLookupByLibrary.simpleMessage( + "Не удалось опубликовать рецепт"), + "could_not_send_complain": + MessageLookupByLibrary.simpleMessage("Не удалось отправить жалобу"), + "could_not_update_blog_post": + MessageLookupByLibrary.simpleMessage("Не удалось обновить пост"), + "could_not_update_diet": + MessageLookupByLibrary.simpleMessage("Не удалось обновить диету"), "country": MessageLookupByLibrary.simpleMessage("Страна"), "create_diet": MessageLookupByLibrary.simpleMessage("Создание диеты"), "customer_price": @@ -100,6 +133,7 @@ class MessageLookup extends MessageLookupByLibrary { "date_of_birth": MessageLookupByLibrary.simpleMessage("Дата рождения"), "day": MessageLookupByLibrary.simpleMessage("День"), "days": MessageLookupByLibrary.simpleMessage("дней"), + "deception": MessageLookupByLibrary.simpleMessage("Обман"), "deleted": MessageLookupByLibrary.simpleMessage("Удалена"), "diary": MessageLookupByLibrary.simpleMessage("Дневник"), "diet": MessageLookupByLibrary.simpleMessage("Диета"), @@ -121,6 +155,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Нижние показатели давления:"), "draft": MessageLookupByLibrary.simpleMessage("Черновик"), "drafted": MessageLookupByLibrary.simpleMessage("Черновики"), + "drugs": MessageLookupByLibrary.simpleMessage("Наркотики"), "eating_behavior": MessageLookupByLibrary.simpleMessage("Пищевое поведение"), "eating_behavior_type": MessageLookupByLibrary.simpleMessage( @@ -144,6 +179,15 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Введите информацию..."), "exit": MessageLookupByLibrary.simpleMessage("Выйти"), "expand": MessageLookupByLibrary.simpleMessage("Показать полностью"), + "explicitImage": + MessageLookupByLibrary.simpleMessage("Откровенное изображение"), + "explicitImages": + MessageLookupByLibrary.simpleMessage("Откровенные изображения"), + "explicitImagesDescription": MessageLookupByLibrary.simpleMessage( + "Отправьте жалобу, если кто-то разместил откровенные изображения"), + "extremism": MessageLookupByLibrary.simpleMessage("Экстремизм"), + "fake_documents": + MessageLookupByLibrary.simpleMessage("Поддельные документы"), "fats": MessageLookupByLibrary.simpleMessage("Жиры"), "favorite_foods": MessageLookupByLibrary.simpleMessage("Любимые продукты"), @@ -162,6 +206,14 @@ class MessageLookup extends MessageLookupByLibrary { "fit_type_desc": MessageLookupByLibrary.simpleMessage( "План питания для поддержания формы сбалансированный и разнообразный, чтобы обеспечить организм всеми необходимыми питательными веществами. Общие принципы: \n● Дробное питание; \n● Разнообразие продуктов; \n● Контроль порций; \n● Питьевой режим; \n● Ограничение вредных продуктов;"), "fitness": MessageLookupByLibrary.simpleMessage("Поддержка формы"), + "for_a_month": MessageLookupByLibrary.simpleMessage("За месяц"), + "for_a_week": MessageLookupByLibrary.simpleMessage("За неделю"), + "for_a_year": MessageLookupByLibrary.simpleMessage("За год"), + "for_whole_period_of_time": + MessageLookupByLibrary.simpleMessage("За всё время"), + "fraud": MessageLookupByLibrary.simpleMessage("Мошенничество"), + "fraud_description": MessageLookupByLibrary.simpleMessage( + "Мошенничество с целью наживы. Если вас обманули, пожалуйста, сообщите об этом в правоохранительные органы."), "from": MessageLookupByLibrary.simpleMessage("от"), "from_a_to_z": MessageLookupByLibrary.simpleMessage("Название: А-Я"), "from_z_to_a": MessageLookupByLibrary.simpleMessage("Название: Я-А"), @@ -175,6 +227,9 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Склонность к гипертонии"), "has_hypo": MessageLookupByLibrary.simpleMessage("Склонность к гипотонии"), + "hatred": MessageLookupByLibrary.simpleMessage("Оскорбление"), + "hatred_description": + MessageLookupByLibrary.simpleMessage("Враждебные высказывания"), "height": MessageLookupByLibrary.simpleMessage("Рост"), "height_measurements": MessageLookupByLibrary.simpleMessage("Замер роста"), @@ -189,6 +244,8 @@ class MessageLookup extends MessageLookupByLibrary { "ingredients": MessageLookupByLibrary.simpleMessage("Ингредиенты"), "initial_measurements": MessageLookupByLibrary.simpleMessage("Первичные замеры"), + "invited_people_count": + MessageLookupByLibrary.simpleMessage("Количество приглашенных"), "label": MessageLookupByLibrary.simpleMessage("Название"), "language": MessageLookupByLibrary.simpleMessage("Язык"), "last_name": MessageLookupByLibrary.simpleMessage("Фамилия"), @@ -196,6 +253,8 @@ class MessageLookup extends MessageLookupByLibrary { "Фамилия обязательна для заполнения"), "last_name_short": MessageLookupByLibrary.simpleMessage( "В фамилии должен быть минимум 6 символов"), + "lawViolation": + MessageLookupByLibrary.simpleMessage("Нарушение законодательства"), "lbl_english": MessageLookupByLibrary.simpleMessage("English"), "light_type": MessageLookupByLibrary.simpleMessage("Легкая"), "light_type_desc": MessageLookupByLibrary.simpleMessage( @@ -217,6 +276,10 @@ class MessageLookup extends MessageLookupByLibrary { "Минимальная цена для покупателя: 1200₽"), "minute": MessageLookupByLibrary.simpleMessage("минута"), "minutes": MessageLookupByLibrary.simpleMessage("минуты"), + "misrepresentation": + MessageLookupByLibrary.simpleMessage("Введение в заблуждение"), + "misrepresentation_description": MessageLookupByLibrary.simpleMessage( + "Новости, которые могут вызвать широкий резонанс или несут угрозу, распространяемые с целью введения людей в заблуждение."), "most_new": MessageLookupByLibrary.simpleMessage("Самые новые"), "most_old": MessageLookupByLibrary.simpleMessage("Самые старые"), "msg_network_err": @@ -231,11 +294,13 @@ class MessageLookup extends MessageLookupByLibrary { "Имя пользователя обязательно для заполнения"), "name_short": MessageLookupByLibrary.simpleMessage( "В имени пользователя должно быть минимум 6 символов"), + "nicotine": MessageLookupByLibrary.simpleMessage("Никотин"), "no": MessageLookupByLibrary.simpleMessage("Нет"), "no_account": MessageLookupByLibrary.simpleMessage("У меня нет аккаунта"), "not_liked_groceries": MessageLookupByLibrary.simpleMessage("Не любимые продукты"), + "ok": MessageLookupByLibrary.simpleMessage("Хорошо"), "one_option": MessageLookupByLibrary.simpleMessage("1 опция"), "option": MessageLookupByLibrary.simpleMessage("Опция"), "option_content": @@ -251,6 +316,7 @@ class MessageLookup extends MessageLookupByLibrary { "Опция подразумевает создание индивидуальной диеты"), "option_title": MessageLookupByLibrary.simpleMessage("Заголовок опции"), "order": MessageLookupByLibrary.simpleMessage("Порядок"), + "other": MessageLookupByLibrary.simpleMessage("Другое"), "other_info": MessageLookupByLibrary.simpleMessage("Прочая информация"), "password": MessageLookupByLibrary.simpleMessage("Пароль"), "password_confirmation": @@ -280,6 +346,8 @@ class MessageLookup extends MessageLookupByLibrary { "plan_type": MessageLookupByLibrary.simpleMessage("Тип плана"), "plans_complete": MessageLookupByLibrary.simpleMessage("Пройденные планы"), + "please_try_again_later": MessageLookupByLibrary.simpleMessage( + "Пожалуйста, попробуйте еще раз позже"), "popular": MessageLookupByLibrary.simpleMessage("Популярные"), "preferred_language": MessageLookupByLibrary.simpleMessage("Предпочитаемый язык общения"), @@ -293,13 +361,28 @@ class MessageLookup extends MessageLookupByLibrary { "price_explanation": MessageLookupByLibrary.simpleMessage( "Разница в ценах, это комиссия Итвио"), "profile": MessageLookupByLibrary.simpleMessage("Профиль"), + "prostitution": MessageLookupByLibrary.simpleMessage("Проституция"), "proteins": MessageLookupByLibrary.simpleMessage("Белки"), "publish": MessageLookupByLibrary.simpleMessage("Опубликовать"), "published": MessageLookupByLibrary.simpleMessage("Опубликованные"), + "published_diets_count": + MessageLookupByLibrary.simpleMessage("Кол-во опубликованных диет"), + "published_posts_count": MessageLookupByLibrary.simpleMessage( + "Количество опубликованных постов"), + "published_recipes_count": MessageLookupByLibrary.simpleMessage( + "Кол-во опубликованных рецептов"), + "rating_and_statistics": + MessageLookupByLibrary.simpleMessage("Рейтинг и статистика"), + "rating_history": + MessageLookupByLibrary.simpleMessage("Начисление рейтинга"), "recipe_author": MessageLookupByLibrary.simpleMessage("Автор рецепта"), + "recipe_publishing": + MessageLookupByLibrary.simpleMessage("Публикация рецепта"), "removeFromActive": MessageLookupByLibrary.simpleMessage("Снять с публикации"), "save": MessageLookupByLibrary.simpleMessage("Сохранить"), + "send_complain": + MessageLookupByLibrary.simpleMessage("Отправить жалобу"), "services": MessageLookupByLibrary.simpleMessage("Оказание услуг"), "settings": MessageLookupByLibrary.simpleMessage("Настройки"), "sex": MessageLookupByLibrary.simpleMessage("Пол"), @@ -309,15 +392,29 @@ class MessageLookup extends MessageLookupByLibrary { "slim_type": MessageLookupByLibrary.simpleMessage("Похудение"), "slim_type_desc": MessageLookupByLibrary.simpleMessage( "План питания для похудения должен быть разработан с учётом индивидуальных особенностей организма, возраста, пола, уровня физической активности и состояния здоровья. Общие принципы: \n● Снижение калорийности; \n● Сбалансированный рацион; \n● Разнообразие продуктов; \n● Питьевой режим; \n● Ограничение вредных продуктов;"), + "some_error_occurred": + MessageLookupByLibrary.simpleMessage("Упс! Произошла ошибка"), + "some_error_occurred_description": MessageLookupByLibrary.simpleMessage( + "Произошла ошибка. Мы уже работаем над ее устранением. Попробуйте позже."), + "something_went_wrong": + MessageLookupByLibrary.simpleMessage("Упс! Что-то пошло не так"), + "spam": MessageLookupByLibrary.simpleMessage("Спам"), + "spam_description": MessageLookupByLibrary.simpleMessage( + "Отправьте жалобу, если Вы заметили рекламу, размещенную в не предназначенном для этого месте."), "sport_hobbies": MessageLookupByLibrary.simpleMessage("Спортивные увлечения"), "stage": MessageLookupByLibrary.simpleMessage("этап"), "stages": MessageLookupByLibrary.simpleMessage("этапов"), "standard": MessageLookupByLibrary.simpleMessage("Стандарт"), "startDiet": MessageLookupByLibrary.simpleMessage("Начать диету"), + "statistic": MessageLookupByLibrary.simpleMessage("Статистика"), "step": MessageLookupByLibrary.simpleMessage("Шаг"), "step_by_step_photo_recipe": MessageLookupByLibrary.simpleMessage("Пошаговый фоторецепт"), + "successfully_published": + MessageLookupByLibrary.simpleMessage("успешно опубликован"), + "suicide": + MessageLookupByLibrary.simpleMessage("Склонение к самоубийству"), "takes_pills": MessageLookupByLibrary.simpleMessage("Принимает лекарства"), "tell_your_schedule": MessageLookupByLibrary.simpleMessage( @@ -327,6 +424,9 @@ class MessageLookup extends MessageLookupByLibrary { "time": MessageLookupByLibrary.simpleMessage("Время прохождения"), "toOffer": MessageLookupByLibrary.simpleMessage("Предложить"), "to_draft": MessageLookupByLibrary.simpleMessage("В черновик"), + "try_again": MessageLookupByLibrary.simpleMessage("Попробовать снова"), + "type_here_your_complaint": + MessageLookupByLibrary.simpleMessage("Напишите здесь свою жалобу"), "unfavorite_foods": MessageLookupByLibrary.simpleMessage("Нелюбимые продукты"), "up_pressure": MessageLookupByLibrary.simpleMessage( @@ -343,9 +443,15 @@ class MessageLookup extends MessageLookupByLibrary { "vegan_type": MessageLookupByLibrary.simpleMessage("Вегетарианская"), "vegan_type_desc": MessageLookupByLibrary.simpleMessage( "Диета основана на потреблении растительной пищи и отказе от продуктов животного происхождения. Такой тип питания может быть разнообразным и вкусным, а также полезным для здоровья."), + "violenceAndHostility": + MessageLookupByLibrary.simpleMessage("Насилие и вражда"), + "violence_against_people_or_animal": + MessageLookupByLibrary.simpleMessage( + "Насилие над людьми или животными"), "waist": MessageLookupByLibrary.simpleMessage("Талия"), "waist_circumference": MessageLookupByLibrary.simpleMessage("Обхват талии"), + "weapon": MessageLookupByLibrary.simpleMessage("Оружие"), "weight": MessageLookupByLibrary.simpleMessage("Вес"), "weight_chart": MessageLookupByLibrary.simpleMessage("График веса"), "weight_measurements": @@ -356,6 +462,9 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Улучшение самочувствия"), "wellness_type_desc": MessageLookupByLibrary.simpleMessage( "Если вы любите мучные продукты, но хотите придерживаться диеты, вам подходит диета с добавленим мучных блюд на основе цельнозерновых вариантов. В них больше сложных углеводов и клетчатки, что способствует длительному чувству сытости и поддерживает здоровье пищеварительной системы."), + "what_do_you_want_to_complaint_about": + MessageLookupByLibrary.simpleMessage( + "На что Вы хотите пожаловаться?"), "what_is_your_name": MessageLookupByLibrary.simpleMessage("Как вас зовут?"), "wrong_credentials": MessageLookupByLibrary.simpleMessage( @@ -367,6 +476,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Ваши спортивные увлечения?"), "your_gender": MessageLookupByLibrary.simpleMessage("Ваш пол?"), "your_height": MessageLookupByLibrary.simpleMessage("Ваш рост:"), + "your_rating": MessageLookupByLibrary.simpleMessage("Ваш рейтинг"), "your_schedule": MessageLookupByLibrary.simpleMessage("Ваш режим дня?"), "your_weight": MessageLookupByLibrary.simpleMessage("Ваш вес:") }; diff --git a/lib/generated/l10n.dart b/lib/generated/l10n.dart index 80b1d82f..1bca8570 100644 --- a/lib/generated/l10n.dart +++ b/lib/generated/l10n.dart @@ -2549,6 +2549,636 @@ class S { args: [], ); } + + /// `Спам` + String get spam { + return Intl.message( + 'Спам', + name: 'spam', + desc: '', + args: [], + ); + } + + /// `Нарушение законодательства` + String get lawViolation { + return Intl.message( + 'Нарушение законодательства', + name: 'lawViolation', + desc: '', + args: [], + ); + } + + /// `Обман` + String get deception { + return Intl.message( + 'Обман', + name: 'deception', + desc: '', + args: [], + ); + } + + /// `Насилие и вражда` + String get violenceAndHostility { + return Intl.message( + 'Насилие и вражда', + name: 'violenceAndHostility', + desc: '', + args: [], + ); + } + + /// `Откровенное изображение` + String get explicitImage { + return Intl.message( + 'Откровенное изображение', + name: 'explicitImage', + desc: '', + args: [], + ); + } + + /// `Откровенные изображения` + String get explicitImages { + return Intl.message( + 'Откровенные изображения', + name: 'explicitImages', + desc: '', + args: [], + ); + } + + /// `Отправьте жалобу, если кто-то разместил откровенные изображения` + String get explicitImagesDescription { + return Intl.message( + 'Отправьте жалобу, если кто-то разместил откровенные изображения', + name: 'explicitImagesDescription', + desc: '', + args: [], + ); + } + + /// `Жалоба` + String get complaint { + return Intl.message( + 'Жалоба', + name: 'complaint', + desc: '', + args: [], + ); + } + + /// `Что именно в этом материале Вы считаете неприемлемым и нарушающим правила?` + String get complaint_question { + return Intl.message( + 'Что именно в этом материале Вы считаете неприемлемым и нарушающим правила?', + name: 'complaint_question', + desc: '', + args: [], + ); + } + + /// `Отправьте жалобу, если Вы заметили рекламу, размещенную в не предназначенном для этого месте.` + String get spam_description { + return Intl.message( + 'Отправьте жалобу, если Вы заметили рекламу, размещенную в не предназначенном для этого месте.', + name: 'spam_description', + desc: '', + args: [], + ); + } + + /// `На что Вы хотите пожаловаться?` + String get what_do_you_want_to_complaint_about { + return Intl.message( + 'На что Вы хотите пожаловаться?', + name: 'what_do_you_want_to_complaint_about', + desc: '', + args: [], + ); + } + + /// `Отправить жалобу` + String get send_complain { + return Intl.message( + 'Отправить жалобу', + name: 'send_complain', + desc: '', + args: [], + ); + } + + /// `Жалоба успешно отправлена` + String get complaint_sent { + return Intl.message( + 'Жалоба успешно отправлена', + name: 'complaint_sent', + desc: '', + args: [], + ); + } + + /// `Ваше обращение принято! Мы рассмотрим его в ближайшее время и примем необходимые меры. Спасибо за вашу бдительность и помощь в улучшении нашего сервиса.` + String get complaint_sent_description { + return Intl.message( + 'Ваше обращение принято! Мы рассмотрим его в ближайшее время и примем необходимые меры. Спасибо за вашу бдительность и помощь в улучшении нашего сервиса.', + name: 'complaint_sent_description', + desc: '', + args: [], + ); + } + + /// `Хорошо` + String get ok { + return Intl.message( + 'Хорошо', + name: 'ok', + desc: '', + args: [], + ); + } + + /// `Алкоголь` + String get alcohol { + return Intl.message( + 'Алкоголь', + name: 'alcohol', + desc: '', + args: [], + ); + } + + /// `Наркотики` + String get drugs { + return Intl.message( + 'Наркотики', + name: 'drugs', + desc: '', + args: [], + ); + } + + /// `Никотин` + String get nicotine { + return Intl.message( + 'Никотин', + name: 'nicotine', + desc: '', + args: [], + ); + } + + /// `Оружие` + String get weapon { + return Intl.message( + 'Оружие', + name: 'weapon', + desc: '', + args: [], + ); + } + + /// `Поддельные документы` + String get fake_documents { + return Intl.message( + 'Поддельные документы', + name: 'fake_documents', + desc: '', + args: [], + ); + } + + /// `Проституция` + String get prostitution { + return Intl.message( + 'Проституция', + name: 'prostitution', + desc: '', + args: [], + ); + } + + /// `Другое` + String get other { + return Intl.message( + 'Другое', + name: 'other', + desc: '', + args: [], + ); + } + + /// `Введение в заблуждение` + String get misrepresentation { + return Intl.message( + 'Введение в заблуждение', + name: 'misrepresentation', + desc: '', + args: [], + ); + } + + /// `Новости, которые могут вызвать широкий резонанс или несут угрозу, распространяемые с целью введения людей в заблуждение.` + String get misrepresentation_description { + return Intl.message( + 'Новости, которые могут вызвать широкий резонанс или несут угрозу, распространяемые с целью введения людей в заблуждение.', + name: 'misrepresentation_description', + desc: '', + args: [], + ); + } + + /// `Мошенничество` + String get fraud { + return Intl.message( + 'Мошенничество', + name: 'fraud', + desc: '', + args: [], + ); + } + + /// `Мошенничество с целью наживы. Если вас обманули, пожалуйста, сообщите об этом в правоохранительные органы.` + String get fraud_description { + return Intl.message( + 'Мошенничество с целью наживы. Если вас обманули, пожалуйста, сообщите об этом в правоохранительные органы.', + name: 'fraud_description', + desc: '', + args: [], + ); + } + + /// `Насилие над людьми или животными` + String get violence_against_people_or_animal { + return Intl.message( + 'Насилие над людьми или животными', + name: 'violence_against_people_or_animal', + desc: '', + args: [], + ); + } + + /// `Оскорбление` + String get hatred { + return Intl.message( + 'Оскорбление', + name: 'hatred', + desc: '', + args: [], + ); + } + + /// `Склонение к самоубийству` + String get suicide { + return Intl.message( + 'Склонение к самоубийству', + name: 'suicide', + desc: '', + args: [], + ); + } + + /// `Враждебные высказывания` + String get hatred_description { + return Intl.message( + 'Враждебные высказывания', + name: 'hatred_description', + desc: '', + args: [], + ); + } + + /// `Экстремизм` + String get extremism { + return Intl.message( + 'Экстремизм', + name: 'extremism', + desc: '', + args: [], + ); + } + + /// `Призывы к травле` + String get calls_for_bullying { + return Intl.message( + 'Призывы к травле', + name: 'calls_for_bullying', + desc: '', + args: [], + ); + } + + /// `Напишите здесь свою жалобу` + String get type_here_your_complaint { + return Intl.message( + 'Напишите здесь свою жалобу', + name: 'type_here_your_complaint', + desc: '', + args: [], + ); + } + + /// `Не удалось отправить жалобу` + String get could_not_send_complain { + return Intl.message( + 'Не удалось отправить жалобу', + name: 'could_not_send_complain', + desc: '', + args: [], + ); + } + + /// `Пожалуйста, попробуйте еще раз позже` + String get please_try_again_later { + return Intl.message( + 'Пожалуйста, попробуйте еще раз позже', + name: 'please_try_again_later', + desc: '', + args: [], + ); + } + + /// `Рейтинг и статистика` + String get rating_and_statistics { + return Intl.message( + 'Рейтинг и статистика', + name: 'rating_and_statistics', + desc: '', + args: [], + ); + } + + /// `Ваш рейтинг` + String get your_rating { + return Intl.message( + 'Ваш рейтинг', + name: 'your_rating', + desc: '', + args: [], + ); + } + + /// `Скопировать пригласительную ссылку` + String get copy_referral_link { + return Intl.message( + 'Скопировать пригласительную ссылку', + name: 'copy_referral_link', + desc: '', + args: [], + ); + } + + /// `Статистика` + String get statistic { + return Intl.message( + 'Статистика', + name: 'statistic', + desc: '', + args: [], + ); + } + + /// `За всё время` + String get for_whole_period_of_time { + return Intl.message( + 'За всё время', + name: 'for_whole_period_of_time', + desc: '', + args: [], + ); + } + + /// `За год` + String get for_a_year { + return Intl.message( + 'За год', + name: 'for_a_year', + desc: '', + args: [], + ); + } + + /// `За месяц` + String get for_a_month { + return Intl.message( + 'За месяц', + name: 'for_a_month', + desc: '', + args: [], + ); + } + + /// `За неделю` + String get for_a_week { + return Intl.message( + 'За неделю', + name: 'for_a_week', + desc: '', + args: [], + ); + } + + /// `Количество приглашенных` + String get invited_people_count { + return Intl.message( + 'Количество приглашенных', + name: 'invited_people_count', + desc: '', + args: [], + ); + } + + /// `Количество опубликованных постов` + String get published_posts_count { + return Intl.message( + 'Количество опубликованных постов', + name: 'published_posts_count', + desc: '', + args: [], + ); + } + + /// `Кол-во опубликованных рецептов` + String get published_recipes_count { + return Intl.message( + 'Кол-во опубликованных рецептов', + name: 'published_recipes_count', + desc: '', + args: [], + ); + } + + /// `Кол-во опубликованных диет` + String get published_diets_count { + return Intl.message( + 'Кол-во опубликованных диет', + name: 'published_diets_count', + desc: '', + args: [], + ); + } + + /// `Кол-во проведенных консулдьтаций` + String get consultations_count { + return Intl.message( + 'Кол-во проведенных консулдьтаций', + name: 'consultations_count', + desc: '', + args: [], + ); + } + + /// `Кол-во активных консультаций` + String get active_consultations_count { + return Intl.message( + 'Кол-во активных консультаций', + name: 'active_consultations_count', + desc: '', + args: [], + ); + } + + /// `Начисление рейтинга` + String get rating_history { + return Intl.message( + 'Начисление рейтинга', + name: 'rating_history', + desc: '', + args: [], + ); + } + + /// `Упс! Что-то пошло не так` + String get something_went_wrong { + return Intl.message( + 'Упс! Что-то пошло не так', + name: 'something_went_wrong', + desc: '', + args: [], + ); + } + + /// `Не удалось загрузить данные. Попробуйте обновить страницу` + String get can_not_load_data_please_try_again { + return Intl.message( + 'Не удалось загрузить данные. Попробуйте обновить страницу', + name: 'can_not_load_data_please_try_again', + desc: '', + args: [], + ); + } + + /// `Попробовать снова` + String get try_again { + return Intl.message( + 'Попробовать снова', + name: 'try_again', + desc: '', + args: [], + ); + } + + /// `Публикация рецепта` + String get recipe_publishing { + return Intl.message( + 'Публикация рецепта', + name: 'recipe_publishing', + desc: '', + args: [], + ); + } + + /// `успешно опубликован` + String get successfully_published { + return Intl.message( + 'успешно опубликован', + name: 'successfully_published', + desc: '', + args: [], + ); + } + + /// `Упс! Произошла ошибка` + String get some_error_occurred { + return Intl.message( + 'Упс! Произошла ошибка', + name: 'some_error_occurred', + desc: '', + args: [], + ); + } + + /// `Произошла ошибка. Мы уже работаем над ее устранением. Попробуйте позже.` + String get some_error_occurred_description { + return Intl.message( + 'Произошла ошибка. Мы уже работаем над ее устранением. Попробуйте позже.', + name: 'some_error_occurred_description', + desc: '', + args: [], + ); + } + + /// `Не удалось опубликовать рецепт` + String get could_not_publish_recipe { + return Intl.message( + 'Не удалось опубликовать рецепт', + name: 'could_not_publish_recipe', + desc: '', + args: [], + ); + } + + /// `Не удалось опубликовать диету` + String get could_not_publish_diet { + return Intl.message( + 'Не удалось опубликовать диету', + name: 'could_not_publish_diet', + desc: '', + args: [], + ); + } + + /// `Не удалось обновить диету` + String get could_not_update_diet { + return Intl.message( + 'Не удалось обновить диету', + name: 'could_not_update_diet', + desc: '', + args: [], + ); + } + + /// `Не удалось опубликовать пост` + String get could_not_publish_blog_post { + return Intl.message( + 'Не удалось опубликовать пост', + name: 'could_not_publish_blog_post', + desc: '', + args: [], + ); + } + + /// `Не удалось обновить пост` + String get could_not_update_blog_post { + return Intl.message( + 'Не удалось обновить пост', + name: 'could_not_update_blog_post', + desc: '', + args: [], + ); + } + + /// `Не удалось удалить пост` + String get could_not_delete_blog_post { + return Intl.message( + 'Не удалось удалить пост', + name: 'could_not_delete_blog_post', + desc: '', + args: [], + ); + } } class AppLocalizationDelegate extends LocalizationsDelegate { diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 3145312f..c976ed0e 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -248,5 +248,69 @@ "ingredients": "Ингредиенты", "step": "Шаг", "step_by_step_photo_recipe": "Пошаговый фоторецепт", - "diet_has_been_sent_to_draft": "снята с публикации и перенесена в черновик" + "diet_has_been_sent_to_draft": "снята с публикации и перенесена в черновик", + "spam": "Спам", + "lawViolation": "Нарушение законодательства", + "deception": "Обман", + "violenceAndHostility": "Насилие и вражда", + "explicitImage": "Откровенное изображение", + "explicitImages": "Откровенные изображения", + "explicitImagesDescription": "Отправьте жалобу, если кто-то разместил откровенные изображения", + "complaint": "Жалоба", + "complaint_question": "Что именно в этом материале Вы считаете неприемлемым и нарушающим правила?", + "spam_description": "Отправьте жалобу, если Вы заметили рекламу, размещенную в не предназначенном для этого месте.", + "what_do_you_want_to_complaint_about": "На что Вы хотите пожаловаться?", + "send_complain": "Отправить жалобу", + "complaint_sent": "Жалоба успешно отправлена", + "complaint_sent_description": "Ваше обращение принято! Мы рассмотрим его в ближайшее время и примем необходимые меры. Спасибо за вашу бдительность и помощь в улучшении нашего сервиса.", + "ok": "Хорошо", + "alcohol": "Алкоголь", + "drugs": "Наркотики", + "nicotine": "Никотин", + "weapon": "Оружие", + "fake_documents": "Поддельные документы", + "prostitution": "Проституция", + "other": "Другое", + "misrepresentation": "Введение в заблуждение", + "misrepresentation_description": "Новости, которые могут вызвать широкий резонанс или несут угрозу, распространяемые с целью введения людей в заблуждение.", + "fraud": "Мошенничество", + "fraud_description": "Мошенничество с целью наживы. Если вас обманули, пожалуйста, сообщите об этом в правоохранительные органы.", + "violence_against_people_or_animal": "Насилие над людьми или животными", + "hatred": "Оскорбление", + "suicide": "Склонение к самоубийству", + "hatred_description": "Враждебные высказывания", + "extremism": "Экстремизм", + "calls_for_bullying": "Призывы к травле", + "type_here_your_complaint": "Напишите здесь свою жалобу", + "could_not_send_complain": "Не удалось отправить жалобу", + "please_try_again_later": "Пожалуйста, попробуйте еще раз позже", + "rating_and_statistics": "Рейтинг и статистика", + "your_rating": "Ваш рейтинг", + "copy_referral_link": "Скопировать пригласительную ссылку", + "statistic": "Статистика", + "for_whole_period_of_time": "За всё время", + "for_a_year": "За год", + "for_a_month": "За месяц", + "for_a_week": "За неделю", + "invited_people_count": "Количество приглашенных", + "published_posts_count": "Количество опубликованных постов", + "published_recipes_count": "Кол-во опубликованных рецептов", + "published_diets_count": "Кол-во опубликованных диет", + "consultations_count": "Кол-во проведенных консулдьтаций", + "active_consultations_count": "Кол-во активных консультаций", + "rating_history": "Начисление рейтинга", + "something_went_wrong": "Упс! Что-то пошло не так", + "can_not_load_data_please_try_again": "Не удалось загрузить данные. Попробуйте обновить страницу", + "try_again": "Попробовать снова", + "recipe_publishing": "Публикация рецепта", + "successfully_published": "успешно опубликован", + "some_error_occurred": "Упс! Произошла ошибка", + "some_error_occurred_description": "Произошла ошибка. Мы уже работаем над ее устранением. Попробуйте позже.", + "could_not_publish_recipe": "Не удалось опубликовать рецепт", + "could_not_publish_diet": "Не удалось опубликовать диету", + "could_not_update_diet": "Не удалось обновить диету", + "could_not_publish_blog_post": "Не удалось опубликовать пост", + "could_not_update_blog_post": "Не удалось обновить пост", + "could_not_delete_blog_post": "Не удалось удалить пост" } +