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/play_icon.svg b/assets/icons/play_icon.svg
new file mode 100644
index 00000000..d12618a2
--- /dev/null
+++ b/assets/icons/play_icon.svg
@@ -0,0 +1,3 @@
+
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 94450f4f..135b465e 100644
--- a/lib/core/app_router.dart
+++ b/lib/core/app_router.dart
@@ -1,4 +1,4 @@
-import 'package:eatvio_app/core/initialization_cubit/ws_manager.dart';
+import 'package:eatvio_app/core/websockets/ws_manager.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/token_storage.dart';
import 'package:eatvio_app/features/auth/presentation/pages/auth_screen.dart';
@@ -8,16 +8,17 @@ import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/prof
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_state.dart';
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/widgets/bottom_navigation_bar_widget.dart';
import 'package:eatvio_app/features/calendar/presentation/pages/calendar_screen.dart';
+import 'package:eatvio_app/features/chat/presentation/bloc/chats_cubit/chats_cubit.dart';
import 'package:eatvio_app/features/chat/presentation/pages/chat_screen.dart';
-import 'package:eatvio_app/features/chat/presentation/pages/chat_select_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';
@@ -34,8 +35,8 @@ import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart';
import 'package:eatvio_app/features/plans/presentation/pages/about_plan_screen.dart';
import 'package:eatvio_app/features/profile/presentation/pages/profile_screen.dart';
import 'package:eatvio_app/features/profile/presentation/pages/subs_screen.dart';
-import 'package:eatvio_app/features/recipes/presentation/pages/about_recipes_screen.dart';
-import 'package:eatvio_app/features/recipes/presentation/pages/recipe_create_screen.dart';
+import 'package:eatvio_app/features/recipe/presentation/about_recipe_screen.dart';
+import 'package:eatvio_app/features/create_recipe/presentation/pages/recipe_create_screen.dart';
import 'package:eatvio_app/features/registration/presentation/pages/registration_screen.dart';
import 'package:eatvio_app/features/registration/presentation/pages/webview_registration_screen.dart';
import 'package:eatvio_app/features/settings/entities/settings_entity.dart';
@@ -64,6 +65,8 @@ class AppRouter {
path: '/auth-screen',
name: 'authScreen',
redirect: (context, state) async {
+ // TokenStorage.removeTokenInStorage();
+ // await TokenStorage.saveTokenInStorage('Bearer 3|laravel_sanctum_zoubsO9NPXAqgLZEdHV9R3XbuDUHJJMJyXcd77mhbb649f04');
if (await TokenStorage.haveTokenInStorage()) {
return '/my-profile-screen';
}
@@ -125,7 +128,7 @@ class AppRouter {
GoRoute(
path: '/chat-select-screen',
name: 'chatSelectScreen',
- pageBuilder: (context, state) => NoTransitionPage(child: ChatSelectScreen()),
+ pageBuilder: (context, state) => NoTransitionPage(child: ChatsScreen()),
),
GoRoute(
path: '/notifications-screen',
@@ -159,9 +162,14 @@ class AppRouter {
GoRoute(
path: '/about-recipes-screen',
name: 'aboutRecipesScreen',
- builder: (context, state) => AboutRecipesScreen(
- recipes: state.extra as Recipes,
- ),
+ builder: (context, state) {
+ final data = state.extra as Map;
+ return AboutRecipeScreen(
+ recipe: data['recipe'] as Recipe,
+ index: data['index'] as int?,
+ navigateToComments: data['navigateToComments'] as bool,
+ );
+ },
),
GoRoute(
path: RecipeCreateScreen.path,
@@ -212,7 +220,15 @@ class AppRouter {
GoRoute(
path: '/chat-screen',
name: 'chatScreen',
- builder: (context, state) => ChatScreen(chatId: state.extra as String),
+ builder: (context, state) {
+ final data = state.extra as Map;
+ final chatsCubit = data['chatsCubit'] as ChatsCubit;
+ final chatId = data['chatId'] as String;
+ return ChatScreen(
+ chatId: chatId,
+ chatsCubit: chatsCubit,
+ );
+ },
),
GoRoute(
path: '/settings-screen',
@@ -298,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,
@@ -348,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/const.dart b/lib/core/const.dart
index dfeae1fc..4e8b3650 100644
--- a/lib/core/const.dart
+++ b/lib/core/const.dart
@@ -1,5 +1,5 @@
-String baseUrl = 'https://eatvio.ru';
-// String baseUrl = 'https://dev2.eatvio.com';
+// String baseUrl = 'https://eatvio.ru';
+String baseUrl = 'https://dev2.eatvio.com';
const List durationValues = [7, 14, 21];
diff --git a/lib/core/enums/base_state.dart b/lib/core/enums/base_state.dart
new file mode 100644
index 00000000..9a65b9e5
--- /dev/null
+++ b/lib/core/enums/base_state.dart
@@ -0,0 +1,6 @@
+enum BaseState {
+ loading,
+ error,
+ initial,
+ success
+}
\ No newline at end of file
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/initialization_cubit/initialization_cubit.dart b/lib/core/initialization_cubit/initialization_cubit.dart
deleted file mode 100644
index e69de29b..00000000
diff --git a/lib/core/locator_service.dart b/lib/core/locator_service.dart
index 4dbfd90e..f5caadfb 100644
--- a/lib/core/locator_service.dart
+++ b/lib/core/locator_service.dart
@@ -1,10 +1,13 @@
import 'package:dio/dio.dart';
import 'package:eatvio_app/core/const.dart';
-import 'package:eatvio_app/core/initialization_cubit/ws_manager.dart';
-import 'package:eatvio_app/core/initialization_cubit/ws_service.dart';
+import 'package:eatvio_app/core/websockets/ws_manager.dart';
+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';
@@ -21,7 +24,7 @@ import 'package:eatvio_app/features/calendar/presentation/bloc/calendar_cubit.da
import 'package:eatvio_app/features/calendar/repositories/calendar_repository.dart';
import 'package:eatvio_app/features/calendar/services/calendar_service.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_select_cubit/chat_select_cubit.dart';
+import 'package:eatvio_app/features/chat/presentation/bloc/chats_cubit/chats_cubit.dart';
import 'package:eatvio_app/features/chat/repositories/chat_repository.dart';
import 'package:eatvio_app/features/chat/services/chat_service.dart';
import 'package:eatvio_app/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_cubit.dart';
@@ -57,9 +60,12 @@ import 'package:eatvio_app/features/profile/presentation/bloc/profile_cubit/prof
import 'package:eatvio_app/features/profile/presentation/bloc/sub_cubit/sub_cubit.dart';
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
import 'package:eatvio_app/features/profile/services/profile_service.dart';
-import 'package:eatvio_app/features/recipes/presentation/bloc/create_recipe_cubit/create_recipe_cubit.dart';
-import 'package:eatvio_app/features/recipes/reporitories/recipe_repository.dart';
-import 'package:eatvio_app/features/recipes/services/recipe_service.dart';
+import 'package:eatvio_app/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit.dart';
+import 'package:eatvio_app/features/create_recipe/reporitories/recipe_repository.dart';
+import 'package:eatvio_app/features/create_recipe/services/recipe_service.dart';
+import 'package:eatvio_app/features/recipe/presentation/cubit/about_recipe_cubit.dart';
+import 'package:eatvio_app/features/recipe/repositories/about_recipe_repository.dart';
+import 'package:eatvio_app/features/recipe/services/about_recipe_service.dart';
import 'package:eatvio_app/features/registration/presentation/bloc/registration_cubit/registration_cubit.dart';
import 'package:eatvio_app/features/registration/repositories/registration_repository.dart';
import 'package:eatvio_app/features/registration/services/registration_service.dart';
@@ -84,13 +90,13 @@ Future init() async {
// Cubit factories
sl.registerFactory(() => RegistrationCubit(registrationRepository: sl()));
sl.registerFactory(() => ChatRoomCubit(chatRepository: sl(), wsManager: sl()));
- sl.registerFactory(() => RecipesCubit(feedRepository: sl(), recipeRepository: sl()));
+ sl.registerFactory(() => RecipesCubit(feedRepository: sl(), recipeRepository: sl(), aboutRecipeRepository: sl()));
sl.registerFactory(() => CalendarCubit(calendarRepository: sl()));
sl.registerFactory(() => ProfileCubit(profileRepository: sl(), chatRepository: sl()));
sl.registerFactory(() => profilePlans.PlansCubit(plansRepository: sl()));
sl.registerFactory(() => DietsCubit(feedRepository: sl(), dietsRepository: sl()));
sl.registerFactory(() => CommentsCubit(feedRepository: sl()));
- sl.registerFactory(() => ChatSelectCubit(chatRepository: sl()));
+ sl.registerFactory(() => ChatsCubit(chatRepository: sl(), wsManager: sl(), profileRepository: sl()));
sl.registerFactory(() => PlansCubit(feedRepository: sl(), planRepository: sl()));
sl.registerFactory(() => SettingsCubit(profileRepository: sl(), settingsRepository: sl()));
sl.registerFactory(() => RegisterConsultantCubit(consultantRepository: sl()));
@@ -103,11 +109,13 @@ Future init() async {
sl.registerFactory(() => FeedProfileCubit(profileRepository: sl()));
sl.registerFactory(() => AboutPlanCubit(planRepository: sl()));
sl.registerFactory(() => CreateRecipeCubit(recipeRepository: sl()));
- sl.registerFactory(() => MyDietsCubit(myDietsRepository: sl()));
+ sl.registerFactory(() => MyDietsCubit(myDietsRepository: sl(), createDietRepository: sl()));
sl.registerFactory(() => MyPlansCubit(myPlansRepository: sl()));
sl.registerFactory(() => UserDetailsCubit(profileRepository: sl()));
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()));
@@ -127,6 +135,8 @@ Future init() async {
sl.registerLazySingleton(() => PlanRepository(planService: sl()));
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
@@ -135,7 +145,7 @@ Future init() async {
sl.registerLazySingleton(() => RegistrationService(sl()));
sl.registerLazySingleton(() => CalendarService(sl()));
sl.registerLazySingleton(() => ProfileService(sl()));
- sl.registerLazySingleton(() => RecipeService(sl()));
+ sl.registerLazySingleton(() => CreateRecipeService(sl()));
sl.registerLazySingleton(() => SettingsService(sl()));
sl.registerLazySingleton(() => ConsultantService(sl()));
sl.registerLazySingleton(() => DiaryService(sl()));
@@ -148,6 +158,8 @@ Future init() async {
sl.registerLazySingleton(() => MyDietsService(sl()));
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 6837e186..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,
@@ -230,10 +236,10 @@ abstract class TextStyles {
fontSize: 12,
fontWeight: FontWeight.w500,
height: 1.255,
- );
+ );
static const textFS10FW500 = TextStyle(
- fontSize: 12,
+ fontSize: 10,
fontWeight: FontWeight.w500,
height: 1.255,
);
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/utils/localization_utils.dart b/lib/core/utils/localization_utils.dart
index 23d846a0..2070f626 100644
--- a/lib/core/utils/localization_utils.dart
+++ b/lib/core/utils/localization_utils.dart
@@ -41,3 +41,33 @@ String getCommentForm(int number, S s) {
return s.comments_plural;
}
}
+
+String getMinuteForm(int number) {
+ if (number % 10 == 1 && number % 100 != 11) {
+ return S.current.minute;
+ } else if ([2, 3, 4].contains(number % 10) && ![12, 13, 14].contains(number % 100)) {
+ return S.current.minutes;
+ } else {
+ return S.current.another_form_of_minute;
+ }
+}
+
+String getHourForm(int number) {
+ if (number % 10 == 1 && number % 100 != 11) {
+ return S.current.hour;
+ } else if ([2, 3, 4].contains(number % 10) && ![12, 13, 14].contains(number % 100)) {
+ return S.current.another_form_of_hour;
+ } else {
+ return S.current.hours;
+ }
+}
+
+String getStepForm(int number) {
+ if (number % 10 == 1 && number % 100 != 11) {
+ return S.current.stage;
+ } else if ([2, 3, 4].contains(number % 10) && ![12, 13, 14].contains(number % 100)) {
+ return S.current.another_form_of_stage;
+ } else {
+ return S.current.stages;
+ }
+}
diff --git a/lib/core/initialization_cubit/ws_manager.dart b/lib/core/websockets/ws_manager.dart
similarity index 98%
rename from lib/core/initialization_cubit/ws_manager.dart
rename to lib/core/websockets/ws_manager.dart
index 3a72512e..e414557d 100644
--- a/lib/core/initialization_cubit/ws_manager.dart
+++ b/lib/core/websockets/ws_manager.dart
@@ -2,7 +2,7 @@ import 'dart:developer';
import 'package:dart_pusher_channels/dart_pusher_channels.dart';
import 'package:eatvio_app/core/const.dart';
-import 'package:eatvio_app/core/initialization_cubit/ws_service.dart';
+import 'package:eatvio_app/core/websockets/ws_service.dart';
import 'package:eatvio_app/core/token_storage.dart';
class WsManager {
diff --git a/lib/core/initialization_cubit/ws_service.dart b/lib/core/websockets/ws_service.dart
similarity index 100%
rename from lib/core/initialization_cubit/ws_service.dart
rename to lib/core/websockets/ws_service.dart
diff --git a/lib/core/initialization_cubit/ws_service.g.dart b/lib/core/websockets/ws_service.g.dart
similarity index 100%
rename from lib/core/initialization_cubit/ws_service.g.dart
rename to lib/core/websockets/ws_service.g.dart
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 564cf015..9f535d91 100644
--- a/lib/core/widgets/info_row.dart
+++ b/lib/core/widgets/info_row.dart
@@ -1,9 +1,11 @@
import 'package:eatvio_app/core/theme/custom_text_style.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';
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart';
+import 'package:eatvio_app/features/feed/models/recipes_model.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:flutter/material.dart';
@@ -11,21 +13,35 @@ import 'package:flutter_svg/svg.dart';
class InfoRow extends StatelessWidget {
const InfoRow({
+ required this.type,
super.key,
this.diet,
this.itemsBackgroundColor,
this.disableDescriptionOfTypes = false,
this.plan,
+ this.recipe,
});
+ final ProductType type;
final Color? itemsBackgroundColor;
final Diet? diet;
final PlanModel? plan;
+ final Recipe? recipe;
final bool disableDescriptionOfTypes;
@override
Widget build(BuildContext context) {
final s = S.of(context);
+ String? timeTitle;
+ if (recipe != null) {
+ final hours = recipe!.cookingTime ~/ 60;
+ final minutes = recipe!.cookingTime % 60;
+ if (hours > 0) {
+ timeTitle = '$hours ${getHourForm(hours)} $minutes ${getMinuteForm(minutes)}';
+ } else {
+ timeTitle = '$minutes ${getMinuteForm(minutes)}';
+ }
+ }
return SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 16),
scrollDirection: Axis.horizontal,
@@ -41,7 +57,11 @@ class InfoRow extends StatelessWidget {
),
SizedBox(width: 4),
Text(
- diet == null ? '${plan?.duration} ${getDayForm(plan?.duration ?? 7, s)}' : '${diet?.duration} ${getDayForm(diet?.duration ?? 7, s)}',
+ switch (type) {
+ ProductType.diet => '${diet?.duration} ${getDayForm(diet?.duration ?? 7, s)}',
+ ProductType.plan => '${plan?.duration} ${getDayForm(plan?.duration ?? 7, s)}',
+ ProductType.recipe => '${timeTitle ?? ''}',
+ },
style: TextStyles.textXSSemibold.copyWith(color: appTheme.black),
),
],
@@ -51,18 +71,42 @@ class InfoRow extends StatelessWidget {
backgroundColor: itemsBackgroundColor,
children: [
SvgPicture.asset(
- 'assets/icons/people.svg',
+ switch (type) {
+ ProductType.diet => 'assets/icons/people.svg',
+ ProductType.plan => 'assets/icons/people.svg',
+ ProductType.recipe => 'assets/icons/calories.svg',
+ },
width: 13.3,
height: 13.3,
),
SizedBox(width: 4),
Text(
- '80 человек прошли',
+ switch (type) {
+ ProductType.diet => '80 человек прошли',
+ ProductType.plan => '80 человек прошли',
+ ProductType.recipe => (recipe?.calories.toStringAsFixed(0) ?? '0') + ' ккал',
+ },
style: TextStyles.textXSSemibold.copyWith(color: appTheme.black),
),
],
),
SizedBox(width: 12),
+ if (type == ProductType.recipe)
+ InfoBouble(
+ backgroundColor: itemsBackgroundColor,
+ children: [
+ SvgPicture.asset(
+ 'assets/icons/steps.svg',
+ width: 13.3,
+ height: 13.3,
+ ),
+ SizedBox(width: 4),
+ Text(
+ '${recipe?.steps?.length ?? 0} ${getStepForm(recipe?.steps?.length ?? 0)}',
+ style: TextStyles.textXSSemibold.copyWith(color: appTheme.black),
+ ),
+ ],
+ ),
if (plan?.type.hasAtLeastOneType() == true || diet?.type.hasAtLeastOneType() == true)
InfoBouble(
backgroundColor: itemsBackgroundColor,
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/features/feed/presentation/widgets/success_published_diet_bottom_sheet.dart b/lib/core/widgets/success_published_diet_bottom_sheet.dart
similarity index 88%
rename from lib/features/feed/presentation/widgets/success_published_diet_bottom_sheet.dart
rename to lib/core/widgets/success_published_diet_bottom_sheet.dart
index eade82c2..606aad41 100644
--- a/lib/features/feed/presentation/widgets/success_published_diet_bottom_sheet.dart
+++ b/lib/core/widgets/success_published_diet_bottom_sheet.dart
@@ -1,7 +1,9 @@
+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';
@@ -10,6 +12,7 @@ class SuccessPublishedDietBottomSheet extends StatelessWidget {
final String title;
@override
Widget build(BuildContext context) {
+ final s = S.of(context);
return Column(
children: [
SizedBox(
@@ -34,9 +37,9 @@ class SuccessPublishedDietBottomSheet extends StatelessWidget {
padding: const EdgeInsets.symmetric(horizontal: 20),
child: CustomOutlinedButton(
onPressed: () {
- GoRouter.of(context).pop();
+ 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/bottom_navigation_bar/presentation/bloc/notifications_cubit/notifications_cubit.dart b/lib/features/bottom_navigation_bar/presentation/bloc/notifications_cubit/notifications_cubit.dart
index 5d9a27f6..e193a23c 100644
--- a/lib/features/bottom_navigation_bar/presentation/bloc/notifications_cubit/notifications_cubit.dart
+++ b/lib/features/bottom_navigation_bar/presentation/bloc/notifications_cubit/notifications_cubit.dart
@@ -1,4 +1,4 @@
-import 'package:eatvio_app/core/initialization_cubit/ws_manager.dart';
+import 'package:eatvio_app/core/websockets/ws_manager.dart';
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/notifications_cubit/notifications_state.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
diff --git a/lib/features/bottom_navigation_bar/presentation/widgets/bottom_navigation_bar_widget.dart b/lib/features/bottom_navigation_bar/presentation/widgets/bottom_navigation_bar_widget.dart
index e96b6f25..32da11b4 100644
--- a/lib/features/bottom_navigation_bar/presentation/widgets/bottom_navigation_bar_widget.dart
+++ b/lib/features/bottom_navigation_bar/presentation/widgets/bottom_navigation_bar_widget.dart
@@ -1,10 +1,10 @@
import 'package:badges/badges.dart' as badges;
import 'package:eatvio_app/core/utils/image_constant.dart';
-import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/new_messages_cubit/new_messages_cubit.dart';
-import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/new_messages_cubit/new_messages_state.dart';
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/notifications_cubit/notifications_cubit.dart';
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/notifications_cubit/notifications_state.dart';
import 'package:eatvio_app/features/profile/models/profile_model.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';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:flutter/material.dart';
@@ -160,15 +160,19 @@ class _BottomNavigationBarWidgetState extends State {
),
BottomNavigationBarItem(
label: s.chat,
- icon: BlocBuilder(
- builder: (BuildContext context, NewMessagesState state) {
+ icon: BlocBuilder(
+ builder: (BuildContext context, ChatsState state) {
+ final hasUnreadMessages = state.chatsWithUnreadMessagesCount > 0;
return badges.Badge(
position: badges.BadgePosition.topEnd(),
badgeAnimation: badges.BadgeAnimation.fade(),
+ badgeStyle: badges.BadgeStyle(
+ badgeColor: hasUnreadMessages ? Colors.red : Colors.transparent,
+ ),
badgeContent: Text(
- (state is NewMessagesStateLoaded) ? state.messagesCount.toString() : '0',
+ hasUnreadMessages ? state.chatsWithUnreadMessagesCount.toString() : '',
style: TextStyle(
- color: Colors.white,
+ color: hasUnreadMessages ? Colors.white : Colors.transparent,
fontSize: 8,
),
),
diff --git a/lib/features/chat/models/chat_message_model.dart b/lib/features/chat/models/chat_message_model.dart
index 23f03f9f..591e5993 100644
--- a/lib/features/chat/models/chat_message_model.dart
+++ b/lib/features/chat/models/chat_message_model.dart
@@ -1,3 +1,4 @@
+import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'chat_message_model.freezed.dart';
@@ -10,7 +11,10 @@ class ChatMessageModel with _$ChatMessageModel {
required String data,
@JsonKey(name: 'media_data') required List? mediaData,
@JsonKey(name: 'author_id') required int authorId,
- @JsonKey(name: 'updated_at') required String updatedAt,
+ @JsonKey(name: 'updated_at', fromJson: parseDate) required DateTime updatedAt,
+ @JsonKey(name: 'read_by_to_user', defaultValue: false) required bool isRead,
+ @JsonKey(name: 'is_author_message_auth_user') required bool? isCurrentUserAuthor,
+ @JsonKey(name: 'room_id') required String? roomId,
}) = _ChatMessageModel;
factory ChatMessageModel.fromJson(Map json) => _$ChatMessageModelFromJson(json);
diff --git a/lib/features/chat/models/chat_message_model.freezed.dart b/lib/features/chat/models/chat_message_model.freezed.dart
index 8f921425..3cddc329 100644
--- a/lib/features/chat/models/chat_message_model.freezed.dart
+++ b/lib/features/chat/models/chat_message_model.freezed.dart
@@ -26,8 +26,14 @@ mixin _$ChatMessageModel {
List? get mediaData => throw _privateConstructorUsedError;
@JsonKey(name: 'author_id')
int get authorId => throw _privateConstructorUsedError;
- @JsonKey(name: 'updated_at')
- String get updatedAt => throw _privateConstructorUsedError;
+ @JsonKey(name: 'updated_at', fromJson: parseDate)
+ DateTime get updatedAt => throw _privateConstructorUsedError;
+ @JsonKey(name: 'read_by_to_user', defaultValue: false)
+ bool get isRead => throw _privateConstructorUsedError;
+ @JsonKey(name: 'is_author_message_auth_user')
+ bool? get isCurrentUserAuthor => throw _privateConstructorUsedError;
+ @JsonKey(name: 'room_id')
+ String? get roomId => throw _privateConstructorUsedError;
/// Serializes this ChatMessageModel to a JSON map.
Map toJson() => throw _privateConstructorUsedError;
@@ -50,7 +56,10 @@ abstract class $ChatMessageModelCopyWith<$Res> {
String data,
@JsonKey(name: 'media_data') List? mediaData,
@JsonKey(name: 'author_id') int authorId,
- @JsonKey(name: 'updated_at') String updatedAt});
+ @JsonKey(name: 'updated_at', fromJson: parseDate) DateTime updatedAt,
+ @JsonKey(name: 'read_by_to_user', defaultValue: false) bool isRead,
+ @JsonKey(name: 'is_author_message_auth_user') bool? isCurrentUserAuthor,
+ @JsonKey(name: 'room_id') String? roomId});
}
/// @nodoc
@@ -73,6 +82,9 @@ class _$ChatMessageModelCopyWithImpl<$Res, $Val extends ChatMessageModel>
Object? mediaData = freezed,
Object? authorId = null,
Object? updatedAt = null,
+ Object? isRead = null,
+ Object? isCurrentUserAuthor = freezed,
+ Object? roomId = freezed,
}) {
return _then(_value.copyWith(
id: null == id
@@ -94,7 +106,19 @@ class _$ChatMessageModelCopyWithImpl<$Res, $Val extends ChatMessageModel>
updatedAt: null == updatedAt
? _value.updatedAt
: updatedAt // ignore: cast_nullable_to_non_nullable
- as String,
+ as DateTime,
+ isRead: null == isRead
+ ? _value.isRead
+ : isRead // ignore: cast_nullable_to_non_nullable
+ as bool,
+ isCurrentUserAuthor: freezed == isCurrentUserAuthor
+ ? _value.isCurrentUserAuthor
+ : isCurrentUserAuthor // ignore: cast_nullable_to_non_nullable
+ as bool?,
+ roomId: freezed == roomId
+ ? _value.roomId
+ : roomId // ignore: cast_nullable_to_non_nullable
+ as String?,
) as $Val);
}
}
@@ -112,7 +136,10 @@ abstract class _$$ChatMessageModelImplCopyWith<$Res>
String data,
@JsonKey(name: 'media_data') List? mediaData,
@JsonKey(name: 'author_id') int authorId,
- @JsonKey(name: 'updated_at') String updatedAt});
+ @JsonKey(name: 'updated_at', fromJson: parseDate) DateTime updatedAt,
+ @JsonKey(name: 'read_by_to_user', defaultValue: false) bool isRead,
+ @JsonKey(name: 'is_author_message_auth_user') bool? isCurrentUserAuthor,
+ @JsonKey(name: 'room_id') String? roomId});
}
/// @nodoc
@@ -133,6 +160,9 @@ class __$$ChatMessageModelImplCopyWithImpl<$Res>
Object? mediaData = freezed,
Object? authorId = null,
Object? updatedAt = null,
+ Object? isRead = null,
+ Object? isCurrentUserAuthor = freezed,
+ Object? roomId = freezed,
}) {
return _then(_$ChatMessageModelImpl(
id: null == id
@@ -154,7 +184,19 @@ class __$$ChatMessageModelImplCopyWithImpl<$Res>
updatedAt: null == updatedAt
? _value.updatedAt
: updatedAt // ignore: cast_nullable_to_non_nullable
- as String,
+ as DateTime,
+ isRead: null == isRead
+ ? _value.isRead
+ : isRead // ignore: cast_nullable_to_non_nullable
+ as bool,
+ isCurrentUserAuthor: freezed == isCurrentUserAuthor
+ ? _value.isCurrentUserAuthor
+ : isCurrentUserAuthor // ignore: cast_nullable_to_non_nullable
+ as bool?,
+ roomId: freezed == roomId
+ ? _value.roomId
+ : roomId // ignore: cast_nullable_to_non_nullable
+ as String?,
));
}
}
@@ -167,7 +209,12 @@ class _$ChatMessageModelImpl implements _ChatMessageModel {
required this.data,
@JsonKey(name: 'media_data') required final List? mediaData,
@JsonKey(name: 'author_id') required this.authorId,
- @JsonKey(name: 'updated_at') required this.updatedAt})
+ @JsonKey(name: 'updated_at', fromJson: parseDate) required this.updatedAt,
+ @JsonKey(name: 'read_by_to_user', defaultValue: false)
+ required this.isRead,
+ @JsonKey(name: 'is_author_message_auth_user')
+ required this.isCurrentUserAuthor,
+ @JsonKey(name: 'room_id') required this.roomId})
: _mediaData = mediaData;
factory _$ChatMessageModelImpl.fromJson(Map json) =>
@@ -192,12 +239,21 @@ class _$ChatMessageModelImpl implements _ChatMessageModel {
@JsonKey(name: 'author_id')
final int authorId;
@override
- @JsonKey(name: 'updated_at')
- final String updatedAt;
+ @JsonKey(name: 'updated_at', fromJson: parseDate)
+ final DateTime updatedAt;
+ @override
+ @JsonKey(name: 'read_by_to_user', defaultValue: false)
+ final bool isRead;
+ @override
+ @JsonKey(name: 'is_author_message_auth_user')
+ final bool? isCurrentUserAuthor;
+ @override
+ @JsonKey(name: 'room_id')
+ final String? roomId;
@override
String toString() {
- return 'ChatMessageModel(id: $id, data: $data, mediaData: $mediaData, authorId: $authorId, updatedAt: $updatedAt)';
+ return 'ChatMessageModel(id: $id, data: $data, mediaData: $mediaData, authorId: $authorId, updatedAt: $updatedAt, isRead: $isRead, isCurrentUserAuthor: $isCurrentUserAuthor, roomId: $roomId)';
}
@override
@@ -212,13 +268,25 @@ class _$ChatMessageModelImpl implements _ChatMessageModel {
(identical(other.authorId, authorId) ||
other.authorId == authorId) &&
(identical(other.updatedAt, updatedAt) ||
- other.updatedAt == updatedAt));
+ other.updatedAt == updatedAt) &&
+ (identical(other.isRead, isRead) || other.isRead == isRead) &&
+ (identical(other.isCurrentUserAuthor, isCurrentUserAuthor) ||
+ other.isCurrentUserAuthor == isCurrentUserAuthor) &&
+ (identical(other.roomId, roomId) || other.roomId == roomId));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
- int get hashCode => Object.hash(runtimeType, id, data,
- const DeepCollectionEquality().hash(_mediaData), authorId, updatedAt);
+ int get hashCode => Object.hash(
+ runtimeType,
+ id,
+ data,
+ const DeepCollectionEquality().hash(_mediaData),
+ authorId,
+ updatedAt,
+ isRead,
+ isCurrentUserAuthor,
+ roomId);
/// Create a copy of ChatMessageModel
/// with the given fields replaced by the non-null parameter values.
@@ -243,7 +311,13 @@ abstract class _ChatMessageModel implements ChatMessageModel {
required final String data,
@JsonKey(name: 'media_data') required final List? mediaData,
@JsonKey(name: 'author_id') required final int authorId,
- @JsonKey(name: 'updated_at') required final String updatedAt}) =
+ @JsonKey(name: 'updated_at', fromJson: parseDate)
+ required final DateTime updatedAt,
+ @JsonKey(name: 'read_by_to_user', defaultValue: false)
+ required final bool isRead,
+ @JsonKey(name: 'is_author_message_auth_user')
+ required final bool? isCurrentUserAuthor,
+ @JsonKey(name: 'room_id') required final String? roomId}) =
_$ChatMessageModelImpl;
factory _ChatMessageModel.fromJson(Map json) =
@@ -260,8 +334,17 @@ abstract class _ChatMessageModel implements ChatMessageModel {
@JsonKey(name: 'author_id')
int get authorId;
@override
- @JsonKey(name: 'updated_at')
- String get updatedAt;
+ @JsonKey(name: 'updated_at', fromJson: parseDate)
+ DateTime get updatedAt;
+ @override
+ @JsonKey(name: 'read_by_to_user', defaultValue: false)
+ bool get isRead;
+ @override
+ @JsonKey(name: 'is_author_message_auth_user')
+ bool? get isCurrentUserAuthor;
+ @override
+ @JsonKey(name: 'room_id')
+ String? get roomId;
/// Create a copy of ChatMessageModel
/// with the given fields replaced by the non-null parameter values.
diff --git a/lib/features/chat/models/chat_message_model.g.dart b/lib/features/chat/models/chat_message_model.g.dart
index 3a8d1827..de7ea3c3 100644
--- a/lib/features/chat/models/chat_message_model.g.dart
+++ b/lib/features/chat/models/chat_message_model.g.dart
@@ -13,7 +13,10 @@ _$ChatMessageModelImpl _$$ChatMessageModelImplFromJson(
data: json['data'] as String,
mediaData: json['media_data'] as List?,
authorId: (json['author_id'] as num).toInt(),
- updatedAt: json['updated_at'] as String,
+ updatedAt: parseDate(json['updated_at'] as String),
+ isRead: json['read_by_to_user'] as bool? ?? false,
+ isCurrentUserAuthor: json['is_author_message_auth_user'] as bool?,
+ roomId: json['room_id'] as String?,
);
Map _$$ChatMessageModelImplToJson(
@@ -23,5 +26,8 @@ Map _$$ChatMessageModelImplToJson(
'data': instance.data,
'media_data': instance.mediaData,
'author_id': instance.authorId,
- 'updated_at': instance.updatedAt,
+ 'updated_at': instance.updatedAt.toIso8601String(),
+ 'read_by_to_user': instance.isRead,
+ 'is_author_message_auth_user': instance.isCurrentUserAuthor,
+ 'room_id': instance.roomId,
};
diff --git a/lib/features/chat/models/chat_room_preview_model.dart b/lib/features/chat/models/chat_room_preview_model.dart
index 29c5e3ac..bc6e4b5d 100644
--- a/lib/features/chat/models/chat_room_preview_model.dart
+++ b/lib/features/chat/models/chat_room_preview_model.dart
@@ -1,3 +1,4 @@
+import 'package:eatvio_app/features/chat/models/chat_message_model.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'chat_room_preview_model.freezed.dart';
@@ -17,26 +18,19 @@ class ChatRoom with _$ChatRoom {
const factory ChatRoom({
@JsonKey(name: "id") required String id,
@JsonKey(name: "room_name") required String roomName,
- @JsonKey(name: "last_message") required dynamic lastMessage,
+ @JsonKey(name: "last_message", fromJson: parseLastMessage) required ChatMessageModel? lastMessage,
@JsonKey(name: "users") required Users users,
+ @JsonKey(name: "unread_messages_count") required int unreadMessagesCount,
}) = _ChatRoom;
factory ChatRoom.fromJson(Map json) => _$ChatRoomFromJson(json);
}
-@freezed
-class LastMessageClass with _$LastMessageClass {
- const factory LastMessageClass({
- @JsonKey(name: "id") required int id,
- @JsonKey(name: "data") required String data,
- @JsonKey(name: "media_data") required List mediaData,
- @JsonKey(name: "author_id") required int authorId,
- @JsonKey(name: "is_author_message_auth_user") required bool isAuthorMessageAuthUser,
- @JsonKey(name: "read_by_to_user") required bool readByToUser,
- @JsonKey(name: "updated_at") required DateTime updatedAt,
- }) = _LastMessageClass;
-
- factory LastMessageClass.fromJson(Map json) => _$LastMessageClassFromJson(json);
+ChatMessageModel? parseLastMessage(dynamic json) {
+ if (json is List) {
+ return null;
+ }
+ return ChatMessageModel.fromJson(json);
}
@freezed
diff --git a/lib/features/chat/models/chat_room_preview_model.freezed.dart b/lib/features/chat/models/chat_room_preview_model.freezed.dart
index 4b006cf0..940c3106 100644
--- a/lib/features/chat/models/chat_room_preview_model.freezed.dart
+++ b/lib/features/chat/models/chat_room_preview_model.freezed.dart
@@ -190,10 +190,12 @@ mixin _$ChatRoom {
String get id => throw _privateConstructorUsedError;
@JsonKey(name: "room_name")
String get roomName => throw _privateConstructorUsedError;
- @JsonKey(name: "last_message")
- dynamic get lastMessage => throw _privateConstructorUsedError;
+ @JsonKey(name: "last_message", fromJson: parseLastMessage)
+ ChatMessageModel? get lastMessage => throw _privateConstructorUsedError;
@JsonKey(name: "users")
Users get users => throw _privateConstructorUsedError;
+ @JsonKey(name: "unread_messages_count")
+ int get unreadMessagesCount => throw _privateConstructorUsedError;
/// Serializes this ChatRoom to a JSON map.
Map toJson() => throw _privateConstructorUsedError;
@@ -213,9 +215,12 @@ abstract class $ChatRoomCopyWith<$Res> {
$Res call(
{@JsonKey(name: "id") String id,
@JsonKey(name: "room_name") String roomName,
- @JsonKey(name: "last_message") dynamic lastMessage,
- @JsonKey(name: "users") Users users});
+ @JsonKey(name: "last_message", fromJson: parseLastMessage)
+ ChatMessageModel? lastMessage,
+ @JsonKey(name: "users") Users users,
+ @JsonKey(name: "unread_messages_count") int unreadMessagesCount});
+ $ChatMessageModelCopyWith<$Res>? get lastMessage;
$UsersCopyWith<$Res> get users;
}
@@ -238,6 +243,7 @@ class _$ChatRoomCopyWithImpl<$Res, $Val extends ChatRoom>
Object? roomName = null,
Object? lastMessage = freezed,
Object? users = null,
+ Object? unreadMessagesCount = null,
}) {
return _then(_value.copyWith(
id: null == id
@@ -251,14 +257,32 @@ class _$ChatRoomCopyWithImpl<$Res, $Val extends ChatRoom>
lastMessage: freezed == lastMessage
? _value.lastMessage
: lastMessage // ignore: cast_nullable_to_non_nullable
- as dynamic,
+ as ChatMessageModel?,
users: null == users
? _value.users
: users // ignore: cast_nullable_to_non_nullable
as Users,
+ unreadMessagesCount: null == unreadMessagesCount
+ ? _value.unreadMessagesCount
+ : unreadMessagesCount // ignore: cast_nullable_to_non_nullable
+ as int,
) as $Val);
}
+ /// Create a copy of ChatRoom
+ /// with the given fields replaced by the non-null parameter values.
+ @override
+ @pragma('vm:prefer-inline')
+ $ChatMessageModelCopyWith<$Res>? get lastMessage {
+ if (_value.lastMessage == null) {
+ return null;
+ }
+
+ return $ChatMessageModelCopyWith<$Res>(_value.lastMessage!, (value) {
+ return _then(_value.copyWith(lastMessage: value) as $Val);
+ });
+ }
+
/// Create a copy of ChatRoom
/// with the given fields replaced by the non-null parameter values.
@override
@@ -281,9 +305,13 @@ abstract class _$$ChatRoomImplCopyWith<$Res>
$Res call(
{@JsonKey(name: "id") String id,
@JsonKey(name: "room_name") String roomName,
- @JsonKey(name: "last_message") dynamic lastMessage,
- @JsonKey(name: "users") Users users});
+ @JsonKey(name: "last_message", fromJson: parseLastMessage)
+ ChatMessageModel? lastMessage,
+ @JsonKey(name: "users") Users users,
+ @JsonKey(name: "unread_messages_count") int unreadMessagesCount});
+ @override
+ $ChatMessageModelCopyWith<$Res>? get lastMessage;
@override
$UsersCopyWith<$Res> get users;
}
@@ -305,6 +333,7 @@ class __$$ChatRoomImplCopyWithImpl<$Res>
Object? roomName = null,
Object? lastMessage = freezed,
Object? users = null,
+ Object? unreadMessagesCount = null,
}) {
return _then(_$ChatRoomImpl(
id: null == id
@@ -318,11 +347,15 @@ class __$$ChatRoomImplCopyWithImpl<$Res>
lastMessage: freezed == lastMessage
? _value.lastMessage
: lastMessage // ignore: cast_nullable_to_non_nullable
- as dynamic,
+ as ChatMessageModel?,
users: null == users
? _value.users
: users // ignore: cast_nullable_to_non_nullable
as Users,
+ unreadMessagesCount: null == unreadMessagesCount
+ ? _value.unreadMessagesCount
+ : unreadMessagesCount // ignore: cast_nullable_to_non_nullable
+ as int,
));
}
}
@@ -333,8 +366,11 @@ class _$ChatRoomImpl implements _ChatRoom {
const _$ChatRoomImpl(
{@JsonKey(name: "id") required this.id,
@JsonKey(name: "room_name") required this.roomName,
- @JsonKey(name: "last_message") required this.lastMessage,
- @JsonKey(name: "users") required this.users});
+ @JsonKey(name: "last_message", fromJson: parseLastMessage)
+ required this.lastMessage,
+ @JsonKey(name: "users") required this.users,
+ @JsonKey(name: "unread_messages_count")
+ required this.unreadMessagesCount});
factory _$ChatRoomImpl.fromJson(Map json) =>
_$$ChatRoomImplFromJson(json);
@@ -346,15 +382,18 @@ class _$ChatRoomImpl implements _ChatRoom {
@JsonKey(name: "room_name")
final String roomName;
@override
- @JsonKey(name: "last_message")
- final dynamic lastMessage;
+ @JsonKey(name: "last_message", fromJson: parseLastMessage)
+ final ChatMessageModel? lastMessage;
@override
@JsonKey(name: "users")
final Users users;
+ @override
+ @JsonKey(name: "unread_messages_count")
+ final int unreadMessagesCount;
@override
String toString() {
- return 'ChatRoom(id: $id, roomName: $roomName, lastMessage: $lastMessage, users: $users)';
+ return 'ChatRoom(id: $id, roomName: $roomName, lastMessage: $lastMessage, users: $users, unreadMessagesCount: $unreadMessagesCount)';
}
@override
@@ -365,15 +404,17 @@ class _$ChatRoomImpl implements _ChatRoom {
(identical(other.id, id) || other.id == id) &&
(identical(other.roomName, roomName) ||
other.roomName == roomName) &&
- const DeepCollectionEquality()
- .equals(other.lastMessage, lastMessage) &&
- (identical(other.users, users) || other.users == users));
+ (identical(other.lastMessage, lastMessage) ||
+ other.lastMessage == lastMessage) &&
+ (identical(other.users, users) || other.users == users) &&
+ (identical(other.unreadMessagesCount, unreadMessagesCount) ||
+ other.unreadMessagesCount == unreadMessagesCount));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
- int get hashCode => Object.hash(runtimeType, id, roomName,
- const DeepCollectionEquality().hash(lastMessage), users);
+ int get hashCode => Object.hash(
+ runtimeType, id, roomName, lastMessage, users, unreadMessagesCount);
/// Create a copy of ChatRoom
/// with the given fields replaced by the non-null parameter values.
@@ -395,8 +436,11 @@ abstract class _ChatRoom implements ChatRoom {
const factory _ChatRoom(
{@JsonKey(name: "id") required final String id,
@JsonKey(name: "room_name") required final String roomName,
- @JsonKey(name: "last_message") required final dynamic lastMessage,
- @JsonKey(name: "users") required final Users users}) = _$ChatRoomImpl;
+ @JsonKey(name: "last_message", fromJson: parseLastMessage)
+ required final ChatMessageModel? lastMessage,
+ @JsonKey(name: "users") required final Users users,
+ @JsonKey(name: "unread_messages_count")
+ required final int unreadMessagesCount}) = _$ChatRoomImpl;
factory _ChatRoom.fromJson(Map json) =
_$ChatRoomImpl.fromJson;
@@ -408,11 +452,14 @@ abstract class _ChatRoom implements ChatRoom {
@JsonKey(name: "room_name")
String get roomName;
@override
- @JsonKey(name: "last_message")
- dynamic get lastMessage;
+ @JsonKey(name: "last_message", fromJson: parseLastMessage)
+ ChatMessageModel? get lastMessage;
@override
@JsonKey(name: "users")
Users get users;
+ @override
+ @JsonKey(name: "unread_messages_count")
+ int get unreadMessagesCount;
/// Create a copy of ChatRoom
/// with the given fields replaced by the non-null parameter values.
@@ -422,327 +469,6 @@ abstract class _ChatRoom implements ChatRoom {
throw _privateConstructorUsedError;
}
-LastMessageClass _$LastMessageClassFromJson(Map json) {
- return _LastMessageClass.fromJson(json);
-}
-
-/// @nodoc
-mixin _$LastMessageClass {
- @JsonKey(name: "id")
- int get id => throw _privateConstructorUsedError;
- @JsonKey(name: "data")
- String get data => throw _privateConstructorUsedError;
- @JsonKey(name: "media_data")
- List get mediaData => throw _privateConstructorUsedError;
- @JsonKey(name: "author_id")
- int get authorId => throw _privateConstructorUsedError;
- @JsonKey(name: "is_author_message_auth_user")
- bool get isAuthorMessageAuthUser => throw _privateConstructorUsedError;
- @JsonKey(name: "read_by_to_user")
- bool get readByToUser => throw _privateConstructorUsedError;
- @JsonKey(name: "updated_at")
- DateTime get updatedAt => throw _privateConstructorUsedError;
-
- /// Serializes this LastMessageClass to a JSON map.
- Map toJson() => throw _privateConstructorUsedError;
-
- /// Create a copy of LastMessageClass
- /// with the given fields replaced by the non-null parameter values.
- @JsonKey(includeFromJson: false, includeToJson: false)
- $LastMessageClassCopyWith get copyWith =>
- throw _privateConstructorUsedError;
-}
-
-/// @nodoc
-abstract class $LastMessageClassCopyWith<$Res> {
- factory $LastMessageClassCopyWith(
- LastMessageClass value, $Res Function(LastMessageClass) then) =
- _$LastMessageClassCopyWithImpl<$Res, LastMessageClass>;
- @useResult
- $Res call(
- {@JsonKey(name: "id") int id,
- @JsonKey(name: "data") String data,
- @JsonKey(name: "media_data") List mediaData,
- @JsonKey(name: "author_id") int authorId,
- @JsonKey(name: "is_author_message_auth_user")
- bool isAuthorMessageAuthUser,
- @JsonKey(name: "read_by_to_user") bool readByToUser,
- @JsonKey(name: "updated_at") DateTime updatedAt});
-}
-
-/// @nodoc
-class _$LastMessageClassCopyWithImpl<$Res, $Val extends LastMessageClass>
- implements $LastMessageClassCopyWith<$Res> {
- _$LastMessageClassCopyWithImpl(this._value, this._then);
-
- // ignore: unused_field
- final $Val _value;
- // ignore: unused_field
- final $Res Function($Val) _then;
-
- /// Create a copy of LastMessageClass
- /// with the given fields replaced by the non-null parameter values.
- @pragma('vm:prefer-inline')
- @override
- $Res call({
- Object? id = null,
- Object? data = null,
- Object? mediaData = null,
- Object? authorId = null,
- Object? isAuthorMessageAuthUser = null,
- Object? readByToUser = null,
- Object? updatedAt = null,
- }) {
- return _then(_value.copyWith(
- id: null == id
- ? _value.id
- : id // ignore: cast_nullable_to_non_nullable
- as int,
- data: null == data
- ? _value.data
- : data // ignore: cast_nullable_to_non_nullable
- as String,
- mediaData: null == mediaData
- ? _value.mediaData
- : mediaData // ignore: cast_nullable_to_non_nullable
- as List,
- authorId: null == authorId
- ? _value.authorId
- : authorId // ignore: cast_nullable_to_non_nullable
- as int,
- isAuthorMessageAuthUser: null == isAuthorMessageAuthUser
- ? _value.isAuthorMessageAuthUser
- : isAuthorMessageAuthUser // ignore: cast_nullable_to_non_nullable
- as bool,
- readByToUser: null == readByToUser
- ? _value.readByToUser
- : readByToUser // ignore: cast_nullable_to_non_nullable
- as bool,
- updatedAt: null == updatedAt
- ? _value.updatedAt
- : updatedAt // ignore: cast_nullable_to_non_nullable
- as DateTime,
- ) as $Val);
- }
-}
-
-/// @nodoc
-abstract class _$$LastMessageClassImplCopyWith<$Res>
- implements $LastMessageClassCopyWith<$Res> {
- factory _$$LastMessageClassImplCopyWith(_$LastMessageClassImpl value,
- $Res Function(_$LastMessageClassImpl) then) =
- __$$LastMessageClassImplCopyWithImpl<$Res>;
- @override
- @useResult
- $Res call(
- {@JsonKey(name: "id") int id,
- @JsonKey(name: "data") String data,
- @JsonKey(name: "media_data") List mediaData,
- @JsonKey(name: "author_id") int authorId,
- @JsonKey(name: "is_author_message_auth_user")
- bool isAuthorMessageAuthUser,
- @JsonKey(name: "read_by_to_user") bool readByToUser,
- @JsonKey(name: "updated_at") DateTime updatedAt});
-}
-
-/// @nodoc
-class __$$LastMessageClassImplCopyWithImpl<$Res>
- extends _$LastMessageClassCopyWithImpl<$Res, _$LastMessageClassImpl>
- implements _$$LastMessageClassImplCopyWith<$Res> {
- __$$LastMessageClassImplCopyWithImpl(_$LastMessageClassImpl _value,
- $Res Function(_$LastMessageClassImpl) _then)
- : super(_value, _then);
-
- /// Create a copy of LastMessageClass
- /// with the given fields replaced by the non-null parameter values.
- @pragma('vm:prefer-inline')
- @override
- $Res call({
- Object? id = null,
- Object? data = null,
- Object? mediaData = null,
- Object? authorId = null,
- Object? isAuthorMessageAuthUser = null,
- Object? readByToUser = null,
- Object? updatedAt = null,
- }) {
- return _then(_$LastMessageClassImpl(
- id: null == id
- ? _value.id
- : id // ignore: cast_nullable_to_non_nullable
- as int,
- data: null == data
- ? _value.data
- : data // ignore: cast_nullable_to_non_nullable
- as String,
- mediaData: null == mediaData
- ? _value._mediaData
- : mediaData // ignore: cast_nullable_to_non_nullable
- as List,
- authorId: null == authorId
- ? _value.authorId
- : authorId // ignore: cast_nullable_to_non_nullable
- as int,
- isAuthorMessageAuthUser: null == isAuthorMessageAuthUser
- ? _value.isAuthorMessageAuthUser
- : isAuthorMessageAuthUser // ignore: cast_nullable_to_non_nullable
- as bool,
- readByToUser: null == readByToUser
- ? _value.readByToUser
- : readByToUser // ignore: cast_nullable_to_non_nullable
- as bool,
- updatedAt: null == updatedAt
- ? _value.updatedAt
- : updatedAt // ignore: cast_nullable_to_non_nullable
- as DateTime,
- ));
- }
-}
-
-/// @nodoc
-@JsonSerializable()
-class _$LastMessageClassImpl implements _LastMessageClass {
- const _$LastMessageClassImpl(
- {@JsonKey(name: "id") required this.id,
- @JsonKey(name: "data") required this.data,
- @JsonKey(name: "media_data") required final List mediaData,
- @JsonKey(name: "author_id") required this.authorId,
- @JsonKey(name: "is_author_message_auth_user")
- required this.isAuthorMessageAuthUser,
- @JsonKey(name: "read_by_to_user") required this.readByToUser,
- @JsonKey(name: "updated_at") required this.updatedAt})
- : _mediaData = mediaData;
-
- factory _$LastMessageClassImpl.fromJson(Map json) =>
- _$$LastMessageClassImplFromJson(json);
-
- @override
- @JsonKey(name: "id")
- final int id;
- @override
- @JsonKey(name: "data")
- final String data;
- final List _mediaData;
- @override
- @JsonKey(name: "media_data")
- List get mediaData {
- if (_mediaData is EqualUnmodifiableListView) return _mediaData;
- // ignore: implicit_dynamic_type
- return EqualUnmodifiableListView(_mediaData);
- }
-
- @override
- @JsonKey(name: "author_id")
- final int authorId;
- @override
- @JsonKey(name: "is_author_message_auth_user")
- final bool isAuthorMessageAuthUser;
- @override
- @JsonKey(name: "read_by_to_user")
- final bool readByToUser;
- @override
- @JsonKey(name: "updated_at")
- final DateTime updatedAt;
-
- @override
- String toString() {
- return 'LastMessageClass(id: $id, data: $data, mediaData: $mediaData, authorId: $authorId, isAuthorMessageAuthUser: $isAuthorMessageAuthUser, readByToUser: $readByToUser, updatedAt: $updatedAt)';
- }
-
- @override
- bool operator ==(Object other) {
- return identical(this, other) ||
- (other.runtimeType == runtimeType &&
- other is _$LastMessageClassImpl &&
- (identical(other.id, id) || other.id == id) &&
- (identical(other.data, data) || other.data == data) &&
- const DeepCollectionEquality()
- .equals(other._mediaData, _mediaData) &&
- (identical(other.authorId, authorId) ||
- other.authorId == authorId) &&
- (identical(
- other.isAuthorMessageAuthUser, isAuthorMessageAuthUser) ||
- other.isAuthorMessageAuthUser == isAuthorMessageAuthUser) &&
- (identical(other.readByToUser, readByToUser) ||
- other.readByToUser == readByToUser) &&
- (identical(other.updatedAt, updatedAt) ||
- other.updatedAt == updatedAt));
- }
-
- @JsonKey(includeFromJson: false, includeToJson: false)
- @override
- int get hashCode => Object.hash(
- runtimeType,
- id,
- data,
- const DeepCollectionEquality().hash(_mediaData),
- authorId,
- isAuthorMessageAuthUser,
- readByToUser,
- updatedAt);
-
- /// Create a copy of LastMessageClass
- /// with the given fields replaced by the non-null parameter values.
- @JsonKey(includeFromJson: false, includeToJson: false)
- @override
- @pragma('vm:prefer-inline')
- _$$LastMessageClassImplCopyWith<_$LastMessageClassImpl> get copyWith =>
- __$$LastMessageClassImplCopyWithImpl<_$LastMessageClassImpl>(
- this, _$identity);
-
- @override
- Map toJson() {
- return _$$LastMessageClassImplToJson(
- this,
- );
- }
-}
-
-abstract class _LastMessageClass implements LastMessageClass {
- const factory _LastMessageClass(
- {@JsonKey(name: "id") required final int id,
- @JsonKey(name: "data") required final String data,
- @JsonKey(name: "media_data") required final List mediaData,
- @JsonKey(name: "author_id") required final int authorId,
- @JsonKey(name: "is_author_message_auth_user")
- required final bool isAuthorMessageAuthUser,
- @JsonKey(name: "read_by_to_user") required final bool readByToUser,
- @JsonKey(name: "updated_at") required final DateTime updatedAt}) =
- _$LastMessageClassImpl;
-
- factory _LastMessageClass.fromJson(Map json) =
- _$LastMessageClassImpl.fromJson;
-
- @override
- @JsonKey(name: "id")
- int get id;
- @override
- @JsonKey(name: "data")
- String get data;
- @override
- @JsonKey(name: "media_data")
- List get mediaData;
- @override
- @JsonKey(name: "author_id")
- int get authorId;
- @override
- @JsonKey(name: "is_author_message_auth_user")
- bool get isAuthorMessageAuthUser;
- @override
- @JsonKey(name: "read_by_to_user")
- bool get readByToUser;
- @override
- @JsonKey(name: "updated_at")
- DateTime get updatedAt;
-
- /// Create a copy of LastMessageClass
- /// with the given fields replaced by the non-null parameter values.
- @override
- @JsonKey(includeFromJson: false, includeToJson: false)
- _$$LastMessageClassImplCopyWith<_$LastMessageClassImpl> get copyWith =>
- throw _privateConstructorUsedError;
-}
-
Users _$UsersFromJson(Map json) {
return _Users.fromJson(json);
}
diff --git a/lib/features/chat/models/chat_room_preview_model.g.dart b/lib/features/chat/models/chat_room_preview_model.g.dart
index 5ae3f709..ad812d46 100644
--- a/lib/features/chat/models/chat_room_preview_model.g.dart
+++ b/lib/features/chat/models/chat_room_preview_model.g.dart
@@ -24,8 +24,9 @@ _$ChatRoomImpl _$$ChatRoomImplFromJson(Map json) =>
_$ChatRoomImpl(
id: json['id'] as String,
roomName: json['room_name'] as String,
- lastMessage: json['last_message'],
+ lastMessage: parseLastMessage(json['last_message']),
users: Users.fromJson(json['users'] as Map),
+ unreadMessagesCount: (json['unread_messages_count'] as num).toInt(),
);
Map _$$ChatRoomImplToJson(_$ChatRoomImpl instance) =>
@@ -34,30 +35,7 @@ Map _$$ChatRoomImplToJson(_$ChatRoomImpl instance) =>
'room_name': instance.roomName,
'last_message': instance.lastMessage,
'users': instance.users,
- };
-
-_$LastMessageClassImpl _$$LastMessageClassImplFromJson(
- Map json) =>
- _$LastMessageClassImpl(
- id: (json['id'] as num).toInt(),
- data: json['data'] as String,
- mediaData: json['media_data'] as List,
- authorId: (json['author_id'] as num).toInt(),
- isAuthorMessageAuthUser: json['is_author_message_auth_user'] as bool,
- readByToUser: json['read_by_to_user'] as bool,
- updatedAt: DateTime.parse(json['updated_at'] as String),
- );
-
-Map _$$LastMessageClassImplToJson(
- _$LastMessageClassImpl instance) =>
- {
- 'id': instance.id,
- 'data': instance.data,
- 'media_data': instance.mediaData,
- 'author_id': instance.authorId,
- 'is_author_message_auth_user': instance.isAuthorMessageAuthUser,
- 'read_by_to_user': instance.readByToUser,
- 'updated_at': instance.updatedAt.toIso8601String(),
+ 'unread_messages_count': instance.unreadMessagesCount,
};
_$UsersImpl _$$UsersImplFromJson(Map json) => _$UsersImpl(
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 a072ad50..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,10 +1,13 @@
+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/initialization_cubit/ws_manager.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/chat_room_cubit/chat_room_state.dart';
import 'package:eatvio_app/features/chat/repositories/chat_repository.dart';
@@ -14,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;
@@ -74,6 +79,9 @@ class ChatRoomCubit extends Cubit {
),
);
} catch (e) {
+ typingSubscription?.cancel();
+ messagesSubscription?.cancel();
+ wsManager.unsubscribeDirectChatChannel();
emit(ChatRoomState.error(errorMessage: 'Ошибка получеия данных'));
}
}
@@ -87,10 +95,12 @@ class ChatRoomCubit extends Cubit {
wsManager.subscribeDirectChatChannel();
wsManager.directChatStream?.listen((event) {
AudioPlayer().play((AssetSource('audio/recive_message.wav')));
+ final message = ChatMessageModel.fromJson(jsonDecode(event.data)['message']);
List messagesList = [
- ChatMessageModel.fromJson(jsonDecode(event.data)['message']),
+ message,
...(state as ChatRoomStateLoaded).chatMessages,
];
+ chatRepository.emitNewMessageToChats(message);
emit(
ChatRoomState.loaded(
chatRoom: (state as ChatRoomStateLoaded).chatRoom,
@@ -125,17 +135,46 @@ class ChatRoomCubit extends Cubit {
});
}
- sendMessage({
+ Future sendMessage({
required String chatId,
String? data,
List? files,
}) async {
- await chatRepository.sendMessage(
- chatId: chatId,
- data: data,
- files: files != null ? await ImageCompressUtility.compressFilesList(files) : null,
- );
- AudioPlayer().play((AssetSource('audio/send_message.wav')));
+ try {
+ await chatRepository.sendMessage(
+ chatId: chatId,
+ data: data,
+ files: files != null ? await ImageCompressUtility.compressFilesList(files) : null,
+ );
+ AudioPlayer().play((AssetSource('audio/send_message.wav')));
+ return true;
+ } catch (e, st) {
+ log(e.toString(), stackTrace: st);
+ return false;
+ }
+ }
+
+ Future markMessagesAsRead(String chatId, List<({int id, int index})> messagesIds) async {
+ try {
+ if (state is ChatRoomStateLoaded) {
+ final ids = messagesIds.map((e) => e.id).toList();
+ await chatRepository.markMessagesAsRead(chatId, ids);
+ final currentMessages = [...(state as ChatRoomStateLoaded).chatMessages];
+ messagesIds.forEach((elem) {
+ currentMessages[elem.index] = currentMessages[elem.index].copyWith(isRead: true);
+ });
+ if (!isClosed) {
+ emit(
+ (state as ChatRoomStateLoaded).copyWith(chatMessages: currentMessages),
+ );
+ }
+ return true;
+ }
+ return false;
+ } catch (e, st) {
+ log(e.toString(), stackTrace: st);
+ return false;
+ }
}
@override
diff --git a/lib/features/chat/presentation/bloc/chat_select_cubit/chat_select_cubit.dart b/lib/features/chat/presentation/bloc/chat_select_cubit/chat_select_cubit.dart
deleted file mode 100644
index bc8f7a96..00000000
--- a/lib/features/chat/presentation/bloc/chat_select_cubit/chat_select_cubit.dart
+++ /dev/null
@@ -1,27 +0,0 @@
-import 'package:eatvio_app/features/chat/presentation/bloc/chat_select_cubit/chat_select_state.dart';
-import 'package:eatvio_app/features/chat/repositories/chat_repository.dart';
-import 'package:flutter_bloc/flutter_bloc.dart';
-
-class ChatSelectCubit extends Cubit {
- final ChatRepository chatRepository;
-
- ChatSelectCubit({
- required this.chatRepository,
- }) : super(ChatSelectState());
-
- getChatRooms({String? query}) async {
- emit(ChatSelectState.loading());
- try {
- final chatRooms = await chatRepository.getChatRoomPreviews(query: query);
- emit(
- ChatSelectState.loaded(
- chatRooms: chatRooms,
- ),
- );
- } catch (e) {
- emit(
- ChatSelectState.error(errorMessage: e.toString()),
- );
- }
- }
-}
diff --git a/lib/features/chat/presentation/bloc/chat_select_cubit/chat_select_state.dart b/lib/features/chat/presentation/bloc/chat_select_cubit/chat_select_state.dart
deleted file mode 100644
index eae74b97..00000000
--- a/lib/features/chat/presentation/bloc/chat_select_cubit/chat_select_state.dart
+++ /dev/null
@@ -1,19 +0,0 @@
-import 'package:eatvio_app/features/chat/models/chat_room_preview_model.dart';
-import 'package:freezed_annotation/freezed_annotation.dart';
-
-part 'chat_select_state.freezed.dart';
-
-@freezed
-abstract class ChatSelectState with _$ChatSelectState {
- const factory ChatSelectState() = _ChatSelectState;
-
- const factory ChatSelectState.loading() = ChatSelectStateLoading;
-
- const factory ChatSelectState.error({
- required String errorMessage,
- }) = ChatSelectStateError;
-
- const factory ChatSelectState.loaded({
- required List chatRooms,
- }) = ChatSelectStateLoaded;
-}
diff --git a/lib/features/chat/presentation/bloc/chat_select_cubit/chat_select_state.freezed.dart b/lib/features/chat/presentation/bloc/chat_select_cubit/chat_select_state.freezed.dart
deleted file mode 100644
index 99fa9054..00000000
--- a/lib/features/chat/presentation/bloc/chat_select_cubit/chat_select_state.freezed.dart
+++ /dev/null
@@ -1,646 +0,0 @@
-// 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 'chat_select_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 _$ChatSelectState {
- @optionalTypeArgs
- TResult when(
- TResult Function() $default, {
- required TResult Function() loading,
- required TResult Function(String errorMessage) error,
- required TResult Function(List chatRooms) loaded,
- }) =>
- throw _privateConstructorUsedError;
- @optionalTypeArgs
- TResult? whenOrNull(
- TResult? Function()? $default, {
- TResult? Function()? loading,
- TResult? Function(String errorMessage)? error,
- TResult? Function(List chatRooms)? loaded,
- }) =>
- throw _privateConstructorUsedError;
- @optionalTypeArgs
- TResult maybeWhen(
- TResult Function()? $default, {
- TResult Function()? loading,
- TResult Function(String errorMessage)? error,
- TResult Function(List chatRooms)? loaded,
- required TResult orElse(),
- }) =>
- throw _privateConstructorUsedError;
- @optionalTypeArgs
- TResult map(
- TResult Function(_ChatSelectState value) $default, {
- required TResult Function(ChatSelectStateLoading value) loading,
- required TResult Function(ChatSelectStateError value) error,
- required TResult Function(ChatSelectStateLoaded value) loaded,
- }) =>
- throw _privateConstructorUsedError;
- @optionalTypeArgs
- TResult? mapOrNull(
- TResult? Function(_ChatSelectState value)? $default, {
- TResult? Function(ChatSelectStateLoading value)? loading,
- TResult? Function(ChatSelectStateError value)? error,
- TResult? Function(ChatSelectStateLoaded value)? loaded,
- }) =>
- throw _privateConstructorUsedError;
- @optionalTypeArgs
- TResult maybeMap(
- TResult Function(_ChatSelectState value)? $default, {
- TResult Function(ChatSelectStateLoading value)? loading,
- TResult Function(ChatSelectStateError value)? error,
- TResult Function(ChatSelectStateLoaded value)? loaded,
- required TResult orElse(),
- }) =>
- throw _privateConstructorUsedError;
-}
-
-/// @nodoc
-abstract class $ChatSelectStateCopyWith<$Res> {
- factory $ChatSelectStateCopyWith(
- ChatSelectState value, $Res Function(ChatSelectState) then) =
- _$ChatSelectStateCopyWithImpl<$Res, ChatSelectState>;
-}
-
-/// @nodoc
-class _$ChatSelectStateCopyWithImpl<$Res, $Val extends ChatSelectState>
- implements $ChatSelectStateCopyWith<$Res> {
- _$ChatSelectStateCopyWithImpl(this._value, this._then);
-
- // ignore: unused_field
- final $Val _value;
- // ignore: unused_field
- final $Res Function($Val) _then;
-
- /// Create a copy of ChatSelectState
- /// with the given fields replaced by the non-null parameter values.
-}
-
-/// @nodoc
-abstract class _$$ChatSelectStateImplCopyWith<$Res> {
- factory _$$ChatSelectStateImplCopyWith(_$ChatSelectStateImpl value,
- $Res Function(_$ChatSelectStateImpl) then) =
- __$$ChatSelectStateImplCopyWithImpl<$Res>;
-}
-
-/// @nodoc
-class __$$ChatSelectStateImplCopyWithImpl<$Res>
- extends _$ChatSelectStateCopyWithImpl<$Res, _$ChatSelectStateImpl>
- implements _$$ChatSelectStateImplCopyWith<$Res> {
- __$$ChatSelectStateImplCopyWithImpl(
- _$ChatSelectStateImpl _value, $Res Function(_$ChatSelectStateImpl) _then)
- : super(_value, _then);
-
- /// Create a copy of ChatSelectState
- /// with the given fields replaced by the non-null parameter values.
-}
-
-/// @nodoc
-
-class _$ChatSelectStateImpl implements _ChatSelectState {
- const _$ChatSelectStateImpl();
-
- @override
- String toString() {
- return 'ChatSelectState()';
- }
-
- @override
- bool operator ==(Object other) {
- return identical(this, other) ||
- (other.runtimeType == runtimeType && other is _$ChatSelectStateImpl);
- }
-
- @override
- int get hashCode => runtimeType.hashCode;
-
- @override
- @optionalTypeArgs
- TResult when(
- TResult Function() $default, {
- required TResult Function() loading,
- required TResult Function(String errorMessage) error,
- required TResult Function(List chatRooms) loaded,
- }) {
- return $default();
- }
-
- @override
- @optionalTypeArgs
- TResult? whenOrNull(
- TResult? Function()? $default, {
- TResult? Function()? loading,
- TResult? Function(String errorMessage)? error,
- TResult? Function(List chatRooms)? loaded,
- }) {
- return $default?.call();
- }
-
- @override
- @optionalTypeArgs
- TResult maybeWhen(
- TResult Function()? $default, {
- TResult Function()? loading,
- TResult Function(String errorMessage)? error,
- TResult Function(List chatRooms)? loaded,
- required TResult orElse(),
- }) {
- if ($default != null) {
- return $default();
- }
- return orElse();
- }
-
- @override
- @optionalTypeArgs
- TResult map(
- TResult Function(_ChatSelectState value) $default, {
- required TResult Function(ChatSelectStateLoading value) loading,
- required TResult Function(ChatSelectStateError value) error,
- required TResult Function(ChatSelectStateLoaded value) loaded,
- }) {
- return $default(this);
- }
-
- @override
- @optionalTypeArgs
- TResult? mapOrNull(
- TResult? Function(_ChatSelectState value)? $default, {
- TResult? Function(ChatSelectStateLoading value)? loading,
- TResult? Function(ChatSelectStateError value)? error,
- TResult? Function(ChatSelectStateLoaded value)? loaded,
- }) {
- return $default?.call(this);
- }
-
- @override
- @optionalTypeArgs
- TResult maybeMap(
- TResult Function(_ChatSelectState value)? $default, {
- TResult Function(ChatSelectStateLoading value)? loading,
- TResult Function(ChatSelectStateError value)? error,
- TResult Function(ChatSelectStateLoaded value)? loaded,
- required TResult orElse(),
- }) {
- if ($default != null) {
- return $default(this);
- }
- return orElse();
- }
-}
-
-abstract class _ChatSelectState implements ChatSelectState {
- const factory _ChatSelectState() = _$ChatSelectStateImpl;
-}
-
-/// @nodoc
-abstract class _$$ChatSelectStateLoadingImplCopyWith<$Res> {
- factory _$$ChatSelectStateLoadingImplCopyWith(
- _$ChatSelectStateLoadingImpl value,
- $Res Function(_$ChatSelectStateLoadingImpl) then) =
- __$$ChatSelectStateLoadingImplCopyWithImpl<$Res>;
-}
-
-/// @nodoc
-class __$$ChatSelectStateLoadingImplCopyWithImpl<$Res>
- extends _$ChatSelectStateCopyWithImpl<$Res, _$ChatSelectStateLoadingImpl>
- implements _$$ChatSelectStateLoadingImplCopyWith<$Res> {
- __$$ChatSelectStateLoadingImplCopyWithImpl(
- _$ChatSelectStateLoadingImpl _value,
- $Res Function(_$ChatSelectStateLoadingImpl) _then)
- : super(_value, _then);
-
- /// Create a copy of ChatSelectState
- /// with the given fields replaced by the non-null parameter values.
-}
-
-/// @nodoc
-
-class _$ChatSelectStateLoadingImpl implements ChatSelectStateLoading {
- const _$ChatSelectStateLoadingImpl();
-
- @override
- String toString() {
- return 'ChatSelectState.loading()';
- }
-
- @override
- bool operator ==(Object other) {
- return identical(this, other) ||
- (other.runtimeType == runtimeType &&
- other is _$ChatSelectStateLoadingImpl);
- }
-
- @override
- int get hashCode => runtimeType.hashCode;
-
- @override
- @optionalTypeArgs
- TResult when(
- TResult Function() $default, {
- required TResult Function() loading,
- required TResult Function(String errorMessage) error,
- required TResult Function(List chatRooms) loaded,
- }) {
- return loading();
- }
-
- @override
- @optionalTypeArgs
- TResult? whenOrNull(
- TResult? Function()? $default, {
- TResult? Function()? loading,
- TResult? Function(String errorMessage)? error,
- TResult? Function(List chatRooms)? loaded,
- }) {
- return loading?.call();
- }
-
- @override
- @optionalTypeArgs
- TResult maybeWhen(
- TResult Function()? $default, {
- TResult Function()? loading,
- TResult Function(String errorMessage)? error,
- TResult Function(List chatRooms)? loaded,
- required TResult orElse(),
- }) {
- if (loading != null) {
- return loading();
- }
- return orElse();
- }
-
- @override
- @optionalTypeArgs
- TResult map(
- TResult Function(_ChatSelectState value) $default, {
- required TResult Function(ChatSelectStateLoading value) loading,
- required TResult Function(ChatSelectStateError value) error,
- required TResult Function(ChatSelectStateLoaded value) loaded,
- }) {
- return loading(this);
- }
-
- @override
- @optionalTypeArgs
- TResult? mapOrNull(
- TResult? Function(_ChatSelectState value)? $default, {
- TResult? Function(ChatSelectStateLoading value)? loading,
- TResult? Function(ChatSelectStateError value)? error,
- TResult? Function(ChatSelectStateLoaded value)? loaded,
- }) {
- return loading?.call(this);
- }
-
- @override
- @optionalTypeArgs
- TResult maybeMap(
- TResult Function(_ChatSelectState value)? $default, {
- TResult Function(ChatSelectStateLoading value)? loading,
- TResult Function(ChatSelectStateError value)? error,
- TResult Function(ChatSelectStateLoaded value)? loaded,
- required TResult orElse(),
- }) {
- if (loading != null) {
- return loading(this);
- }
- return orElse();
- }
-}
-
-abstract class ChatSelectStateLoading implements ChatSelectState {
- const factory ChatSelectStateLoading() = _$ChatSelectStateLoadingImpl;
-}
-
-/// @nodoc
-abstract class _$$ChatSelectStateErrorImplCopyWith<$Res> {
- factory _$$ChatSelectStateErrorImplCopyWith(_$ChatSelectStateErrorImpl value,
- $Res Function(_$ChatSelectStateErrorImpl) then) =
- __$$ChatSelectStateErrorImplCopyWithImpl<$Res>;
- @useResult
- $Res call({String errorMessage});
-}
-
-/// @nodoc
-class __$$ChatSelectStateErrorImplCopyWithImpl<$Res>
- extends _$ChatSelectStateCopyWithImpl<$Res, _$ChatSelectStateErrorImpl>
- implements _$$ChatSelectStateErrorImplCopyWith<$Res> {
- __$$ChatSelectStateErrorImplCopyWithImpl(_$ChatSelectStateErrorImpl _value,
- $Res Function(_$ChatSelectStateErrorImpl) _then)
- : super(_value, _then);
-
- /// Create a copy of ChatSelectState
- /// with the given fields replaced by the non-null parameter values.
- @pragma('vm:prefer-inline')
- @override
- $Res call({
- Object? errorMessage = null,
- }) {
- return _then(_$ChatSelectStateErrorImpl(
- errorMessage: null == errorMessage
- ? _value.errorMessage
- : errorMessage // ignore: cast_nullable_to_non_nullable
- as String,
- ));
- }
-}
-
-/// @nodoc
-
-class _$ChatSelectStateErrorImpl implements ChatSelectStateError {
- const _$ChatSelectStateErrorImpl({required this.errorMessage});
-
- @override
- final String errorMessage;
-
- @override
- String toString() {
- return 'ChatSelectState.error(errorMessage: $errorMessage)';
- }
-
- @override
- bool operator ==(Object other) {
- return identical(this, other) ||
- (other.runtimeType == runtimeType &&
- other is _$ChatSelectStateErrorImpl &&
- (identical(other.errorMessage, errorMessage) ||
- other.errorMessage == errorMessage));
- }
-
- @override
- int get hashCode => Object.hash(runtimeType, errorMessage);
-
- /// Create a copy of ChatSelectState
- /// with the given fields replaced by the non-null parameter values.
- @JsonKey(includeFromJson: false, includeToJson: false)
- @override
- @pragma('vm:prefer-inline')
- _$$ChatSelectStateErrorImplCopyWith<_$ChatSelectStateErrorImpl>
- get copyWith =>
- __$$ChatSelectStateErrorImplCopyWithImpl<_$ChatSelectStateErrorImpl>(
- this, _$identity);
-
- @override
- @optionalTypeArgs
- TResult when(
- TResult Function() $default, {
- required TResult Function() loading,
- required TResult Function(String errorMessage) error,
- required TResult Function(List chatRooms) loaded,
- }) {
- return error(errorMessage);
- }
-
- @override
- @optionalTypeArgs
- TResult? whenOrNull(
- TResult? Function()? $default, {
- TResult? Function()? loading,
- TResult? Function(String errorMessage)? error,
- TResult? Function(List chatRooms)? loaded,
- }) {
- return error?.call(errorMessage);
- }
-
- @override
- @optionalTypeArgs
- TResult maybeWhen(
- TResult Function()? $default, {
- TResult Function()? loading,
- TResult Function(String errorMessage)? error,
- TResult Function(List chatRooms)? loaded,
- required TResult orElse(),
- }) {
- if (error != null) {
- return error(errorMessage);
- }
- return orElse();
- }
-
- @override
- @optionalTypeArgs
- TResult map(
- TResult Function(_ChatSelectState value) $default, {
- required TResult Function(ChatSelectStateLoading value) loading,
- required TResult Function(ChatSelectStateError value) error,
- required TResult Function(ChatSelectStateLoaded value) loaded,
- }) {
- return error(this);
- }
-
- @override
- @optionalTypeArgs
- TResult? mapOrNull(
- TResult? Function(_ChatSelectState value)? $default, {
- TResult? Function(ChatSelectStateLoading value)? loading,
- TResult? Function(ChatSelectStateError value)? error,
- TResult? Function(ChatSelectStateLoaded value)? loaded,
- }) {
- return error?.call(this);
- }
-
- @override
- @optionalTypeArgs
- TResult maybeMap(
- TResult Function(_ChatSelectState value)? $default, {
- TResult Function(ChatSelectStateLoading value)? loading,
- TResult Function(ChatSelectStateError value)? error,
- TResult Function(ChatSelectStateLoaded value)? loaded,
- required TResult orElse(),
- }) {
- if (error != null) {
- return error(this);
- }
- return orElse();
- }
-}
-
-abstract class ChatSelectStateError implements ChatSelectState {
- const factory ChatSelectStateError({required final String errorMessage}) =
- _$ChatSelectStateErrorImpl;
-
- String get errorMessage;
-
- /// Create a copy of ChatSelectState
- /// with the given fields replaced by the non-null parameter values.
- @JsonKey(includeFromJson: false, includeToJson: false)
- _$$ChatSelectStateErrorImplCopyWith<_$ChatSelectStateErrorImpl>
- get copyWith => throw _privateConstructorUsedError;
-}
-
-/// @nodoc
-abstract class _$$ChatSelectStateLoadedImplCopyWith<$Res> {
- factory _$$ChatSelectStateLoadedImplCopyWith(
- _$ChatSelectStateLoadedImpl value,
- $Res Function(_$ChatSelectStateLoadedImpl) then) =
- __$$ChatSelectStateLoadedImplCopyWithImpl<$Res>;
- @useResult
- $Res call({List chatRooms});
-}
-
-/// @nodoc
-class __$$ChatSelectStateLoadedImplCopyWithImpl<$Res>
- extends _$ChatSelectStateCopyWithImpl<$Res, _$ChatSelectStateLoadedImpl>
- implements _$$ChatSelectStateLoadedImplCopyWith<$Res> {
- __$$ChatSelectStateLoadedImplCopyWithImpl(_$ChatSelectStateLoadedImpl _value,
- $Res Function(_$ChatSelectStateLoadedImpl) _then)
- : super(_value, _then);
-
- /// Create a copy of ChatSelectState
- /// with the given fields replaced by the non-null parameter values.
- @pragma('vm:prefer-inline')
- @override
- $Res call({
- Object? chatRooms = null,
- }) {
- return _then(_$ChatSelectStateLoadedImpl(
- chatRooms: null == chatRooms
- ? _value._chatRooms
- : chatRooms // ignore: cast_nullable_to_non_nullable
- as List,
- ));
- }
-}
-
-/// @nodoc
-
-class _$ChatSelectStateLoadedImpl implements ChatSelectStateLoaded {
- const _$ChatSelectStateLoadedImpl({required final List chatRooms})
- : _chatRooms = chatRooms;
-
- final List _chatRooms;
- @override
- List get chatRooms {
- if (_chatRooms is EqualUnmodifiableListView) return _chatRooms;
- // ignore: implicit_dynamic_type
- return EqualUnmodifiableListView(_chatRooms);
- }
-
- @override
- String toString() {
- return 'ChatSelectState.loaded(chatRooms: $chatRooms)';
- }
-
- @override
- bool operator ==(Object other) {
- return identical(this, other) ||
- (other.runtimeType == runtimeType &&
- other is _$ChatSelectStateLoadedImpl &&
- const DeepCollectionEquality()
- .equals(other._chatRooms, _chatRooms));
- }
-
- @override
- int get hashCode =>
- Object.hash(runtimeType, const DeepCollectionEquality().hash(_chatRooms));
-
- /// Create a copy of ChatSelectState
- /// with the given fields replaced by the non-null parameter values.
- @JsonKey(includeFromJson: false, includeToJson: false)
- @override
- @pragma('vm:prefer-inline')
- _$$ChatSelectStateLoadedImplCopyWith<_$ChatSelectStateLoadedImpl>
- get copyWith => __$$ChatSelectStateLoadedImplCopyWithImpl<
- _$ChatSelectStateLoadedImpl>(this, _$identity);
-
- @override
- @optionalTypeArgs
- TResult when(
- TResult Function() $default, {
- required TResult Function() loading,
- required TResult Function(String errorMessage) error,
- required TResult Function(List chatRooms) loaded,
- }) {
- return loaded(chatRooms);
- }
-
- @override
- @optionalTypeArgs
- TResult? whenOrNull(
- TResult? Function()? $default, {
- TResult? Function()? loading,
- TResult? Function(String errorMessage)? error,
- TResult? Function(List chatRooms)? loaded,
- }) {
- return loaded?.call(chatRooms);
- }
-
- @override
- @optionalTypeArgs
- TResult maybeWhen(
- TResult Function()? $default, {
- TResult Function()? loading,
- TResult Function(String errorMessage)? error,
- TResult Function(List chatRooms)? loaded,
- required TResult orElse(),
- }) {
- if (loaded != null) {
- return loaded(chatRooms);
- }
- return orElse();
- }
-
- @override
- @optionalTypeArgs
- TResult map(
- TResult Function(_ChatSelectState value) $default, {
- required TResult Function(ChatSelectStateLoading value) loading,
- required TResult Function(ChatSelectStateError value) error,
- required TResult Function(ChatSelectStateLoaded value) loaded,
- }) {
- return loaded(this);
- }
-
- @override
- @optionalTypeArgs
- TResult? mapOrNull(
- TResult? Function(_ChatSelectState value)? $default, {
- TResult? Function(ChatSelectStateLoading value)? loading,
- TResult? Function(ChatSelectStateError value)? error,
- TResult? Function(ChatSelectStateLoaded value)? loaded,
- }) {
- return loaded?.call(this);
- }
-
- @override
- @optionalTypeArgs
- TResult maybeMap(
- TResult Function(_ChatSelectState value)? $default, {
- TResult Function(ChatSelectStateLoading value)? loading,
- TResult Function(ChatSelectStateError value)? error,
- TResult Function(ChatSelectStateLoaded value)? loaded,
- required TResult orElse(),
- }) {
- if (loaded != null) {
- return loaded(this);
- }
- return orElse();
- }
-}
-
-abstract class ChatSelectStateLoaded implements ChatSelectState {
- const factory ChatSelectStateLoaded(
- {required final List chatRooms}) = _$ChatSelectStateLoadedImpl;
-
- List get chatRooms;
-
- /// Create a copy of ChatSelectState
- /// with the given fields replaced by the non-null parameter values.
- @JsonKey(includeFromJson: false, includeToJson: false)
- _$$ChatSelectStateLoadedImplCopyWith<_$ChatSelectStateLoadedImpl>
- get copyWith => throw _privateConstructorUsedError;
-}
diff --git a/lib/features/chat/presentation/bloc/chats_cubit/chats_cubit.dart b/lib/features/chat/presentation/bloc/chats_cubit/chats_cubit.dart
new file mode 100644
index 00000000..f165ca83
--- /dev/null
+++ b/lib/features/chat/presentation/bloc/chats_cubit/chats_cubit.dart
@@ -0,0 +1,144 @@
+import 'dart:async';
+import 'dart:convert';
+
+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';
+import 'package:eatvio_app/features/chat/repositories/chat_repository.dart';
+import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+class ChatsCubit extends Cubit {
+ final ChatRepository chatRepository;
+ final ProfileRepository profileRepository;
+ final WsManager wsManager;
+ late StreamSubscription? newIncomingMessagesSubscription;
+ late StreamSubscription? newLastMessageSubscription;
+
+ ChatsCubit({
+ required this.chatRepository,
+ required this.wsManager,
+ required this.profileRepository,
+ }) : super(ChatsState()) {
+ newIncomingMessagesSubscription = wsManager.newMessagesStream?.listen((event) {
+ _handleNewMessageViaSocket(event.data);
+ });
+
+ newLastMessageSubscription = chatRepository.newUserMessagesStream.listen((message) {
+ _setNewLastMessage(message);
+ });
+ }
+ String? _query;
+ Future getChatRoomsWithLoading({String? query}) async {
+ _query = query;
+ emit(state.copyWith(state: BaseState.loading));
+ try {
+ final chatRooms = await chatRepository.getChatRoomPreviews(query: query);
+ final unreadChats = chatRooms.where(
+ (element) => element.unreadMessagesCount > 0,
+ );
+ emit(
+ state.copyWith(
+ chatRooms: chatRooms,
+ chatsWithUnreadMessagesCount: unreadChats.length,
+ state: BaseState.success,
+ ),
+ );
+ } catch (e) {
+ emit(
+ state.copyWith(errorMessage: e.toString(), state: BaseState.error),
+ );
+ }
+ }
+
+ Future getChatRooms({String? query}) async {
+ _query = query;
+ try {
+ final chatRooms = await chatRepository.getChatRoomPreviews(query: query);
+ final unreadChats = chatRooms.where(
+ (element) => element.unreadMessagesCount > 0,
+ );
+ emit(
+ state.copyWith(
+ chatRooms: chatRooms,
+ chatsWithUnreadMessagesCount: unreadChats.length,
+ state: BaseState.success,
+ ),
+ );
+ } catch (e) {
+ emit(
+ state.copyWith(errorMessage: e.toString(), state: BaseState.error),
+ );
+ }
+ }
+
+ void reduceUnreadMessagesCount({required String chatId, required int count}) {
+ bool isChatRead = false;
+ final updatedChatRooms = state.chatRooms.map((room) {
+ if (room.id == chatId) {
+ int unreadMessagesCount = room.unreadMessagesCount - count;
+ if (unreadMessagesCount <= 0) {
+ unreadMessagesCount = 0;
+ isChatRead = true;
+ }
+ return room.copyWith(unreadMessagesCount: unreadMessagesCount);
+ }
+ return room;
+ }).toList();
+ emit(
+ state.copyWith(
+ chatRooms: updatedChatRooms,
+ chatsWithUnreadMessagesCount: isChatRead ? state.chatsWithUnreadMessagesCount - 1 : state.chatsWithUnreadMessagesCount,
+ ),
+ );
+ }
+
+ Future _handleNewMessageViaSocket(dynamic data) async {
+ final json = jsonDecode(data);
+ final message = ChatMessageModel.fromJson(json['message']);
+
+ final currentChatRooms = [...state.chatRooms];
+
+ bool wasChanged = false;
+ bool willIncrementUnreadChats = false;
+ final updatedChatRooms = currentChatRooms.map((chatRoom) {
+ if (chatRoom.id == message.roomId) {
+ wasChanged = true;
+ if (chatRoom.unreadMessagesCount == 0) {
+ willIncrementUnreadChats = true;
+ }
+ return chatRoom.copyWith(lastMessage: message, unreadMessagesCount: chatRoom.unreadMessagesCount + 1);
+ }
+ return chatRoom;
+ }).toList();
+
+ if (!wasChanged && _query == null) {
+ getChatRooms();
+ } else {
+ emit(
+ state.copyWith(
+ chatRooms: updatedChatRooms,
+ chatsWithUnreadMessagesCount: willIncrementUnreadChats ? state.chatsWithUnreadMessagesCount + 1 : state.chatsWithUnreadMessagesCount,
+ ),
+ );
+ }
+ }
+
+ void _setNewLastMessage(ChatMessageModel message) {
+ final chats = state.chatRooms.map((chat) {
+ if (chat.id == message.roomId) {
+ return chat.copyWith(lastMessage: message);
+ }
+ return chat;
+ }).toList();
+
+ emit(state.copyWith(chatRooms: chats));
+ }
+
+ @override
+ Future close() {
+ newIncomingMessagesSubscription?.cancel();
+ return super.close();
+ }
+}
diff --git a/lib/features/chat/presentation/bloc/chats_cubit/chats_state.dart b/lib/features/chat/presentation/bloc/chats_cubit/chats_state.dart
new file mode 100644
index 00000000..24bde4fa
--- /dev/null
+++ b/lib/features/chat/presentation/bloc/chats_cubit/chats_state.dart
@@ -0,0 +1,17 @@
+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';
+
+part 'chats_state.freezed.dart';
+
+@freezed
+class ChatsState with _$ChatsState {
+
+ factory ChatsState({
+ @Default(BaseState.initial) BaseState state,
+ @Default(0) int chatsWithUnreadMessagesCount,
+ @Default([]) List chatRooms,
+ @Default(null) String? errorMessage,
+ }) = _ChatsState;
+
+}
\ No newline at end of file
diff --git a/lib/features/chat/presentation/bloc/chats_cubit/chats_state.freezed.dart b/lib/features/chat/presentation/bloc/chats_cubit/chats_state.freezed.dart
new file mode 100644
index 00000000..77fd911d
--- /dev/null
+++ b/lib/features/chat/presentation/bloc/chats_cubit/chats_state.freezed.dart
@@ -0,0 +1,228 @@
+// 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 'chats_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 _$ChatsState {
+ BaseState get state => throw _privateConstructorUsedError;
+ int get chatsWithUnreadMessagesCount => throw _privateConstructorUsedError;
+ List get chatRooms => throw _privateConstructorUsedError;
+ String? get errorMessage => throw _privateConstructorUsedError;
+
+ /// Create a copy of ChatsState
+ /// with the given fields replaced by the non-null parameter values.
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ $ChatsStateCopyWith get copyWith =>
+ throw _privateConstructorUsedError;
+}
+
+/// @nodoc
+abstract class $ChatsStateCopyWith<$Res> {
+ factory $ChatsStateCopyWith(
+ ChatsState value, $Res Function(ChatsState) then) =
+ _$ChatsStateCopyWithImpl<$Res, ChatsState>;
+ @useResult
+ $Res call(
+ {BaseState state,
+ int chatsWithUnreadMessagesCount,
+ List chatRooms,
+ String? errorMessage});
+}
+
+/// @nodoc
+class _$ChatsStateCopyWithImpl<$Res, $Val extends ChatsState>
+ implements $ChatsStateCopyWith<$Res> {
+ _$ChatsStateCopyWithImpl(this._value, this._then);
+
+ // ignore: unused_field
+ final $Val _value;
+ // ignore: unused_field
+ final $Res Function($Val) _then;
+
+ /// Create a copy of ChatsState
+ /// with the given fields replaced by the non-null parameter values.
+ @pragma('vm:prefer-inline')
+ @override
+ $Res call({
+ Object? state = null,
+ Object? chatsWithUnreadMessagesCount = null,
+ Object? chatRooms = null,
+ Object? errorMessage = freezed,
+ }) {
+ return _then(_value.copyWith(
+ state: null == state
+ ? _value.state
+ : state // ignore: cast_nullable_to_non_nullable
+ as BaseState,
+ chatsWithUnreadMessagesCount: null == chatsWithUnreadMessagesCount
+ ? _value.chatsWithUnreadMessagesCount
+ : chatsWithUnreadMessagesCount // ignore: cast_nullable_to_non_nullable
+ as int,
+ chatRooms: null == chatRooms
+ ? _value.chatRooms
+ : chatRooms // ignore: cast_nullable_to_non_nullable
+ as List,
+ errorMessage: freezed == errorMessage
+ ? _value.errorMessage
+ : errorMessage // ignore: cast_nullable_to_non_nullable
+ as String?,
+ ) as $Val);
+ }
+}
+
+/// @nodoc
+abstract class _$$ChatsStateImplCopyWith<$Res>
+ implements $ChatsStateCopyWith<$Res> {
+ factory _$$ChatsStateImplCopyWith(
+ _$ChatsStateImpl value, $Res Function(_$ChatsStateImpl) then) =
+ __$$ChatsStateImplCopyWithImpl<$Res>;
+ @override
+ @useResult
+ $Res call(
+ {BaseState state,
+ int chatsWithUnreadMessagesCount,
+ List chatRooms,
+ String? errorMessage});
+}
+
+/// @nodoc
+class __$$ChatsStateImplCopyWithImpl<$Res>
+ extends _$ChatsStateCopyWithImpl<$Res, _$ChatsStateImpl>
+ implements _$$ChatsStateImplCopyWith<$Res> {
+ __$$ChatsStateImplCopyWithImpl(
+ _$ChatsStateImpl _value, $Res Function(_$ChatsStateImpl) _then)
+ : super(_value, _then);
+
+ /// Create a copy of ChatsState
+ /// with the given fields replaced by the non-null parameter values.
+ @pragma('vm:prefer-inline')
+ @override
+ $Res call({
+ Object? state = null,
+ Object? chatsWithUnreadMessagesCount = null,
+ Object? chatRooms = null,
+ Object? errorMessage = freezed,
+ }) {
+ return _then(_$ChatsStateImpl(
+ state: null == state
+ ? _value.state
+ : state // ignore: cast_nullable_to_non_nullable
+ as BaseState,
+ chatsWithUnreadMessagesCount: null == chatsWithUnreadMessagesCount
+ ? _value.chatsWithUnreadMessagesCount
+ : chatsWithUnreadMessagesCount // ignore: cast_nullable_to_non_nullable
+ as int,
+ chatRooms: null == chatRooms
+ ? _value._chatRooms
+ : chatRooms // ignore: cast_nullable_to_non_nullable
+ as List,
+ errorMessage: freezed == errorMessage
+ ? _value.errorMessage
+ : errorMessage // ignore: cast_nullable_to_non_nullable
+ as String?,
+ ));
+ }
+}
+
+/// @nodoc
+
+class _$ChatsStateImpl implements _ChatsState {
+ _$ChatsStateImpl(
+ {this.state = BaseState.initial,
+ this.chatsWithUnreadMessagesCount = 0,
+ final List chatRooms = const [],
+ this.errorMessage = null})
+ : _chatRooms = chatRooms;
+
+ @override
+ @JsonKey()
+ final BaseState state;
+ @override
+ @JsonKey()
+ final int chatsWithUnreadMessagesCount;
+ final List _chatRooms;
+ @override
+ @JsonKey()
+ List get chatRooms {
+ if (_chatRooms is EqualUnmodifiableListView) return _chatRooms;
+ // ignore: implicit_dynamic_type
+ return EqualUnmodifiableListView(_chatRooms);
+ }
+
+ @override
+ @JsonKey()
+ final String? errorMessage;
+
+ @override
+ String toString() {
+ return 'ChatsState(state: $state, chatsWithUnreadMessagesCount: $chatsWithUnreadMessagesCount, chatRooms: $chatRooms, errorMessage: $errorMessage)';
+ }
+
+ @override
+ bool operator ==(Object other) {
+ return identical(this, other) ||
+ (other.runtimeType == runtimeType &&
+ other is _$ChatsStateImpl &&
+ (identical(other.state, state) || other.state == state) &&
+ (identical(other.chatsWithUnreadMessagesCount,
+ chatsWithUnreadMessagesCount) ||
+ other.chatsWithUnreadMessagesCount ==
+ chatsWithUnreadMessagesCount) &&
+ const DeepCollectionEquality()
+ .equals(other._chatRooms, _chatRooms) &&
+ (identical(other.errorMessage, errorMessage) ||
+ other.errorMessage == errorMessage));
+ }
+
+ @override
+ int get hashCode => Object.hash(
+ runtimeType,
+ state,
+ chatsWithUnreadMessagesCount,
+ const DeepCollectionEquality().hash(_chatRooms),
+ errorMessage);
+
+ /// Create a copy of ChatsState
+ /// with the given fields replaced by the non-null parameter values.
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ @override
+ @pragma('vm:prefer-inline')
+ _$$ChatsStateImplCopyWith<_$ChatsStateImpl> get copyWith =>
+ __$$ChatsStateImplCopyWithImpl<_$ChatsStateImpl>(this, _$identity);
+}
+
+abstract class _ChatsState implements ChatsState {
+ factory _ChatsState(
+ {final BaseState state,
+ final int chatsWithUnreadMessagesCount,
+ final List chatRooms,
+ final String? errorMessage}) = _$ChatsStateImpl;
+
+ @override
+ BaseState get state;
+ @override
+ int get chatsWithUnreadMessagesCount;
+ @override
+ List get chatRooms;
+ @override
+ String? get errorMessage;
+
+ /// Create a copy of ChatsState
+ /// with the given fields replaced by the non-null parameter values.
+ @override
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ _$$ChatsStateImplCopyWith<_$ChatsStateImpl> get copyWith =>
+ throw _privateConstructorUsedError;
+}
diff --git a/lib/features/chat/presentation/pages/chat_screen.dart b/lib/features/chat/presentation/pages/chat_screen.dart
index d446e4b2..4144fbd5 100644
--- a/lib/features/chat/presentation/pages/chat_screen.dart
+++ b/lib/features/chat/presentation/pages/chat_screen.dart
@@ -1,9 +1,12 @@
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';
+import 'package:eatvio_app/features/chat/presentation/bloc/chats_cubit/chats_cubit.dart';
import 'package:eatvio_app/features/chat/presentation/widgets/chat_bubble_widget.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/theme/custom_text_style.dart';
@@ -12,12 +15,14 @@ import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:intl/intl.dart';
+import 'package:visibility_detector/visibility_detector.dart';
class ChatScreen extends StatefulWidget {
final String chatId;
-
+ final ChatsCubit chatsCubit;
const ChatScreen({
required this.chatId,
+ required this.chatsCubit,
super.key,
});
@@ -29,6 +34,8 @@ class _ChatScreenState extends State {
final scrollController = ScrollController();
final TextEditingController textEditingController = TextEditingController();
final ChatRoomCubit chatRoomCubit = sl();
+ final List<({int id, int index})> unreadMessagesIds = [];
+ final List<({int id, int index})> unreadMessagesIdsCopy = [];
@override
void initState() {
@@ -60,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(
@@ -152,25 +157,65 @@ class _ChatScreenState extends State {
(context, index) {
if (index < state.chatMessages.length) {
if ((index + 1 != state.chatMessages.length &&
- DateTime.parse(state.chatMessages[index].updatedAt).day != DateTime.parse(state.chatMessages[index + 1].updatedAt).day) ||
+ state.chatMessages[index].updatedAt.day != state.chatMessages[index + 1].updatedAt.day) ||
index + 1 == state.chatMessages.length) {
+ final isCurrentUserAuthor = state.chatRoom.users['to_chat_user']!.id != state.chatMessages[index].authorId;
return Column(
children: [
_messageDate(state.chatMessages[index].updatedAt),
- ChatBubbleWidget(
- sendTime: state.chatMessages[index].updatedAt,
- text: state.chatMessages[index].data.trim(),
- isCurrentUser: state.chatRoom.users['to_chat_user']!.id != state.chatMessages[index].authorId,
- mediaData: state.chatMessages[index].mediaData ?? [],
+ VisibilityDetector(
+ key: Key(state.chatMessages[index].id.toString()),
+ onVisibilityChanged: (VisibilityInfo info) {
+ if (!state.chatMessages[index].isRead && !isCurrentUserAuthor && info.visibleFraction > 0) {
+ unreadMessagesIdsCopy.add((id: state.chatMessages[index].id, index: index));
+ EasyDebounce.debounce(
+ 'mark_as_read',
+ Duration(milliseconds: 500),
+ () async {
+ final result = await context.read().markMessagesAsRead(state.chatRoom.id, unreadMessagesIdsCopy);
+ if (!result) return;
+ widget.chatsCubit.reduceUnreadMessagesCount(chatId: state.chatRoom.id, count: unreadMessagesIdsCopy.length);
+ unreadMessagesIdsCopy.clear();
+ },
+ );
+ }
+ },
+ child: ChatBubbleWidget(
+ isRead: state.chatMessages[index].isRead,
+ sendTime: state.chatMessages[index].updatedAt,
+ text: state.chatMessages[index].data.trim(),
+ isCurrentUser: isCurrentUserAuthor,
+ mediaData: state.chatMessages[index].mediaData ?? [],
+ ),
),
],
);
}
- return ChatBubbleWidget(
- sendTime: state.chatMessages[index].updatedAt,
- text: state.chatMessages[index].data.trim(),
- isCurrentUser: state.chatRoom.users['to_chat_user']!.id != state.chatMessages[index].authorId,
- mediaData: state.chatMessages[index].mediaData ?? [],
+ final isCurrentUserAuthor = state.chatRoom.users['to_chat_user']!.id != state.chatMessages[index].authorId;
+ return VisibilityDetector(
+ key: Key(state.chatMessages[index].id.toString()),
+ onVisibilityChanged: (VisibilityInfo info) {
+ if (!state.chatMessages[index].isRead && !isCurrentUserAuthor && info.visibleFraction > 0) {
+ unreadMessagesIds.add((id: state.chatMessages[index].id, index: index));
+ EasyDebounce.debounce(
+ 'mark_as_read',
+ Duration(milliseconds: 500),
+ () async {
+ final result = await context.read().markMessagesAsRead(state.chatRoom.id, unreadMessagesIds);
+ if (!result) return;
+ widget.chatsCubit.reduceUnreadMessagesCount(chatId: state.chatRoom.id, count: unreadMessagesIds.length);
+ unreadMessagesIds.clear();
+ },
+ );
+ }
+ },
+ child: ChatBubbleWidget(
+ isRead: state.chatMessages[index].isRead,
+ sendTime: state.chatMessages[index].updatedAt,
+ text: state.chatMessages[index].data.trim(),
+ isCurrentUser: isCurrentUserAuthor,
+ mediaData: state.chatMessages[index].mediaData ?? [],
+ ),
);
} else {
return Padding(
@@ -222,7 +267,7 @@ class _ChatScreenState extends State {
if (result != null) {
List files = result.paths.map((path) => File(path!)).toList();
- chatRoomCubit.sendMessage(
+ await chatRoomCubit.sendMessage(
chatId: widget.chatId,
files: files,
);
@@ -329,13 +374,13 @@ class _ChatScreenState extends State {
);
}
- _messageDate(String date) {
+ _messageDate(DateTime date) {
return Center(
child: Padding(
- padding: const EdgeInsets.symmetric(vertical: 8),
+ padding: const EdgeInsets.only(top: 32),
child: Text(
DateFormat.yMMMMd('RU').format(
- DateTime.parse(date),
+ date,
),
style: const TextStyle(
height: 1,
diff --git a/lib/features/chat/presentation/pages/chat_select_screen.dart b/lib/features/chat/presentation/pages/chat_select_screen.dart
deleted file mode 100644
index ce286468..00000000
--- a/lib/features/chat/presentation/pages/chat_select_screen.dart
+++ /dev/null
@@ -1,113 +0,0 @@
-import 'package:easy_debounce/easy_debounce.dart';
-import 'package:eatvio_app/core/widgets/loader.dart';
-import 'package:eatvio_app/features/chat/presentation/bloc/chat_select_cubit/chat_select_cubit.dart';
-import 'package:eatvio_app/features/chat/presentation/bloc/chat_select_cubit/chat_select_state.dart';
-import 'package:eatvio_app/features/chat/presentation/widgets/chat_entry_widget.dart';
-import 'package:eatvio_app/features/chat/presentation/widgets/chat_search_bar_widget.dart';
-import 'package:eatvio_app/core/locator_service.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter_bloc/flutter_bloc.dart';
-
-class ChatSelectScreen extends StatefulWidget {
- const ChatSelectScreen({Key? key}) : super(key: key);
-
- @override
- State createState() => _ChatSelectScreenState();
-}
-
-class _ChatSelectScreenState extends State {
- final ChatSelectCubit chatSelectCubit = sl();
- final searchController = TextEditingController();
-
- Widget build(BuildContext context) {
- return BlocProvider(
- create: (context) => chatSelectCubit..getChatRooms(),
- child: BlocBuilder(
- builder: (context, state) {
- return Scaffold(
- appBar: AppBar(
- backgroundColor: Colors.white,
- toolbarHeight: 0,
- ),
- body: NestedScrollView(
- headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
- return [
- SliverPadding(
- padding: const EdgeInsets.only(top: 8),
- sliver: SliverAppBar(
- automaticallyImplyLeading: false,
- stretchTriggerOffset: 1,
- scrolledUnderElevation: 0,
- floating: true,
- snap: true,
- title: Padding(
- padding: const EdgeInsets.only(top: 8),
- child: ChatSearchBarWidget(
- onSuffixIconTap: () => context.read().getChatRooms(),
- onChanged: (query) {
- if (query.length > 2) {
- EasyDebounce.debounce(
- 'searchChatRoom',
- Duration(milliseconds: 300),
- () => context.read().getChatRooms(query: query),
- );
- } else {
- context.read().getChatRooms();
- }
- },
- controller: searchController,
- fillColor: Color(0xFFF0F2F5),
- hintText: "Поиск",
- hintStyle: TextStyle(
- color: Color(0xFF7B8794),
- ),
- ),
- ),
- ),
- ),
- ];
- },
- body: stateBuilder(state),
- ),
- );
- },
- ),
- );
- }
-
- stateBuilder(state) {
- if (state is ChatSelectStateLoading) {
- return Scaffold(
- body: const Loader(),
- );
- } else if (state is ChatSelectStateLoaded) {
- return _buildChatList(context, state);
- } else if (state is ChatSelectStateError) {
- return Padding(
- padding: const EdgeInsets.all(16),
- child: Center(child: Text(state.errorMessage)),
- );
- } else {
- return Scaffold(
- body: const Loader(),
- );
- }
- }
-}
-
-_buildChatList(BuildContext context, ChatSelectStateLoaded state) {
- return ListView.separated(
- padding: EdgeInsets.all(16),
- shrinkWrap: true,
- physics: NeverScrollableScrollPhysics(),
- separatorBuilder: (context, index) {
- return SizedBox(
- height: 8,
- );
- },
- itemCount: state.chatRooms.length,
- itemBuilder: (context, index) {
- return ChatEntryWidget(chatRoom: state.chatRooms[index]);
- },
- );
-}
diff --git a/lib/features/chat/presentation/pages/chats_screen.dart b/lib/features/chat/presentation/pages/chats_screen.dart
new file mode 100644
index 00000000..18f2b581
--- /dev/null
+++ b/lib/features/chat/presentation/pages/chats_screen.dart
@@ -0,0 +1,109 @@
+import 'package:easy_debounce/easy_debounce.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';
+import 'package:eatvio_app/features/chat/presentation/widgets/chat_entry_widget.dart';
+import 'package:eatvio_app/features/chat/presentation/widgets/chat_search_bar_widget.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+class ChatsScreen extends StatefulWidget {
+ const ChatsScreen({Key? key}) : super(key: key);
+
+ @override
+ State createState() => _ChatsScreenState();
+}
+
+class _ChatsScreenState extends State {
+ final searchController = TextEditingController();
+
+ Widget build(BuildContext context) {
+ return BlocBuilder(
+ builder: (context, state) {
+ return Scaffold(
+ appBar: AppBar(
+ backgroundColor: Colors.white,
+ toolbarHeight: 0,
+ ),
+ body: NestedScrollView(
+ headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
+ return [
+ SliverPadding(
+ padding: const EdgeInsets.only(top: 8),
+ sliver: SliverAppBar(
+ automaticallyImplyLeading: false,
+ stretchTriggerOffset: 1,
+ scrolledUnderElevation: 0,
+ floating: true,
+ snap: true,
+ title: Padding(
+ padding: const EdgeInsets.only(top: 8),
+ child: ChatSearchBarWidget(
+ onSuffixIconTap: () => context.read().getChatRooms(),
+ onChanged: (query) {
+ if (query.length > 2) {
+ EasyDebounce.debounce(
+ 'searchChatRoom',
+ Duration(milliseconds: 300),
+ () => context.read().getChatRooms(query: query),
+ );
+ } else {
+ context.read().getChatRooms();
+ }
+ },
+ controller: searchController,
+ fillColor: Color(0xFFF0F2F5),
+ hintText: "Поиск",
+ hintStyle: TextStyle(
+ color: Color(0xFF7B8794),
+ ),
+ ),
+ ),
+ ),
+ ),
+ ];
+ },
+ body: stateBuilder(state),
+ ),
+ );
+ },
+ );
+ }
+
+ stateBuilder(ChatsState state) {
+ if (state.state == BaseState.loading) {
+ return Scaffold(
+ body: const Loader(),
+ );
+ } else if (state.state == BaseState.success) {
+ return _buildChatList(context, state);
+ } else if (state.state == BaseState.error) {
+ return Scaffold(
+ body: DataErrorWidget(onReload: () => context.read().getChatRooms()),
+ );
+ } else {
+ return Scaffold(
+ body: const Loader(),
+ );
+ }
+ }
+
+ _buildChatList(BuildContext context, ChatsState state) {
+ return ListView.separated(
+ padding: EdgeInsets.all(16),
+ shrinkWrap: true,
+ physics: NeverScrollableScrollPhysics(),
+ separatorBuilder: (context, index) {
+ return SizedBox(
+ height: 8,
+ );
+ },
+ itemCount: state.chatRooms.length,
+ itemBuilder: (context, index) {
+ return ChatEntryWidget(chatRoom: state.chatRooms[index]);
+ },
+ );
+ }
+}
diff --git a/lib/features/chat/presentation/widgets/chat_bubble_widget.dart b/lib/features/chat/presentation/widgets/chat_bubble_widget.dart
index 11c44530..195f19de 100644
--- a/lib/features/chat/presentation/widgets/chat_bubble_widget.dart
+++ b/lib/features/chat/presentation/widgets/chat_bubble_widget.dart
@@ -15,118 +15,146 @@ class ChatBubbleWidget extends StatelessWidget {
required this.text,
required this.isCurrentUser,
required this.mediaData,
+ required this.isRead,
});
- final String sendTime;
+ final DateTime sendTime;
final String text;
final bool isCurrentUser;
+ final bool isRead;
final List mediaData;
@override
Widget build(BuildContext context) {
- return Padding(
- padding: EdgeInsets.fromLTRB(isCurrentUser ? 64 : 16, 4, isCurrentUser ? 16 : 64, 4),
- child: Align(
- alignment: isCurrentUser ? Alignment.centerRight : Alignment.centerLeft,
- child: DecoratedBox(
- decoration: BoxDecoration(
- color: isCurrentUser ? appTheme.gray50 : appTheme.gray60004,
- borderRadius: mediaData.length > 0
- ? BorderRadius.circular(24)
- : BorderRadius.horizontal(
- left: isCurrentUser ? Radius.circular(32) : Radius.circular(16),
- right: isCurrentUser ? Radius.circular(16) : Radius.circular(24),
- ),
- ),
- child: Padding(
- padding: mediaData.length > 0 ? EdgeInsets.all(8) : EdgeInsets.only(top: 8, bottom: 8, left: isCurrentUser ? 16 : 8, right: isCurrentUser ? 8 : 16),
- child: Column(
- crossAxisAlignment: isCurrentUser ? CrossAxisAlignment.end : CrossAxisAlignment.start,
- children: [
- if (mediaData.isNotEmpty)
- CustomScrollView(
- shrinkWrap: true,
- physics: NeverScrollableScrollPhysics(),
- slivers: [
- SliverList.separated(
- itemBuilder: (BuildContext context, int index) {
- if (!mediaData[index].toString().contains('.png') &&
- !mediaData[index].toString().contains('.jpg') &&
- !mediaData[index].toString().contains('.jpeg')) {
- return ListTile(
- contentPadding: EdgeInsets.all(4),
- horizontalTitleGap: 8,
- onTap: () async {
- if (!await launchUrl(Uri.parse(baseUrl + '/storage/' + mediaData[index].toString()))) {
- throw Exception('Could not launch ${mediaData[index].toString()}');
- }
- },
- leading: Icon(
- Icons.file_copy_outlined,
- color: Colors.white,
- ),
- trailing: Icon(
- Icons.download_outlined,
- color: Colors.white,
- ),
- title: Text(
- mediaData[index].toString().replaceAll('chat_files/', ''),
- maxLines: 2,
- style: TextStyle(
- color: Colors.white,
- fontSize: 12,
- overflow: TextOverflow.ellipsis,
- ),
- ),
- );
- }
- return GestureDetector(
- onTap: () {
- context.pushTransparentRoute(
- PhotoViewScreen(
- imageUrls: mediaData.map((e) => baseUrl + '/storage/' + e.toString()).toList(),
- index: index,
- ),
- );
- },
- child: Hero(
- tag: baseUrl + '/storage/' + '${mediaData[index]}',
- transitionOnUserGestures: true,
- child: ClipRRect(
- borderRadius: BorderRadius.circular(16),
- child: CachedNetworkImage(
- imageUrl: baseUrl + '/storage/' + '${mediaData[index]}',
- ),
- ),
- ),
- );
- },
- separatorBuilder: (BuildContext context, int index) => const SizedBox(height: 8),
- itemCount: mediaData.length,
- ),
- ],
- ),
- if (mediaData.length > 0 && text.isNotEmpty) SizedBox(height: 8),
- if (text.isNotEmpty)
- Text(
- text,
- style: TextStyles.textFS13FW400.copyWith(color: appTheme.black, fontSize: 16),
- ),
- SizedBox(height: 8),
- Padding(
- padding: mediaData.length > 0 ? const EdgeInsets.only(right: 8, top: 4, left: 8) : EdgeInsets.zero,
- child: Text(
- DateTime.parse(sendTime).format('hh:mm'),
- style: TextStyle(
- color: Color(0xFF7B8794),
- height: 1,
- fontSize: 10,
- ),
+ final size = MediaQuery.sizeOf(context);
+ return Container(
+ width: size.width,
+ child: Padding(
+ padding: EdgeInsets.fromLTRB(isCurrentUser ? 64 : 16, 10, isCurrentUser ? 16 : 64, 4),
+ child: Row(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.end,
+ mainAxisAlignment: isCurrentUser ? MainAxisAlignment.end : MainAxisAlignment.start,
+ children: [
+ if (isCurrentUser)
+ Padding(
+ padding: const EdgeInsets.only(right: 4, bottom: 4),
+ child: Text(
+ sendTime.format('hh:mm'),
+ style: TextStyle(
+ color: Color(0xFF7B8794),
+ height: 1,
+ fontSize: 10,
),
),
- ],
+ ),
+ Container(
+ constraints: BoxConstraints(maxWidth: size.width * 0.7),
+ decoration: BoxDecoration(
+ color: isCurrentUser ? appTheme.gray50 : appTheme.gray60004,
+ borderRadius: mediaData.length > 0
+ ? BorderRadius.circular(24)
+ : BorderRadius.horizontal(
+ left: isCurrentUser ? Radius.circular(32) : Radius.circular(16),
+ right: isCurrentUser ? Radius.circular(16) : Radius.circular(24),
+ ),
+ ),
+ child: Padding(
+ padding:
+ mediaData.isNotEmpty ? EdgeInsets.all(8) : EdgeInsets.only(top: 8, bottom: 8, left: isCurrentUser ? 16 : 8, right: isCurrentUser ? 8 : 16),
+ child: Column(
+ crossAxisAlignment: isCurrentUser ? CrossAxisAlignment.end : CrossAxisAlignment.start,
+ children: [
+ if (mediaData.isNotEmpty)
+ SizedBox(
+ width: size.width * 0.7,
+ child: CustomScrollView(
+ shrinkWrap: true,
+ physics: NeverScrollableScrollPhysics(),
+ slivers: [
+ SliverList.separated(
+ itemBuilder: (BuildContext context, int index) {
+ if (!mediaData[index].toString().contains('.png') &&
+ !mediaData[index].toString().contains('.jpg') &&
+ !mediaData[index].toString().contains('.jpeg')) {
+ return ListTile(
+ contentPadding: EdgeInsets.all(4),
+ horizontalTitleGap: 8,
+ onTap: () async {
+ if (!await launchUrl(Uri.parse(baseUrl + '/storage/' + mediaData[index].toString()))) {
+ throw Exception('Could not launch ${mediaData[index].toString()}');
+ }
+ },
+ leading: Icon(
+ Icons.file_copy_outlined,
+ color: Colors.white,
+ ),
+ trailing: Icon(
+ Icons.download_outlined,
+ color: Colors.white,
+ ),
+ title: Text(
+ mediaData[index].toString().replaceAll('chat_files/', ''),
+ maxLines: 2,
+ style: TextStyle(
+ color: Colors.white,
+ fontSize: 12,
+ overflow: TextOverflow.ellipsis,
+ ),
+ ),
+ );
+ }
+ return GestureDetector(
+ onTap: () {
+ context.pushTransparentRoute(
+ PhotoViewScreen(
+ imageUrls: mediaData.map((e) => baseUrl + '/storage/' + e.toString()).toList(),
+ index: index,
+ ),
+ );
+ },
+ child: Hero(
+ tag: baseUrl + '/storage/' + '${mediaData[index]}',
+ transitionOnUserGestures: true,
+ child: ClipRRect(
+ borderRadius: BorderRadius.circular(16),
+ child: CachedNetworkImage(
+ imageUrl: baseUrl + '/storage/' + '${mediaData[index]}',
+ ),
+ ),
+ ),
+ );
+ },
+ separatorBuilder: (BuildContext context, int index) => const SizedBox(height: 8),
+ itemCount: mediaData.length,
+ ),
+ ],
+ ),
+ ),
+ if (mediaData.isNotEmpty && text.isNotEmpty) SizedBox(height: 8),
+ if (text.isNotEmpty)
+ Text(
+ text,
+ style: TextStyles.textFS13FW400.copyWith(color: appTheme.black, fontSize: 16),
+ ),
+ ],
+ ),
+ ),
),
- ),
+ if (!isCurrentUser)
+ Padding(
+ padding: const EdgeInsets.only(left: 4, bottom: 4),
+ child: Text(
+ sendTime.format('hh:mm'),
+ style: TextStyle(
+ color: Color(0xFF7B8794),
+ height: 1,
+ fontSize: 10,
+ fontWeight: FontWeight.w500,
+ ),
+ ),
+ ),
+ ],
),
),
);
diff --git a/lib/features/chat/presentation/widgets/chat_entry_widget.dart b/lib/features/chat/presentation/widgets/chat_entry_widget.dart
index 34b13c94..61e6ea17 100644
--- a/lib/features/chat/presentation/widgets/chat_entry_widget.dart
+++ b/lib/features/chat/presentation/widgets/chat_entry_widget.dart
@@ -2,7 +2,9 @@ import 'package:eatvio_app/core/utils/date_time_utils.dart';
import 'package:eatvio_app/features/chat/models/chat_room_preview_model.dart';
import 'package:eatvio_app/core/theme/custom_text_style.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
+import 'package:eatvio_app/features/chat/presentation/bloc/chats_cubit/chats_cubit.dart';
import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
class ChatEntryWidget extends StatefulWidget {
@@ -18,26 +20,19 @@ class ChatEntryWidget extends StatefulWidget {
}
class _ChatEntryWidgetState extends State {
- LastMessageClass? lastMessage;
-
- @override
- void initState() {
- super.initState();
- if (widget.chatRoom.lastMessage is Map) {
- lastMessage = LastMessageClass.fromJson(widget.chatRoom.lastMessage);
- }
- }
-
@override
Widget build(BuildContext context) {
return InkWell(
borderRadius: BorderRadius.circular(16),
onTap: () => GoRouter.of(context).pushNamed(
'chatScreen',
- extra: widget.chatRoom.id.toString(),
+ extra: {
+ 'chatId': widget.chatRoom.id.toString(),
+ 'chatsCubit': context.read(),
+ },
),
child: Ink(
- padding: EdgeInsets.all(16),
+ padding: EdgeInsets.all(12),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16),
color: Color(0xFFF6FAFF),
@@ -54,39 +49,54 @@ class _ChatEntryWidgetState extends State {
backgroundColor: Colors.white,
),
const SizedBox(width: 12),
- Expanded(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Row(
- children: [
- Text(
- widget.chatRoom.users.toChatUser.name.length <= 20
- ? widget.chatRoom.users.toChatUser.name
- : widget.chatRoom.users.toChatUser.name.substring(0, 20) + '...',
- style: theme.textTheme.titleMedium,
- ),
- Spacer(),
- if (lastMessage != null)
- Padding(
- padding: EdgeInsets.only(left: 8),
- child: Text(
- lastMessage!.updatedAt.format('dd MMM', 'ru'),
- style: theme.textTheme.bodyMedium,
- ),
- ),
- ],
- ),
- SizedBox(height: 8),
- if (lastMessage != null)
- Text(
- lastMessage!.data.toString().trim(),
- maxLines: 2,
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ widget.chatRoom.users.toChatUser.name.length <= 20
+ ? widget.chatRoom.users.toChatUser.name
+ : widget.chatRoom.users.toChatUser.name.substring(0, 20) + '...',
+ style: theme.textTheme.titleMedium,
+ ),
+ SizedBox(height: 8),
+ if (widget.chatRoom.lastMessage != null)
+ SizedBox(
+ width: MediaQuery.sizeOf(context).width * 0.5,
+ child: Text(
+ widget.chatRoom.lastMessage!.data.toString().trim(),
+ maxLines: 1,
overflow: TextOverflow.ellipsis,
style: CustomTextStyles.bodyMediumBluegray800,
),
- ],
- ),
+ ),
+ ],
+ ),
+ Spacer(),
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.end,
+ children: [
+ if (widget.chatRoom.lastMessage != null)
+ Padding(
+ padding: EdgeInsets.only(left: 8),
+ child: Text(
+ widget.chatRoom.lastMessage!.updatedAt.format('dd MMM', 'ru'),
+ style: theme.textTheme.bodyMedium,
+ ),
+ ),
+ SizedBox(height: 4),
+ if (widget.chatRoom.unreadMessagesCount > 0)
+ Container(
+ padding: EdgeInsets.symmetric(horizontal: 8, vertical: 6),
+ decoration: BoxDecoration(
+ shape: BoxShape.circle,
+ color: appTheme.blueA400,
+ ),
+ child: Text(
+ widget.chatRoom.unreadMessagesCount.toString(),
+ style: TextStyles.textXSSemibold.copyWith(color: appTheme.whiteA700),
+ ),
+ ),
+ ],
),
],
),
diff --git a/lib/features/chat/repositories/chat_repository.dart b/lib/features/chat/repositories/chat_repository.dart
index 67b49b73..9458d9f5 100644
--- a/lib/features/chat/repositories/chat_repository.dart
+++ b/lib/features/chat/repositories/chat_repository.dart
@@ -1,6 +1,8 @@
+import 'dart:async';
import 'dart:io';
import 'package:dio/dio.dart';
+import 'package:eatvio_app/features/chat/models/chat_message_model.dart';
import 'package:eatvio_app/features/chat/models/chat_messages_model.dart';
import 'package:eatvio_app/features/chat/models/chat_room_model.dart';
import 'package:eatvio_app/features/chat/models/chat_room_preview_model.dart';
@@ -10,10 +12,18 @@ import 'package:http_parser/http_parser.dart';
class ChatRepository {
final ChatService chatService;
+ final _newUserMessagesController = StreamController();
+
+ Stream get newUserMessagesStream => _newUserMessagesController.stream;
+
ChatRepository({
required this.chatService,
});
+ void emitNewMessageToChats(ChatMessageModel message) {
+ _newUserMessagesController.add(message);
+ }
+
Future sendMessage({
required String chatId,
String? data,
@@ -66,7 +76,7 @@ class ChatRepository {
} catch (e) {
rethrow;
}
- }
+ }
Future getChatRoomIdByUserSlug({required String slug}) async {
try {
@@ -75,4 +85,12 @@ class ChatRepository {
rethrow;
}
}
+
+ Future markMessagesAsRead(String chatId, List messageIds) async {
+ try {
+ await chatService.markMessagesAsRead(id: chatId, body: {'messages_ids': messageIds});
+ } catch (e) {
+ rethrow;
+ }
+ }
}
diff --git a/lib/features/chat/services/chat_service.dart b/lib/features/chat/services/chat_service.dart
index 499f2584..2eb2423f 100644
--- a/lib/features/chat/services/chat_service.dart
+++ b/lib/features/chat/services/chat_service.dart
@@ -38,4 +38,7 @@ abstract class ChatService {
Future getChatRoomIdByUserSlug({
@Path() required String user,
});
+
+ @POST('/api/chat/{id}/messages/markAsRead')
+ Future markMessagesAsRead({@Path() required String id, @Body() required Map> body});
}
diff --git a/lib/features/chat/services/chat_service.g.dart b/lib/features/chat/services/chat_service.g.dart
index 25c122bc..4cd570df 100644
--- a/lib/features/chat/services/chat_service.g.dart
+++ b/lib/features/chat/services/chat_service.g.dart
@@ -167,6 +167,29 @@ class _ChatService implements ChatService {
return _value;
}
+ @override
+ Future markMessagesAsRead({
+ required String id,
+ required Map> body,
+ }) async {
+ final _extra = {};
+ final queryParameters = {};
+ final _headers = {};
+ final _data = {};
+ _data.addAll(body);
+ final _options = _setStreamType(
+ Options(method: 'POST', headers: _headers, extra: _extra)
+ .compose(
+ _dio.options,
+ '/api/chat/${id}/messages/markAsRead',
+ 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 ||
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