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