From 93899e316fbfbb7b11fc5cad32a3db7ddb98263a Mon Sep 17 00:00:00 2001 From: Username1548 Date: Thu, 20 Feb 2025 16:41:07 +0700 Subject: [PATCH] added unread messages and unread chats counters --- lib/core/app_router.dart | 14 +- lib/core/const.dart | 4 +- .../initialization_cubit.dart | 0 lib/core/locator_service.dart | 8 +- lib/core/theme/custom_text_style.dart | 4 +- lib/core/utils/enums.dart | 7 + .../ws_manager.dart | 2 +- .../ws_service.dart | 0 .../ws_service.g.dart | 0 .../success_published_diet_bottom_sheet.dart | 3 +- .../notifications_cubit.dart | 2 +- .../widgets/bottom_navigation_bar_widget.dart | 14 +- .../chat/models/chat_message_model.dart | 6 +- .../models/chat_message_model.freezed.dart | 115 ++- .../chat/models/chat_message_model.g.dart | 10 +- .../chat/models/chat_room_preview_model.dart | 22 +- .../chat_room_preview_model.freezed.dart | 412 ++-------- .../models/chat_room_preview_model.g.dart | 28 +- .../bloc/chat_room_cubit/chat_room_cubit.dart | 50 +- .../chat_select_cubit/chat_select_cubit.dart | 27 - .../chat_select_cubit/chat_select_state.dart | 19 - .../chat_select_state.freezed.dart | 646 ---------------- .../bloc/chats_cubit/chats_cubit.dart | 144 ++++ .../bloc/chats_cubit/chats_state.dart | 17 + .../bloc/chats_cubit/chats_state.freezed.dart | 228 ++++++ .../chat/presentation/pages/chat_screen.dart | 79 +- .../pages/chat_select_screen.dart | 113 --- .../chat/presentation/pages/chats_screen.dart | 109 +++ .../widgets/chat_bubble_widget.dart | 230 +++--- .../widgets/chat_entry_widget.dart | 92 ++- .../chat/repositories/chat_repository.dart | 20 +- lib/features/chat/services/chat_service.dart | 3 + .../chat/services/chat_service.g.dart | 23 + .../presentation/create_diet_screen.dart | 52 +- .../feed/presentation/pages/feed_screen.dart | 13 +- .../presentation/cubit/my_diets_cubit.dart | 24 +- .../presentation/my_diets_screen.dart | 5 +- .../widgets/diet_deleted_bottom_sheet.dart | 57 ++ .../widgets/diet_drafetd_bottom_sheet.dart | 59 ++ .../presentation/widgets/my_diet_widget.dart | 83 +- lib/generated/intl/messages_en.dart | 725 +++++++++--------- lib/generated/l10n.dart | 187 ++++- lib/l10n/intl_en.arb | 3 +- lib/main.dart | 2 + pubspec.yaml | 7 +- 45 files changed, 1784 insertions(+), 1884 deletions(-) delete mode 100644 lib/core/initialization_cubit/initialization_cubit.dart rename lib/core/{initialization_cubit => websockets}/ws_manager.dart (98%) rename lib/core/{initialization_cubit => websockets}/ws_service.dart (100%) rename lib/core/{initialization_cubit => websockets}/ws_service.g.dart (100%) rename lib/{features/feed/presentation => core}/widgets/success_published_diet_bottom_sheet.dart (93%) delete mode 100644 lib/features/chat/presentation/bloc/chat_select_cubit/chat_select_cubit.dart delete mode 100644 lib/features/chat/presentation/bloc/chat_select_cubit/chat_select_state.dart delete mode 100644 lib/features/chat/presentation/bloc/chat_select_cubit/chat_select_state.freezed.dart create mode 100644 lib/features/chat/presentation/bloc/chats_cubit/chats_cubit.dart create mode 100644 lib/features/chat/presentation/bloc/chats_cubit/chats_state.dart create mode 100644 lib/features/chat/presentation/bloc/chats_cubit/chats_state.freezed.dart delete mode 100644 lib/features/chat/presentation/pages/chat_select_screen.dart create mode 100644 lib/features/chat/presentation/pages/chats_screen.dart create mode 100644 lib/features/my_diets/presentation/widgets/diet_deleted_bottom_sheet.dart create mode 100644 lib/features/my_diets/presentation/widgets/diet_drafetd_bottom_sheet.dart diff --git a/lib/core/app_router.dart b/lib/core/app_router.dart index 4226b718..b1fc54a8 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/auth_screen/auth_screen.dart'; @@ -6,8 +6,9 @@ import 'package:eatvio_app/features/blog_post/presentation/pages/blog_post_scree import 'package:eatvio_app/features/blog_post_create/presentation/pages/blog_post_create_screen.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'; @@ -112,7 +113,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', @@ -204,7 +205,12 @@ 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', 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/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 cf873dd2..d6344124 100644 --- a/lib/core/locator_service.dart +++ b/lib/core/locator_service.dart @@ -1,7 +1,7 @@ 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/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'; @@ -18,7 +18,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'; @@ -90,7 +90,7 @@ Future init() async { 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())); diff --git a/lib/core/theme/custom_text_style.dart b/lib/core/theme/custom_text_style.dart index 6837e186..4ee16600 100644 --- a/lib/core/theme/custom_text_style.dart +++ b/lib/core/theme/custom_text_style.dart @@ -230,10 +230,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/utils/enums.dart b/lib/core/utils/enums.dart index 9e6eca73..97413565 100644 --- a/lib/core/utils/enums.dart +++ b/lib/core/utils/enums.dart @@ -3,3 +3,10 @@ enum ProductType { plan, recipe } + +enum BaseState { + loading, + error, + initial, + success +} \ No newline at end of file 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/features/feed/presentation/widgets/success_published_diet_bottom_sheet.dart b/lib/core/widgets/success_published_diet_bottom_sheet.dart similarity index 93% 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..a3857bf4 100644 --- a/lib/features/feed/presentation/widgets/success_published_diet_bottom_sheet.dart +++ b/lib/core/widgets/success_published_diet_bottom_sheet.dart @@ -1,3 +1,4 @@ +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'; @@ -34,7 +35,7 @@ class SuccessPublishedDietBottomSheet extends StatelessWidget { padding: const EdgeInsets.symmetric(horizontal: 20), child: CustomOutlinedButton( onPressed: () { - GoRouter.of(context).pop(); + AppRouter.navigatorKey.currentContext?.pop(); }, text: 'Хорошо', height: 52, 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 1ee52bf2..7274a3ca 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 @@ -6,6 +6,8 @@ import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/noti import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/notifications_cubit/notifications_state.dart'; import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart'; import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_state.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'; @@ -136,15 +138,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..980b233f 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,11 @@ import 'dart:convert'; +import 'dart:developer'; import 'dart:io'; import 'package:audioplayers/audioplayers.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'; @@ -87,10 +88,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 +128,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..f4425e7e --- /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/utils/enums.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..f7f74987 --- /dev/null +++ b/lib/features/chat/presentation/bloc/chats_cubit/chats_state.dart @@ -0,0 +1,17 @@ +import 'package:eatvio_app/core/utils/enums.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..527ce84d 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:async'; import 'dart:io'; import 'dart:math'; +import 'package:easy_debounce/easy_debounce.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() { @@ -152,25 +159,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 +269,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 +376,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..186dc890 --- /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/utils/enums.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 Padding( + padding: const EdgeInsets.all(16), + child: Center(child: Text(state.errorMessage ?? '')), + ); + } 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..0fe691c5 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,15 +20,6 @@ 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) { @@ -34,10 +27,13 @@ class _ChatEntryWidgetState extends State { 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 +50,51 @@ 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, - ), - ), - ], + 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) + Text( + widget.chatRoom.lastMessage!.data.toString().trim(), + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: CustomTextStyles.bodyMediumBluegray800, ), - SizedBox(height: 8), - if (lastMessage != null) - Text( - lastMessage!.data.toString().trim(), - maxLines: 2, - 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/create_diet/presentation/create_diet_screen.dart b/lib/features/create_diet/presentation/create_diet_screen.dart index 6cd1ea03..3afc21d0 100644 --- a/lib/features/create_diet/presentation/create_diet_screen.dart +++ b/lib/features/create_diet/presentation/create_diet_screen.dart @@ -2,12 +2,12 @@ import 'package:eatvio_app/core/locator_service.dart'; import 'package:eatvio_app/core/theme/custom_text_style.dart'; import 'package:eatvio_app/core/theme/theme_helper.dart'; import 'package:eatvio_app/core/utils/localization_utils.dart'; -import 'package:eatvio_app/core/widgets/bottom_sheet_handler_bar.dart'; import 'package:eatvio_app/core/widgets/bottom_sheet_with_blur.dart'; import 'package:eatvio_app/core/widgets/custom_outlined_button.dart'; import 'package:eatvio_app/core/widgets/custom_text_form_field.dart'; import 'package:eatvio_app/core/widgets/loader.dart'; import 'package:eatvio_app/core/widgets/main_background_bouble.dart'; +import 'package:eatvio_app/core/widgets/success_published_diet_bottom_sheet.dart'; import 'package:eatvio_app/features/create_diet/presentation/cubit/create_diet_cubit.dart'; import 'package:eatvio_app/features/create_diet/presentation/cubit/create_diet_state.dart'; import 'package:eatvio_app/features/create_diet/presentation/widgets/day_widget.dart'; @@ -48,6 +48,7 @@ class CreateDietScreenContent extends StatefulWidget { class _CreateDietScreenContentState extends State { final nameController = TextEditingController(); final descriptionController = TextEditingController(); + final scaffoldKey = GlobalKey(); @override void initState() { @@ -65,49 +66,22 @@ class _CreateDietScreenContentState extends State { builder: (context, state) { if (state.published) { Future.delayed(Duration(milliseconds: 500), () { - context.pop('${state.dietData.name ?? 'Диета'}'); + context.pop(); + showBottomSheetWithBlur( + userRootNavigator: true, + context: context, + child: SuccessPublishedDietBottomSheet( + title: '${state.dietData.name ?? 'Диета'}', + ), + ); }); } else if (state.drafted) { Future.delayed(Duration(milliseconds: 500), () { - context.pop('${state.dietData.name ?? 'Диета'}'); + context.pop(); showBottomSheetWithBlur( context: context, - child: Column( - children: [ - SizedBox( - height: 20, - ), - BottomSheetHandlerBar(), - SizedBox( - height: 20, - ), - Text( - '${state.dietData.name ?? 'Диета'} сохраненна в черновиках', - textAlign: TextAlign.center, - style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black), - ), - SizedBox( - height: 20, - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: CustomOutlinedButton( - onPressed: () { - Navigator.of(context, rootNavigator: true).pop(); - }, - text: 'Хорошо', - height: 52, - buttonTextStyle: TextStyles.textSSemibold.copyWith(color: appTheme.whiteA700), - buttonStyle: ButtonStyle( - side: WidgetStatePropertyAll(BorderSide.none), - backgroundColor: WidgetStatePropertyAll(appTheme.blueA400), - ), - ), - ), - SizedBox( - height: 20, - ), - ], + child: SuccessPublishedDietBottomSheet( + title: '${state.dietData.name ?? 'Диета'} сохраненна в черновиках', ), ); }); diff --git a/lib/features/feed/presentation/pages/feed_screen.dart b/lib/features/feed/presentation/pages/feed_screen.dart index a6b8aff7..a42bab2f 100644 --- a/lib/features/feed/presentation/pages/feed_screen.dart +++ b/lib/features/feed/presentation/pages/feed_screen.dart @@ -1,6 +1,5 @@ import 'package:easy_debounce/easy_debounce.dart'; import 'package:eatvio_app/core/widgets/add_item_button.dart'; -import 'package:eatvio_app/core/widgets/bottom_sheet_with_blur.dart'; import 'package:eatvio_app/core/widgets/custom_search_bar.dart'; import 'package:eatvio_app/features/create_diet/presentation/create_diet_screen.dart'; import 'package:eatvio_app/features/create_plan/presentation/create_plan_screen.dart'; @@ -15,7 +14,6 @@ import 'package:eatvio_app/features/feed/presentation/widgets/chip_group.dart'; import 'package:eatvio_app/features/feed/presentation/widgets/consultants.dart'; import 'package:eatvio_app/features/feed/presentation/widgets/diets.dart'; import 'package:eatvio_app/features/feed/presentation/widgets/plans.dart'; -import 'package:eatvio_app/features/feed/presentation/widgets/success_published_diet_bottom_sheet.dart'; import 'package:eatvio_app/features/feed/presentation/widgets/tags_modal_widget.dart'; import 'package:eatvio_app/features/feed/presentation/bloc/plans_cubit/plans_cubit.dart'; import 'package:eatvio_app/core/locator_service.dart'; @@ -263,16 +261,7 @@ class _FeedScreenContentState extends State { 2 => AddItemButton( onTap: state.profile?.roles.contains('consultant') ?? false ? () async { - final result = await GoRouter.of(context).pushNamed(CreateDietScreen.name); - if (result != null) { - showBottomSheetWithBlur( - userRootNavigator: true, - context: context, - child: SuccessPublishedDietBottomSheet( - title: result, - ), - ); - } + await GoRouter.of(context).pushNamed(CreateDietScreen.name); } : null, ), diff --git a/lib/features/my_diets/presentation/cubit/my_diets_cubit.dart b/lib/features/my_diets/presentation/cubit/my_diets_cubit.dart index 7a86efcc..4e17fcce 100644 --- a/lib/features/my_diets/presentation/cubit/my_diets_cubit.dart +++ b/lib/features/my_diets/presentation/cubit/my_diets_cubit.dart @@ -278,8 +278,7 @@ class MyDietsCubit extends Cubit { Future deleteDiet(int dietId) async { try { - await myDietsRepository.deleteDiet(dietId); - await loadAllDiets(); + await myDietsRepository.deleteDiet(dietId); return true; } catch (e) { emit( @@ -289,16 +288,29 @@ class MyDietsCubit extends Cubit { } } - Future publishDiet(int dietId) async { + Future publishDiet(int dietId) async { try { await createDietRepository.updateDiet( dietId: dietId, state: DietState.active, - ); - return null; + ); + return true; } catch (e, st) { log(e.toString(), stackTrace: st); - return 'Произошла непредвиденная ошибка'; + return false; + } + } + + Future unpublishDiet(int dietId) async { + try { + await createDietRepository.updateDiet( + dietId: dietId, + state: DietState.draft, + ); + return true; + } catch (e, st) { + log(e.toString(), stackTrace: st); + return false; } } } diff --git a/lib/features/my_diets/presentation/my_diets_screen.dart b/lib/features/my_diets/presentation/my_diets_screen.dart index a58f3bdc..40c77356 100644 --- a/lib/features/my_diets/presentation/my_diets_screen.dart +++ b/lib/features/my_diets/presentation/my_diets_screen.dart @@ -164,7 +164,10 @@ class MyDietsScreenContent extends StatelessWidget { shrinkWrap: true, physics: BouncingScrollPhysics(), itemBuilder: (context, index) { - return MyDietWidget(diet: diets[index]); + return MyDietWidget( + diet: diets[index], + key: ValueKey(diets[index].id), + ); }, separatorBuilder: (context, index) { return SizedBox(height: 20); diff --git a/lib/features/my_diets/presentation/widgets/diet_deleted_bottom_sheet.dart b/lib/features/my_diets/presentation/widgets/diet_deleted_bottom_sheet.dart new file mode 100644 index 00000000..e9561def --- /dev/null +++ b/lib/features/my_diets/presentation/widgets/diet_deleted_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 DietDeletedBottomSheet extends StatelessWidget { + const DietDeletedBottomSheet({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( + title, + style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black), + ), + Text( + s.deleted, + style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black), + ), + SizedBox( + height: 20, + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: CustomOutlinedButton( + onPressed: () { + AppRouter.navigatorKey.currentContext?.pop(); + }, + text: 'Хорошо', + height: 52, + buttonTextStyle: TextStyles.textSSemibold.copyWith(color: appTheme.whiteA700), + buttonStyle: ButtonStyle( + side: WidgetStatePropertyAll(BorderSide.none), + backgroundColor: WidgetStatePropertyAll(appTheme.blueA400), + ), + ), + ), + SizedBox( + height: 20, + ), + ], + ); + } +} diff --git a/lib/features/my_diets/presentation/widgets/diet_drafetd_bottom_sheet.dart b/lib/features/my_diets/presentation/widgets/diet_drafetd_bottom_sheet.dart new file mode 100644 index 00000000..4bcffd1c --- /dev/null +++ b/lib/features/my_diets/presentation/widgets/diet_drafetd_bottom_sheet.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/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 DietDraftedBottomSheet extends StatelessWidget { + const DietDraftedBottomSheet({super.key, required this.title}); + final String title; + @override + Widget build(BuildContext context) { + final s = S.of(context); + return Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox( + height: 20, + ), + BottomSheetHandlerBar(), + SizedBox( + height: 20, + ), + Text( + title, + style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black), + ), + Text( + s.diet_has_been_sent_to_draft, + textAlign: TextAlign.center, + 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: 'Хорошо', + 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/my_diets/presentation/widgets/my_diet_widget.dart b/lib/features/my_diets/presentation/widgets/my_diet_widget.dart index 496a3dea..b5a32136 100644 --- a/lib/features/my_diets/presentation/widgets/my_diet_widget.dart +++ b/lib/features/my_diets/presentation/widgets/my_diet_widget.dart @@ -1,16 +1,21 @@ import 'package:cached_network_image/cached_network_image.dart'; +import 'package:eatvio_app/core/app_router.dart'; import 'package:eatvio_app/core/const.dart'; import 'package:eatvio_app/core/theme/custom_text_style.dart'; import 'package:eatvio_app/core/theme/theme_helper.dart'; import 'package:eatvio_app/core/utils/enums.dart'; +import 'package:eatvio_app/core/utils/toasts.dart'; import 'package:eatvio_app/core/widgets/bottom_sheet_handler_bar.dart'; import 'package:eatvio_app/core/widgets/bottom_sheet_with_blur.dart'; import 'package:eatvio_app/core/widgets/custom_outlined_button.dart'; import 'package:eatvio_app/core/widgets/info_row.dart'; +import 'package:eatvio_app/core/widgets/success_published_diet_bottom_sheet.dart'; import 'package:eatvio_app/features/create_diet/presentation/create_diet_screen.dart'; import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart'; import 'package:eatvio_app/features/my_diets/presentation/cubit/my_diets_cubit.dart'; import 'package:eatvio_app/features/my_diets/presentation/cubit/my_diets_state.dart'; +import 'package:eatvio_app/features/my_diets/presentation/widgets/diet_deleted_bottom_sheet.dart'; +import 'package:eatvio_app/features/my_diets/presentation/widgets/diet_drafetd_bottom_sheet.dart'; import 'package:eatvio_app/generated/l10n.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -60,12 +65,7 @@ class _MyDietWidgetState extends State { bottom: 16, child: SizedBox( width: MediaQuery.sizeOf(context).width, - child: InfoRow( - diet: widget.diet, - itemsBackgroundColor: appTheme.whiteA700, - disableDescriptionOfTypes: true, - type: ProductType.diet - ), + child: InfoRow(diet: widget.diet, itemsBackgroundColor: appTheme.whiteA700, disableDescriptionOfTypes: true, type: ProductType.diet), ), ), ], @@ -113,9 +113,18 @@ class _MyDietWidgetState extends State { children: [ Expanded( child: CustomOutlinedButton( - onPressed: () { + onPressed: () async { if (state.selectedStateIndex == 1) { - context.read().publishDiet(widget.diet.id); + final result = await context.read().publishDiet(widget.diet.id); + if (result) { + showBottomSheetWithBlur( + context: context, + child: SuccessPublishedDietBottomSheet(title: '${widget.diet.name}'), + ); + await context.read().loadAllDiets(); + } else { + showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время'); + } } else if (state.selectedStateIndex == 2) { } else { GoRouter.of(context).pushNamed(CreateDietScreen.name, extra: widget.diet); @@ -171,8 +180,19 @@ class _MyDietWidgetState extends State { children: [ Expanded( child: CustomOutlinedButton( - onPressed: () { + onPressed: () async { if (state.selectedStateIndex == 0) { + final result = await context.read().unpublishDiet(widget.diet.id); + if (result) { + showBottomSheetWithBlur( + context: context, + height: 250, + child: DietDraftedBottomSheet(title: '${widget.diet.name}'), + ); + await context.read().loadAllDiets(); + } else { + showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время'); + } } else { GoRouter.of(context).pushNamed(CreateDietScreen.name, extra: widget.diet); } @@ -194,50 +214,13 @@ class _MyDietWidgetState extends State { CustomOutlinedButton( onPressed: () async { final result = await context.read().deleteDiet(widget.diet.id); - if (result) + if (result) { showBottomSheetWithBlur( context: context, - child: Column( - children: [ - SizedBox( - height: 20, - ), - BottomSheetHandlerBar(), - SizedBox( - height: 20, - ), - Text( - '${widget.diet.name}', - style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black), - ), - Text( - s.deleted, - style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black), - ), - SizedBox( - height: 20, - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: CustomOutlinedButton( - onPressed: () { - context.pop(); - }, - text: 'Хорошо', - height: 52, - buttonTextStyle: TextStyles.textSSemibold.copyWith(color: appTheme.whiteA700), - buttonStyle: ButtonStyle( - side: WidgetStatePropertyAll(BorderSide.none), - backgroundColor: WidgetStatePropertyAll(appTheme.blueA400), - ), - ), - ), - SizedBox( - height: 20, - ), - ], - ), + child: DietDeletedBottomSheet(title: widget.diet.name), ); + await context.read().loadAllDiets(); + } }, height: 52, width: 52, diff --git a/lib/generated/intl/messages_en.dart b/lib/generated/intl/messages_en.dart index 2bcea0e1..4f013f2a 100644 --- a/lib/generated/intl/messages_en.dart +++ b/lib/generated/intl/messages_en.dart @@ -22,381 +22,352 @@ class MessageLookup extends MessageLookupByLibrary { final messages = _notInlinedMessages(_notInlinedMessages); static Map _notInlinedMessages(_) => { - "Moscow": MessageLookupByLibrary.simpleMessage("Москва"), - "Russia": MessageLookupByLibrary.simpleMessage("Россия"), - "about_diet": MessageLookupByLibrary.simpleMessage("Об этой диете"), - "about_plan": MessageLookupByLibrary.simpleMessage("Об этом плане"), - "active": MessageLookupByLibrary.simpleMessage("Активен"), - "active_diet_state": MessageLookupByLibrary.simpleMessage("Активная"), - "add": MessageLookupByLibrary.simpleMessage("Добавить"), - "add_cover": MessageLookupByLibrary.simpleMessage("Добавьте обложку"), - "add_measure": MessageLookupByLibrary.simpleMessage("Добавить замер"), - "adding_plan": MessageLookupByLibrary.simpleMessage("Добавление плана"), - "additional_information": MessageLookupByLibrary.simpleMessage( - "Дополнительная информация", - ), - "admin": MessageLookupByLibrary.simpleMessage("Админ"), - "all": MessageLookupByLibrary.simpleMessage("Все"), - "allergies_presence": MessageLookupByLibrary.simpleMessage( - "Наличие аллергии", - ), - "another_form_of_comment": MessageLookupByLibrary.simpleMessage( - "комментария", - ), - "another_form_of_day": MessageLookupByLibrary.simpleMessage("дня"), - "answer": MessageLookupByLibrary.simpleMessage("Ответить"), - "archived": MessageLookupByLibrary.simpleMessage("В архиве"), - "authentificate": MessageLookupByLibrary.simpleMessage("Войти"), - "average_pressure": MessageLookupByLibrary.simpleMessage( - "Средние показатели давления:", - ), - "bodymass_chart": MessageLookupByLibrary.simpleMessage("График ИМТ"), - "by_default": MessageLookupByLibrary.simpleMessage("По умолчянию"), - "calories": MessageLookupByLibrary.simpleMessage("Калории"), - "cancel_diet": MessageLookupByLibrary.simpleMessage("Отменить диету?"), - "carb_type": MessageLookupByLibrary.simpleMessage("Мучное"), - "carb_type_desc": MessageLookupByLibrary.simpleMessage( - "Если вы любите мучные продукты, но хотите придерживаться диеты, вам подходит диета с добавленим мучных блюд на основе цельнозерновых вариантов. В них больше сложных углеводов и клетчатки, что способствует длительному чувству сытости и поддерживает здоровье пищеварительной системы.", - ), - "carbohydrates": MessageLookupByLibrary.simpleMessage("Углеводы"), - "chat": MessageLookupByLibrary.simpleMessage("Чат"), - "chest": MessageLookupByLibrary.simpleMessage("Грудь"), - "chest_circumference": MessageLookupByLibrary.simpleMessage("Обхват груди"), - "choose_diet_duration": MessageLookupByLibrary.simpleMessage( - "Выберите срок диеты", - ), - "choose_plan_duration": MessageLookupByLibrary.simpleMessage( - "Выберите срок плана", - ), - "choose_start_diet_date": MessageLookupByLibrary.simpleMessage( - "Выберите дату старты диеты. Она отобразиться у Вас в дневнике", - ), - "circumference_empty": MessageLookupByLibrary.simpleMessage( - "Все замеры обязательны к заполнению", - ), - "circumference_is_not_a_number": MessageLookupByLibrary.simpleMessage( - "Все замеры должны быть числами", - ), - "circumferences_chart": MessageLookupByLibrary.simpleMessage( - "График обхватов", - ), - "city": MessageLookupByLibrary.simpleMessage("Город"), - "city_of_living": MessageLookupByLibrary.simpleMessage("Город проживания"), - "comfort": MessageLookupByLibrary.simpleMessage("Комфорт"), - "comment": MessageLookupByLibrary.simpleMessage("комментарий"), - "comments": MessageLookupByLibrary.simpleMessage("Комментарии"), - "comments_plural": MessageLookupByLibrary.simpleMessage("комментариев"), - "complain": MessageLookupByLibrary.simpleMessage("Пожаловаться"), - "confirm": MessageLookupByLibrary.simpleMessage("Подтвердить"), - "continueButton": MessageLookupByLibrary.simpleMessage("Продолжить"), - "continue_": MessageLookupByLibrary.simpleMessage("Продолжить"), - "copied": MessageLookupByLibrary.simpleMessage("cкопирован"), - "copy_link": MessageLookupByLibrary.simpleMessage("Скопировать ссылку"), - "could_not_cancel_diet": MessageLookupByLibrary.simpleMessage( - "Не удалось отменить диету", - ), - "country": MessageLookupByLibrary.simpleMessage("Страна"), - "create_diet": MessageLookupByLibrary.simpleMessage("Создание диеты"), - "customer_price": MessageLookupByLibrary.simpleMessage( - "Цена для покупателя", - ), - "date_of_birth": MessageLookupByLibrary.simpleMessage("Дата рождения"), - "day": MessageLookupByLibrary.simpleMessage("День"), - "days": MessageLookupByLibrary.simpleMessage("дней"), - "deleted": MessageLookupByLibrary.simpleMessage("Удалена"), - "diary": MessageLookupByLibrary.simpleMessage("Дневник"), - "diet": MessageLookupByLibrary.simpleMessage("Диета"), - "diet_author": MessageLookupByLibrary.simpleMessage("Автор диеты"), - "diet_plan": MessageLookupByLibrary.simpleMessage("План диеты"), - "diet_started": MessageLookupByLibrary.simpleMessage("Диета уже начата"), - "diet_state": MessageLookupByLibrary.simpleMessage("Статус диеты"), - "diet_type": MessageLookupByLibrary.simpleMessage("Тип диеты"), - "do_you_have_hypertension_tendency": MessageLookupByLibrary.simpleMessage( - "Есть ли у вас гипертония?", - ), - "do_you_have_hypotension_tendency": MessageLookupByLibrary.simpleMessage( - "Есть ли у вас гипотония?", - ), - "do_you_take_pills": MessageLookupByLibrary.simpleMessage( - "Принимаете ли вы лекарства или бады?", - ), - "down_pressure": MessageLookupByLibrary.simpleMessage( - "Нижние показатели давления:", - ), - "draft": MessageLookupByLibrary.simpleMessage("Черновик"), - "drafted": MessageLookupByLibrary.simpleMessage("Черновики"), - "eating_behavior": MessageLookupByLibrary.simpleMessage( - "Пищевое поведение", - ), - "eating_behavior_type": MessageLookupByLibrary.simpleMessage( - "Формирование пищевого поведения", - ), - "eating_behavior_type_desc": MessageLookupByLibrary.simpleMessage( - "Формирование здорового пищевого поведения — это процесс, который включает в себя изменение привычек и отношения к еде. Он направлен на то, чтобы помочь людям научиться правильно питаться и поддерживать здоровый образ жизни.Основные принципы формирования здорового пищевого поведения: \n● Разнообразие продуктов; \n● Сбалансированный рацион; \n● Питьевой режим; \n● Ограничение вредных продуктов; \n● Регулярные приёмы пищи; \n● Умеренность;Осознанное питание; \n● Физическая активность;", - ), - "economy": MessageLookupByLibrary.simpleMessage("Эконом"), - "edit": MessageLookupByLibrary.simpleMessage("Редактировать"), - "email": MessageLookupByLibrary.simpleMessage("Электронная почта"), - "email_empty": MessageLookupByLibrary.simpleMessage( - "Необходимо ввести адрес электронной почты", - ), - "email_invalid": MessageLookupByLibrary.simpleMessage( - "Некорректный адрес электронной почты", - ), - "enter_additional_information": MessageLookupByLibrary.simpleMessage( - "Введите любую дополнительную информацию на своё усмотрение:", - ), - "enter_diet_description": MessageLookupByLibrary.simpleMessage( - "Введите описание диеты", - ), - "enter_diet_name": MessageLookupByLibrary.simpleMessage( - "Выведите название диеты", - ), - "enter_information": MessageLookupByLibrary.simpleMessage( - "Введите информацию...", - ), - "exit": MessageLookupByLibrary.simpleMessage("Выйти"), - "expand": MessageLookupByLibrary.simpleMessage("Показать полностью"), - "fats": MessageLookupByLibrary.simpleMessage("Жиры"), - "favorite_foods": MessageLookupByLibrary.simpleMessage("Любимые продукты"), - "favorite_unfavorite_foods": MessageLookupByLibrary.simpleMessage( - "Ваши любимые/нелюбимые продукты питания?", - ), - "female": MessageLookupByLibrary.simpleMessage("Женский"), - "fill_in_information": MessageLookupByLibrary.simpleMessage( - "Заполните информацию:", - ), - "finish": MessageLookupByLibrary.simpleMessage("Завершить"), - "first_name": MessageLookupByLibrary.simpleMessage("Имя"), - "first_name_empty": MessageLookupByLibrary.simpleMessage( - "Имя обязательно для заполнения", - ), - "first_name_short": MessageLookupByLibrary.simpleMessage( - "В имени должен быть минимум 1 символ", - ), - "fit_type": MessageLookupByLibrary.simpleMessage("Поддержание формы"), - "fit_type_desc": MessageLookupByLibrary.simpleMessage( - "План питания для поддержания формы сбалансированный и разнообразный, чтобы обеспечить организм всеми необходимыми питательными веществами. Общие принципы: \n● Дробное питание; \n● Разнообразие продуктов; \n● Контроль порций; \n● Питьевой режим; \n● Ограничение вредных продуктов;", - ), - "fitness": MessageLookupByLibrary.simpleMessage("Поддержка формы"), - "from": MessageLookupByLibrary.simpleMessage("от"), - "from_a_to_z": MessageLookupByLibrary.simpleMessage("Название: А-Я"), - "from_z_to_a": MessageLookupByLibrary.simpleMessage("Название: Я-А"), - "gallery": MessageLookupByLibrary.simpleMessage("Галерея"), - "general_info": MessageLookupByLibrary.simpleMessage("Общая информация"), - "gr": MessageLookupByLibrary.simpleMessage("гр"), - "grams": MessageLookupByLibrary.simpleMessage("гр"), - "has_alergy": MessageLookupByLibrary.simpleMessage("Наличие аллергии"), - "has_hyper": MessageLookupByLibrary.simpleMessage( - "Склонность к гипертонии", - ), - "has_hypo": MessageLookupByLibrary.simpleMessage("Склонность к гипотонии"), - "height": MessageLookupByLibrary.simpleMessage("Рост"), - "height_measurements": MessageLookupByLibrary.simpleMessage("Замер роста"), - "hide": MessageLookupByLibrary.simpleMessage("Свернуть"), - "hip_circumference": MessageLookupByLibrary.simpleMessage("Обхват бедер"), - "hips": MessageLookupByLibrary.simpleMessage("Бедра"), - "in_progress": MessageLookupByLibrary.simpleMessage("В процессе"), - "inactive_diet_state": MessageLookupByLibrary.simpleMessage("Черновик"), - "initial_measurements": MessageLookupByLibrary.simpleMessage( - "Первичные замеры", - ), - "label": MessageLookupByLibrary.simpleMessage("Название"), - "language": MessageLookupByLibrary.simpleMessage("Язык"), - "last_name": MessageLookupByLibrary.simpleMessage("Фамилия"), - "last_name_empty": MessageLookupByLibrary.simpleMessage( - "Фамилия обязательна для заполнения", - ), - "last_name_short": MessageLookupByLibrary.simpleMessage( - "В фамилии должен быть минимум 6 символов", - ), - "lbl_english": MessageLookupByLibrary.simpleMessage("English"), - "light_type": MessageLookupByLibrary.simpleMessage("Легкая"), - "light_type_desc": MessageLookupByLibrary.simpleMessage( - "С некоторыми видами диет можно употреблять алкоголь в умеренных количествах. Рекомендуется совмещать вместе с низкокалорийными закусками.", - ), - "liked_groceries": MessageLookupByLibrary.simpleMessage("Любимые продукты"), - "link_was_copied": MessageLookupByLibrary.simpleMessage( - "Ссылка успешно скопирована", - ), - "loading": MessageLookupByLibrary.simpleMessage("Загрузка..."), - "lower": MessageLookupByLibrary.simpleMessage("Нижнее"), - "male": MessageLookupByLibrary.simpleMessage("Мужской"), - "meal": MessageLookupByLibrary.simpleMessage("Приём пищи"), - "meat": MessageLookupByLibrary.simpleMessage("Мясо"), - "meat_type": MessageLookupByLibrary.simpleMessage("С мясом"), - "meat_type_desc": MessageLookupByLibrary.simpleMessage( - "Диета с мясом полезная и сбалансированная. Включает в себя нежирное мясо: курицу, индейку, кролика или говядину. В сочетании с овощами получаете все необходимые витамины и минералы. Не забывайте о порциях и старайтесь есть медленно, прислушиваясь к своему организму.", - ), - "milliliter": MessageLookupByLibrary.simpleMessage("мл"), - "minimal_customer_price": MessageLookupByLibrary.simpleMessage( - "Минимальная цена для покупателя: 1200₽", - ), - "most_new": MessageLookupByLibrary.simpleMessage("Самые новые"), - "most_old": MessageLookupByLibrary.simpleMessage("Самые старые"), - "msg_network_err": MessageLookupByLibrary.simpleMessage("Сетевая ошибка!"), - "msg_something_went_wrong": MessageLookupByLibrary.simpleMessage( - "Что то пошло не так!", - ), - "my_blog": MessageLookupByLibrary.simpleMessage("Мой блог"), - "my_diets": MessageLookupByLibrary.simpleMessage("Мои диеты"), - "my_plans": MessageLookupByLibrary.simpleMessage("Мои планы"), - "my_settings": MessageLookupByLibrary.simpleMessage("Мои настройки"), - "name_empty": MessageLookupByLibrary.simpleMessage( - "Имя пользователя обязательно для заполнения", - ), - "name_short": MessageLookupByLibrary.simpleMessage( - "В имени пользователя должно быть минимум 6 символов", - ), - "no": MessageLookupByLibrary.simpleMessage("Нет"), - "no_account": MessageLookupByLibrary.simpleMessage("У меня нет аккаунта"), - "not_liked_groceries": MessageLookupByLibrary.simpleMessage( - "Не любимые продукты", - ), - "one_option": MessageLookupByLibrary.simpleMessage("1 опция"), - "option": MessageLookupByLibrary.simpleMessage("Опция"), - "option_content": MessageLookupByLibrary.simpleMessage("Содержание опции"), - "option_content_hint_text": MessageLookupByLibrary.simpleMessage( - "На связи 7 дней в неделю", - ), - "option_desc": MessageLookupByLibrary.simpleMessage("Описание опции"), - "option_desc_hint_text": MessageLookupByLibrary.simpleMessage( - "Этот план питания для похудения на 7 дней сбалансированный и разнообразный", - ), - "option_duration": MessageLookupByLibrary.simpleMessage( - "Продолжительность опции", - ), - "option_personal_diet_desc": MessageLookupByLibrary.simpleMessage( - "Опция подразумевает создание индивидуальной диеты", - ), - "option_title": MessageLookupByLibrary.simpleMessage("Заголовок опции"), - "order": MessageLookupByLibrary.simpleMessage("Порядок"), - "other_info": MessageLookupByLibrary.simpleMessage("Прочая информация"), - "password": MessageLookupByLibrary.simpleMessage("Пароль"), - "password_confirmation": MessageLookupByLibrary.simpleMessage( - "Подтвердите пароль", - ), - "password_confirmation_mismatch": MessageLookupByLibrary.simpleMessage( - "Пароли не совпадают", - ), - "password_empty": MessageLookupByLibrary.simpleMessage( - "Пароль обязателен для заполнения", - ), - "password_short": MessageLookupByLibrary.simpleMessage( - "В пароле должно быть минимум 6 символов", - ), - "pcs": MessageLookupByLibrary.simpleMessage("шт"), - "personal": MessageLookupByLibrary.simpleMessage("Персональные"), - "personal_diet_state": MessageLookupByLibrary.simpleMessage("Персональная"), - "photo": MessageLookupByLibrary.simpleMessage("Фото"), - "physical_fitness": MessageLookupByLibrary.simpleMessage( - "Физическая форма", - ), - "place_of_living": MessageLookupByLibrary.simpleMessage("Место проживания"), - "plan_author": MessageLookupByLibrary.simpleMessage("Автор плана"), - "plan_desc": MessageLookupByLibrary.simpleMessage("Описание плана"), - "plan_desc_example": MessageLookupByLibrary.simpleMessage( - "Большое подробное, привлекательное описание плана", - ), - "plan_duration": MessageLookupByLibrary.simpleMessage("Срок плана"), - "plan_label_desc": MessageLookupByLibrary.simpleMessage( - "План для поддержания формы", - ), - "plan_type": MessageLookupByLibrary.simpleMessage("Тип плана"), - "plans_complete": MessageLookupByLibrary.simpleMessage("Пройденные планы"), - "popular": MessageLookupByLibrary.simpleMessage("Популярные"), - "preferred_language": MessageLookupByLibrary.simpleMessage( - "Предпочитаемый язык общения", - ), - "pressure": MessageLookupByLibrary.simpleMessage("Давление"), - "pressure_empty": MessageLookupByLibrary.simpleMessage( - "Все показатели давления обязательны к заполнению", - ), - "pressure_is_not_a_number": MessageLookupByLibrary.simpleMessage( - "Все показатели давления должны быть числами", - ), - "preview": MessageLookupByLibrary.simpleMessage("Превью"), - "price": MessageLookupByLibrary.simpleMessage("Цена"), - "price_explanation": MessageLookupByLibrary.simpleMessage( - "Разница в ценах, это комиссия Итвио", - ), - "profile": MessageLookupByLibrary.simpleMessage("Профиль"), - "proteins": MessageLookupByLibrary.simpleMessage("Белки"), - "publish": MessageLookupByLibrary.simpleMessage("Опубликовать"), - "published": MessageLookupByLibrary.simpleMessage("Опубликованные"), - "removeFromActive": MessageLookupByLibrary.simpleMessage( - "Снять с публикации", - ), - "save": MessageLookupByLibrary.simpleMessage("Сохранить"), - "services": MessageLookupByLibrary.simpleMessage("Оказание услуг"), - "settings": MessageLookupByLibrary.simpleMessage("Настройки"), - "sex": MessageLookupByLibrary.simpleMessage("Пол"), - "share": MessageLookupByLibrary.simpleMessage("Поделиться"), - "skip": MessageLookupByLibrary.simpleMessage("Пропустить"), - "slim": MessageLookupByLibrary.simpleMessage("Похудение"), - "slim_type": MessageLookupByLibrary.simpleMessage("Похудение"), - "slim_type_desc": MessageLookupByLibrary.simpleMessage( - "План питания для похудения должен быть разработан с учётом индивидуальных особенностей организма, возраста, пола, уровня физической активности и состояния здоровья. Общие принципы: \n● Снижение калорийности; \n● Сбалансированный рацион; \n● Разнообразие продуктов; \n● Питьевой режим; \n● Ограничение вредных продуктов;", - ), - "sport_hobbies": MessageLookupByLibrary.simpleMessage( - "Спортивные увлечения", - ), - "standard": MessageLookupByLibrary.simpleMessage("Стандарт"), - "startDiet": MessageLookupByLibrary.simpleMessage("Начать диету"), - "takes_pills": MessageLookupByLibrary.simpleMessage("Принимает лекарства"), - "tell_your_schedule": MessageLookupByLibrary.simpleMessage( - "Расскажите в свободной форме о том как просыпаетесь, когда едите, когда ложитесь спать:", - ), - "three_options": MessageLookupByLibrary.simpleMessage("3 опции"), - "time": MessageLookupByLibrary.simpleMessage("Время прохождения"), - "toOffer": MessageLookupByLibrary.simpleMessage("Предложить"), - "to_draft": MessageLookupByLibrary.simpleMessage("В черновик"), - "unfavorite_foods": MessageLookupByLibrary.simpleMessage( - "Нелюбимые продукты", - ), - "up_pressure": MessageLookupByLibrary.simpleMessage( - "Верхние показатели давления:", - ), - "upper": MessageLookupByLibrary.simpleMessage("Верхнее"), - "user_authorization": MessageLookupByLibrary.simpleMessage( - "Аутентификация", - ), - "user_data": MessageLookupByLibrary.simpleMessage("Данные пользователя"), - "user_registration": MessageLookupByLibrary.simpleMessage( - "Регистрация пользователя", - ), - "username": MessageLookupByLibrary.simpleMessage("Имя пользователя"), - "vegan": MessageLookupByLibrary.simpleMessage("Веган."), - "vegan_type": MessageLookupByLibrary.simpleMessage("Вегетарианская"), - "vegan_type_desc": MessageLookupByLibrary.simpleMessage( - "Диета основана на потреблении растительной пищи и отказе от продуктов животного происхождения. Такой тип питания может быть разнообразным и вкусным, а также полезным для здоровья.", - ), - "waist": MessageLookupByLibrary.simpleMessage("Талия"), - "waist_circumference": MessageLookupByLibrary.simpleMessage("Обхват талии"), - "weight": MessageLookupByLibrary.simpleMessage("Вес"), - "weight_chart": MessageLookupByLibrary.simpleMessage("График веса"), - "weight_measurements": MessageLookupByLibrary.simpleMessage("Замер веса"), - "wellness": MessageLookupByLibrary.simpleMessage("Улучшение самочувствия"), - "wellness_type": MessageLookupByLibrary.simpleMessage( - "Улучшение самочувствия", - ), - "wellness_type_desc": MessageLookupByLibrary.simpleMessage( - "Если вы любите мучные продукты, но хотите придерживаться диеты, вам подходит диета с добавленим мучных блюд на основе цельнозерновых вариантов. В них больше сложных углеводов и клетчатки, что способствует длительному чувству сытости и поддерживает здоровье пищеварительной системы.", - ), - "what_is_your_name": MessageLookupByLibrary.simpleMessage("Как вас зовут?"), - "wrong_credentials": MessageLookupByLibrary.simpleMessage( - "Неверные данные пользователя", - ), - "you_will_get": MessageLookupByLibrary.simpleMessage("Вы получите"), - "your_blood_pressure": MessageLookupByLibrary.simpleMessage( - "Ваше артериальное давление?", - ), - "your_favorite_sports": MessageLookupByLibrary.simpleMessage( - "Ваши спортивные увлечения?", - ), - "your_gender": MessageLookupByLibrary.simpleMessage("Ваш пол?"), - "your_height": MessageLookupByLibrary.simpleMessage("Ваш рост:"), - "your_schedule": MessageLookupByLibrary.simpleMessage("Ваш режим дня?"), - "your_weight": MessageLookupByLibrary.simpleMessage("Ваш вес:"), - }; + "Moscow": MessageLookupByLibrary.simpleMessage("Москва"), + "Russia": MessageLookupByLibrary.simpleMessage("Россия"), + "about_diet": MessageLookupByLibrary.simpleMessage("Об этой диете"), + "about_plan": MessageLookupByLibrary.simpleMessage("Об этом плане"), + "about_recipe": MessageLookupByLibrary.simpleMessage("Об этом рецепте"), + "active": MessageLookupByLibrary.simpleMessage("Активен"), + "active_diet_state": MessageLookupByLibrary.simpleMessage("Активная"), + "add": MessageLookupByLibrary.simpleMessage("Добавить"), + "add_cover": MessageLookupByLibrary.simpleMessage("Добавьте обложку"), + "add_measure": MessageLookupByLibrary.simpleMessage("Добавить замер"), + "adding_plan": MessageLookupByLibrary.simpleMessage("Добавление плана"), + "additional_information": + MessageLookupByLibrary.simpleMessage("Дополнительная информация"), + "admin": MessageLookupByLibrary.simpleMessage("Админ"), + "all": MessageLookupByLibrary.simpleMessage("Все"), + "allergies_presence": + MessageLookupByLibrary.simpleMessage("Наличие аллергии"), + "another_form_of_comment": + MessageLookupByLibrary.simpleMessage("комментария"), + "another_form_of_day": MessageLookupByLibrary.simpleMessage("дня"), + "another_form_of_hour": MessageLookupByLibrary.simpleMessage("часа"), + "another_form_of_minute": MessageLookupByLibrary.simpleMessage("минут"), + "another_form_of_stage": MessageLookupByLibrary.simpleMessage("этапа"), + "answer": MessageLookupByLibrary.simpleMessage("Ответить"), + "archived": MessageLookupByLibrary.simpleMessage("В архиве"), + "authentificate": MessageLookupByLibrary.simpleMessage("Войти"), + "average_pressure": MessageLookupByLibrary.simpleMessage( + "Средние показатели давления:"), + "bodymass_chart": MessageLookupByLibrary.simpleMessage("График ИМТ"), + "by_default": MessageLookupByLibrary.simpleMessage("По умолчянию"), + "calories": MessageLookupByLibrary.simpleMessage("Калории"), + "cancel_diet": MessageLookupByLibrary.simpleMessage("Отменить диету?"), + "carb_type": MessageLookupByLibrary.simpleMessage("Мучное"), + "carb_type_desc": MessageLookupByLibrary.simpleMessage( + "Если вы любите мучные продукты, но хотите придерживаться диеты, вам подходит диета с добавленим мучных блюд на основе цельнозерновых вариантов. В них больше сложных углеводов и клетчатки, что способствует длительному чувству сытости и поддерживает здоровье пищеварительной системы."), + "carbohydrates": MessageLookupByLibrary.simpleMessage("Углеводы"), + "chat": MessageLookupByLibrary.simpleMessage("Чат"), + "check_out_video_recipe": + MessageLookupByLibrary.simpleMessage("Посмотреть видео рецепт"), + "chest": MessageLookupByLibrary.simpleMessage("Грудь"), + "chest_circumference": + MessageLookupByLibrary.simpleMessage("Обхват груди"), + "choose_diet_duration": + MessageLookupByLibrary.simpleMessage("Выберите срок диеты"), + "choose_plan_duration": + MessageLookupByLibrary.simpleMessage("Выберите срок плана"), + "choose_start_diet_date": MessageLookupByLibrary.simpleMessage( + "Выберите дату старты диеты. Она отобразиться у Вас в дневнике"), + "circumference_empty": MessageLookupByLibrary.simpleMessage( + "Все замеры обязательны к заполнению"), + "circumference_is_not_a_number": MessageLookupByLibrary.simpleMessage( + "Все замеры должны быть числами"), + "circumferences_chart": + MessageLookupByLibrary.simpleMessage("График обхватов"), + "city": MessageLookupByLibrary.simpleMessage("Город"), + "city_of_living": + MessageLookupByLibrary.simpleMessage("Город проживания"), + "comfort": MessageLookupByLibrary.simpleMessage("Комфорт"), + "comment": MessageLookupByLibrary.simpleMessage("комментарий"), + "comments": MessageLookupByLibrary.simpleMessage("Комментарии"), + "comments_plural": MessageLookupByLibrary.simpleMessage("комментариев"), + "complain": MessageLookupByLibrary.simpleMessage("Пожаловаться"), + "confirm": MessageLookupByLibrary.simpleMessage("Подтвердить"), + "continueButton": MessageLookupByLibrary.simpleMessage("Продолжить"), + "continue_": MessageLookupByLibrary.simpleMessage("Продолжить"), + "copied": MessageLookupByLibrary.simpleMessage("cкопирован"), + "copy_link": MessageLookupByLibrary.simpleMessage("Скопировать ссылку"), + "could_not_cancel_diet": + MessageLookupByLibrary.simpleMessage("Не удалось отменить диету"), + "could_not_launch_video": + MessageLookupByLibrary.simpleMessage("Не удалось запустить видео"), + "country": MessageLookupByLibrary.simpleMessage("Страна"), + "create_diet": MessageLookupByLibrary.simpleMessage("Создание диеты"), + "customer_price": + MessageLookupByLibrary.simpleMessage("Цена для покупателя"), + "date_of_birth": MessageLookupByLibrary.simpleMessage("Дата рождения"), + "day": MessageLookupByLibrary.simpleMessage("День"), + "days": MessageLookupByLibrary.simpleMessage("дней"), + "deleted": MessageLookupByLibrary.simpleMessage("Удалена"), + "diary": MessageLookupByLibrary.simpleMessage("Дневник"), + "diet": MessageLookupByLibrary.simpleMessage("Диета"), + "diet_author": MessageLookupByLibrary.simpleMessage("Автор диеты"), + "diet_has_been_sent_to_draft": MessageLookupByLibrary.simpleMessage( + "снята с публикации и перенесена в черновик"), + "diet_plan": MessageLookupByLibrary.simpleMessage("План диеты"), + "diet_started": + MessageLookupByLibrary.simpleMessage("Диета уже начата"), + "diet_state": MessageLookupByLibrary.simpleMessage("Статус диеты"), + "diet_type": MessageLookupByLibrary.simpleMessage("Тип диеты"), + "do_you_have_hypertension_tendency": + MessageLookupByLibrary.simpleMessage("Есть ли у вас гипертония?"), + "do_you_have_hypotension_tendency": + MessageLookupByLibrary.simpleMessage("Есть ли у вас гипотония?"), + "do_you_take_pills": MessageLookupByLibrary.simpleMessage( + "Принимаете ли вы лекарства или бады?"), + "down_pressure": + MessageLookupByLibrary.simpleMessage("Нижние показатели давления:"), + "draft": MessageLookupByLibrary.simpleMessage("Черновик"), + "drafted": MessageLookupByLibrary.simpleMessage("Черновики"), + "eating_behavior": + MessageLookupByLibrary.simpleMessage("Пищевое поведение"), + "eating_behavior_type": MessageLookupByLibrary.simpleMessage( + "Формирование пищевого поведения"), + "eating_behavior_type_desc": MessageLookupByLibrary.simpleMessage( + "Формирование здорового пищевого поведения — это процесс, который включает в себя изменение привычек и отношения к еде. Он направлен на то, чтобы помочь людям научиться правильно питаться и поддерживать здоровый образ жизни.Основные принципы формирования здорового пищевого поведения: \n● Разнообразие продуктов; \n● Сбалансированный рацион; \n● Питьевой режим; \n● Ограничение вредных продуктов; \n● Регулярные приёмы пищи; \n● Умеренность;Осознанное питание; \n● Физическая активность;"), + "economy": MessageLookupByLibrary.simpleMessage("Эконом"), + "edit": MessageLookupByLibrary.simpleMessage("Редактировать"), + "email": MessageLookupByLibrary.simpleMessage("Электронная почта"), + "email_empty": MessageLookupByLibrary.simpleMessage( + "Необходимо ввести адрес электронной почты"), + "email_invalid": MessageLookupByLibrary.simpleMessage( + "Некорректный адрес электронной почты"), + "enter_additional_information": MessageLookupByLibrary.simpleMessage( + "Введите любую дополнительную информацию на своё усмотрение:"), + "enter_diet_description": + MessageLookupByLibrary.simpleMessage("Введите описание диеты"), + "enter_diet_name": + MessageLookupByLibrary.simpleMessage("Выведите название диеты"), + "enter_information": + MessageLookupByLibrary.simpleMessage("Введите информацию..."), + "exit": MessageLookupByLibrary.simpleMessage("Выйти"), + "expand": MessageLookupByLibrary.simpleMessage("Показать полностью"), + "fats": MessageLookupByLibrary.simpleMessage("Жиры"), + "favorite_foods": + MessageLookupByLibrary.simpleMessage("Любимые продукты"), + "favorite_unfavorite_foods": MessageLookupByLibrary.simpleMessage( + "Ваши любимые/нелюбимые продукты питания?"), + "female": MessageLookupByLibrary.simpleMessage("Женский"), + "fill_in_information": + MessageLookupByLibrary.simpleMessage("Заполните информацию:"), + "finish": MessageLookupByLibrary.simpleMessage("Завершить"), + "first_name": MessageLookupByLibrary.simpleMessage("Имя"), + "first_name_empty": MessageLookupByLibrary.simpleMessage( + "Имя обязательно для заполнения"), + "first_name_short": MessageLookupByLibrary.simpleMessage( + "В имени должен быть минимум 1 символ"), + "fit_type": MessageLookupByLibrary.simpleMessage("Поддержание формы"), + "fit_type_desc": MessageLookupByLibrary.simpleMessage( + "План питания для поддержания формы сбалансированный и разнообразный, чтобы обеспечить организм всеми необходимыми питательными веществами. Общие принципы: \n● Дробное питание; \n● Разнообразие продуктов; \n● Контроль порций; \n● Питьевой режим; \n● Ограничение вредных продуктов;"), + "fitness": MessageLookupByLibrary.simpleMessage("Поддержка формы"), + "from": MessageLookupByLibrary.simpleMessage("от"), + "from_a_to_z": MessageLookupByLibrary.simpleMessage("Название: А-Я"), + "from_z_to_a": MessageLookupByLibrary.simpleMessage("Название: Я-А"), + "gallery": MessageLookupByLibrary.simpleMessage("Галерея"), + "general_info": + MessageLookupByLibrary.simpleMessage("Общая информация"), + "gr": MessageLookupByLibrary.simpleMessage("гр"), + "grams": MessageLookupByLibrary.simpleMessage("гр"), + "has_alergy": MessageLookupByLibrary.simpleMessage("Наличие аллергии"), + "has_hyper": + MessageLookupByLibrary.simpleMessage("Склонность к гипертонии"), + "has_hypo": + MessageLookupByLibrary.simpleMessage("Склонность к гипотонии"), + "height": MessageLookupByLibrary.simpleMessage("Рост"), + "height_measurements": + MessageLookupByLibrary.simpleMessage("Замер роста"), + "hide": MessageLookupByLibrary.simpleMessage("Свернуть"), + "hip_circumference": + MessageLookupByLibrary.simpleMessage("Обхват бедер"), + "hips": MessageLookupByLibrary.simpleMessage("Бедра"), + "hour": MessageLookupByLibrary.simpleMessage("час"), + "hours": MessageLookupByLibrary.simpleMessage("часов"), + "in_progress": MessageLookupByLibrary.simpleMessage("В процессе"), + "inactive_diet_state": MessageLookupByLibrary.simpleMessage("Черновик"), + "ingredients": MessageLookupByLibrary.simpleMessage("Ингредиенты"), + "initial_measurements": + MessageLookupByLibrary.simpleMessage("Первичные замеры"), + "label": MessageLookupByLibrary.simpleMessage("Название"), + "language": MessageLookupByLibrary.simpleMessage("Язык"), + "last_name": MessageLookupByLibrary.simpleMessage("Фамилия"), + "last_name_empty": MessageLookupByLibrary.simpleMessage( + "Фамилия обязательна для заполнения"), + "last_name_short": MessageLookupByLibrary.simpleMessage( + "В фамилии должен быть минимум 6 символов"), + "lbl_english": MessageLookupByLibrary.simpleMessage("English"), + "light_type": MessageLookupByLibrary.simpleMessage("Легкая"), + "light_type_desc": MessageLookupByLibrary.simpleMessage( + "С некоторыми видами диет можно употреблять алкоголь в умеренных количествах. Рекомендуется совмещать вместе с низкокалорийными закусками."), + "liked_groceries": + MessageLookupByLibrary.simpleMessage("Любимые продукты"), + "link_was_copied": + MessageLookupByLibrary.simpleMessage("Ссылка успешно скопирована"), + "loading": MessageLookupByLibrary.simpleMessage("Загрузка..."), + "lower": MessageLookupByLibrary.simpleMessage("Нижнее"), + "male": MessageLookupByLibrary.simpleMessage("Мужской"), + "meal": MessageLookupByLibrary.simpleMessage("Приём пищи"), + "meat": MessageLookupByLibrary.simpleMessage("Мясо"), + "meat_type": MessageLookupByLibrary.simpleMessage("С мясом"), + "meat_type_desc": MessageLookupByLibrary.simpleMessage( + "Диета с мясом полезная и сбалансированная. Включает в себя нежирное мясо: курицу, индейку, кролика или говядину. В сочетании с овощами получаете все необходимые витамины и минералы. Не забывайте о порциях и старайтесь есть медленно, прислушиваясь к своему организму."), + "milliliter": MessageLookupByLibrary.simpleMessage("мл"), + "minimal_customer_price": MessageLookupByLibrary.simpleMessage( + "Минимальная цена для покупателя: 1200₽"), + "minute": MessageLookupByLibrary.simpleMessage("минута"), + "minutes": MessageLookupByLibrary.simpleMessage("минуты"), + "most_new": MessageLookupByLibrary.simpleMessage("Самые новые"), + "most_old": MessageLookupByLibrary.simpleMessage("Самые старые"), + "msg_network_err": + MessageLookupByLibrary.simpleMessage("Сетевая ошибка!"), + "msg_something_went_wrong": + MessageLookupByLibrary.simpleMessage("Что то пошло не так!"), + "my_blog": MessageLookupByLibrary.simpleMessage("Мой блог"), + "my_diets": MessageLookupByLibrary.simpleMessage("Мои диеты"), + "my_plans": MessageLookupByLibrary.simpleMessage("Мои планы"), + "my_settings": MessageLookupByLibrary.simpleMessage("Мои настройки"), + "name_empty": MessageLookupByLibrary.simpleMessage( + "Имя пользователя обязательно для заполнения"), + "name_short": MessageLookupByLibrary.simpleMessage( + "В имени пользователя должно быть минимум 6 символов"), + "no": MessageLookupByLibrary.simpleMessage("Нет"), + "no_account": + MessageLookupByLibrary.simpleMessage("У меня нет аккаунта"), + "not_liked_groceries": + MessageLookupByLibrary.simpleMessage("Не любимые продукты"), + "one_option": MessageLookupByLibrary.simpleMessage("1 опция"), + "option": MessageLookupByLibrary.simpleMessage("Опция"), + "option_content": + MessageLookupByLibrary.simpleMessage("Содержание опции"), + "option_content_hint_text": + MessageLookupByLibrary.simpleMessage("На связи 7 дней в неделю"), + "option_desc": MessageLookupByLibrary.simpleMessage("Описание опции"), + "option_desc_hint_text": MessageLookupByLibrary.simpleMessage( + "Этот план питания для похудения на 7 дней сбалансированный и разнообразный"), + "option_duration": + MessageLookupByLibrary.simpleMessage("Продолжительность опции"), + "option_personal_diet_desc": MessageLookupByLibrary.simpleMessage( + "Опция подразумевает создание индивидуальной диеты"), + "option_title": MessageLookupByLibrary.simpleMessage("Заголовок опции"), + "order": MessageLookupByLibrary.simpleMessage("Порядок"), + "other_info": MessageLookupByLibrary.simpleMessage("Прочая информация"), + "password": MessageLookupByLibrary.simpleMessage("Пароль"), + "password_confirmation": + MessageLookupByLibrary.simpleMessage("Подтвердите пароль"), + "password_confirmation_mismatch": + MessageLookupByLibrary.simpleMessage("Пароли не совпадают"), + "password_empty": MessageLookupByLibrary.simpleMessage( + "Пароль обязателен для заполнения"), + "password_short": MessageLookupByLibrary.simpleMessage( + "В пароле должно быть минимум 6 символов"), + "pcs": MessageLookupByLibrary.simpleMessage("шт"), + "personal": MessageLookupByLibrary.simpleMessage("Персональные"), + "personal_diet_state": + MessageLookupByLibrary.simpleMessage("Персональная"), + "photo": MessageLookupByLibrary.simpleMessage("Фото"), + "physical_fitness": + MessageLookupByLibrary.simpleMessage("Физическая форма"), + "place_of_living": + MessageLookupByLibrary.simpleMessage("Место проживания"), + "plan_author": MessageLookupByLibrary.simpleMessage("Автор плана"), + "plan_desc": MessageLookupByLibrary.simpleMessage("Описание плана"), + "plan_desc_example": MessageLookupByLibrary.simpleMessage( + "Большое подробное, привлекательное описание плана"), + "plan_duration": MessageLookupByLibrary.simpleMessage("Срок плана"), + "plan_label_desc": + MessageLookupByLibrary.simpleMessage("План для поддержания формы"), + "plan_type": MessageLookupByLibrary.simpleMessage("Тип плана"), + "plans_complete": + MessageLookupByLibrary.simpleMessage("Пройденные планы"), + "popular": MessageLookupByLibrary.simpleMessage("Популярные"), + "preferred_language": + MessageLookupByLibrary.simpleMessage("Предпочитаемый язык общения"), + "pressure": MessageLookupByLibrary.simpleMessage("Давление"), + "pressure_empty": MessageLookupByLibrary.simpleMessage( + "Все показатели давления обязательны к заполнению"), + "pressure_is_not_a_number": MessageLookupByLibrary.simpleMessage( + "Все показатели давления должны быть числами"), + "preview": MessageLookupByLibrary.simpleMessage("Превью"), + "price": MessageLookupByLibrary.simpleMessage("Цена"), + "price_explanation": MessageLookupByLibrary.simpleMessage( + "Разница в ценах, это комиссия Итвио"), + "profile": MessageLookupByLibrary.simpleMessage("Профиль"), + "proteins": MessageLookupByLibrary.simpleMessage("Белки"), + "publish": MessageLookupByLibrary.simpleMessage("Опубликовать"), + "published": MessageLookupByLibrary.simpleMessage("Опубликованные"), + "recipe_author": MessageLookupByLibrary.simpleMessage("Автор рецепта"), + "removeFromActive": + MessageLookupByLibrary.simpleMessage("Снять с публикации"), + "save": MessageLookupByLibrary.simpleMessage("Сохранить"), + "services": MessageLookupByLibrary.simpleMessage("Оказание услуг"), + "settings": MessageLookupByLibrary.simpleMessage("Настройки"), + "sex": MessageLookupByLibrary.simpleMessage("Пол"), + "share": MessageLookupByLibrary.simpleMessage("Поделиться"), + "skip": MessageLookupByLibrary.simpleMessage("Пропустить"), + "slim": MessageLookupByLibrary.simpleMessage("Похудение"), + "slim_type": MessageLookupByLibrary.simpleMessage("Похудение"), + "slim_type_desc": MessageLookupByLibrary.simpleMessage( + "План питания для похудения должен быть разработан с учётом индивидуальных особенностей организма, возраста, пола, уровня физической активности и состояния здоровья. Общие принципы: \n● Снижение калорийности; \n● Сбалансированный рацион; \n● Разнообразие продуктов; \n● Питьевой режим; \n● Ограничение вредных продуктов;"), + "sport_hobbies": + MessageLookupByLibrary.simpleMessage("Спортивные увлечения"), + "stage": MessageLookupByLibrary.simpleMessage("этап"), + "stages": MessageLookupByLibrary.simpleMessage("этапов"), + "standard": MessageLookupByLibrary.simpleMessage("Стандарт"), + "startDiet": MessageLookupByLibrary.simpleMessage("Начать диету"), + "step": MessageLookupByLibrary.simpleMessage("Шаг"), + "step_by_step_photo_recipe": + MessageLookupByLibrary.simpleMessage("Пошаговый фоторецепт"), + "takes_pills": + MessageLookupByLibrary.simpleMessage("Принимает лекарства"), + "tell_your_schedule": MessageLookupByLibrary.simpleMessage( + "Расскажите в свободной форме о том как просыпаетесь, когда едите, когда ложитесь спать:"), + "there_is_a_video": MessageLookupByLibrary.simpleMessage("Есть видео"), + "three_options": MessageLookupByLibrary.simpleMessage("3 опции"), + "time": MessageLookupByLibrary.simpleMessage("Время прохождения"), + "toOffer": MessageLookupByLibrary.simpleMessage("Предложить"), + "to_draft": MessageLookupByLibrary.simpleMessage("В черновик"), + "unfavorite_foods": + MessageLookupByLibrary.simpleMessage("Нелюбимые продукты"), + "up_pressure": MessageLookupByLibrary.simpleMessage( + "Верхние показатели давления:"), + "upper": MessageLookupByLibrary.simpleMessage("Верхнее"), + "user_authorization": + MessageLookupByLibrary.simpleMessage("Аутентификация"), + "user_data": + MessageLookupByLibrary.simpleMessage("Данные пользователя"), + "user_registration": + MessageLookupByLibrary.simpleMessage("Регистрация пользователя"), + "username": MessageLookupByLibrary.simpleMessage("Имя пользователя"), + "vegan": MessageLookupByLibrary.simpleMessage("Веган."), + "vegan_type": MessageLookupByLibrary.simpleMessage("Вегетарианская"), + "vegan_type_desc": MessageLookupByLibrary.simpleMessage( + "Диета основана на потреблении растительной пищи и отказе от продуктов животного происхождения. Такой тип питания может быть разнообразным и вкусным, а также полезным для здоровья."), + "waist": MessageLookupByLibrary.simpleMessage("Талия"), + "waist_circumference": + MessageLookupByLibrary.simpleMessage("Обхват талии"), + "weight": MessageLookupByLibrary.simpleMessage("Вес"), + "weight_chart": MessageLookupByLibrary.simpleMessage("График веса"), + "weight_measurements": + MessageLookupByLibrary.simpleMessage("Замер веса"), + "wellness": + MessageLookupByLibrary.simpleMessage("Улучшение самочувствия"), + "wellness_type": + MessageLookupByLibrary.simpleMessage("Улучшение самочувствия"), + "wellness_type_desc": MessageLookupByLibrary.simpleMessage( + "Если вы любите мучные продукты, но хотите придерживаться диеты, вам подходит диета с добавленим мучных блюд на основе цельнозерновых вариантов. В них больше сложных углеводов и клетчатки, что способствует длительному чувству сытости и поддерживает здоровье пищеварительной системы."), + "what_is_your_name": + MessageLookupByLibrary.simpleMessage("Как вас зовут?"), + "wrong_credentials": MessageLookupByLibrary.simpleMessage( + "Неверные данные пользователя"), + "you_will_get": MessageLookupByLibrary.simpleMessage("Вы получите"), + "your_blood_pressure": + MessageLookupByLibrary.simpleMessage("Ваше артериальное давление?"), + "your_favorite_sports": + MessageLookupByLibrary.simpleMessage("Ваши спортивные увлечения?"), + "your_gender": MessageLookupByLibrary.simpleMessage("Ваш пол?"), + "your_height": MessageLookupByLibrary.simpleMessage("Ваш рост:"), + "your_schedule": MessageLookupByLibrary.simpleMessage("Ваш режим дня?"), + "your_weight": MessageLookupByLibrary.simpleMessage("Ваш вес:") + }; } diff --git a/lib/generated/l10n.dart b/lib/generated/l10n.dart index 79e15f7f..80b1d82f 100644 --- a/lib/generated/l10n.dart +++ b/lib/generated/l10n.dart @@ -2352,7 +2352,12 @@ class S { /// `Подтвердить` String get confirm { - return Intl.message('Подтвердить', name: 'confirm', desc: '', args: []); + return Intl.message( + 'Подтвердить', + name: 'confirm', + desc: '', + args: [], + ); } /// `Не удалось отменить диету` @@ -2364,6 +2369,186 @@ class S { args: [], ); } + + /// `часов` + String get hours { + return Intl.message( + 'часов', + name: 'hours', + desc: '', + args: [], + ); + } + + /// `час` + String get hour { + return Intl.message( + 'час', + name: 'hour', + desc: '', + args: [], + ); + } + + /// `часа` + String get another_form_of_hour { + return Intl.message( + 'часа', + name: 'another_form_of_hour', + desc: '', + args: [], + ); + } + + /// `минуты` + String get minutes { + return Intl.message( + 'минуты', + name: 'minutes', + desc: '', + args: [], + ); + } + + /// `минута` + String get minute { + return Intl.message( + 'минута', + name: 'minute', + desc: '', + args: [], + ); + } + + /// `минут` + String get another_form_of_minute { + return Intl.message( + 'минут', + name: 'another_form_of_minute', + desc: '', + args: [], + ); + } + + /// `Есть видео` + String get there_is_a_video { + return Intl.message( + 'Есть видео', + name: 'there_is_a_video', + desc: '', + args: [], + ); + } + + /// `Автор рецепта` + String get recipe_author { + return Intl.message( + 'Автор рецепта', + name: 'recipe_author', + desc: '', + args: [], + ); + } + + /// `Об этом рецепте` + String get about_recipe { + return Intl.message( + 'Об этом рецепте', + name: 'about_recipe', + desc: '', + args: [], + ); + } + + /// `этапов` + String get stages { + return Intl.message( + 'этапов', + name: 'stages', + desc: '', + args: [], + ); + } + + /// `этап` + String get stage { + return Intl.message( + 'этап', + name: 'stage', + desc: '', + args: [], + ); + } + + /// `этапа` + String get another_form_of_stage { + return Intl.message( + 'этапа', + name: 'another_form_of_stage', + desc: '', + args: [], + ); + } + + /// `Посмотреть видео рецепт` + String get check_out_video_recipe { + return Intl.message( + 'Посмотреть видео рецепт', + name: 'check_out_video_recipe', + desc: '', + args: [], + ); + } + + /// `Не удалось запустить видео` + String get could_not_launch_video { + return Intl.message( + 'Не удалось запустить видео', + name: 'could_not_launch_video', + desc: '', + args: [], + ); + } + + /// `Ингредиенты` + String get ingredients { + return Intl.message( + 'Ингредиенты', + name: 'ingredients', + desc: '', + args: [], + ); + } + + /// `Шаг` + String get step { + return Intl.message( + 'Шаг', + name: 'step', + desc: '', + args: [], + ); + } + + /// `Пошаговый фоторецепт` + String get step_by_step_photo_recipe { + return Intl.message( + 'Пошаговый фоторецепт', + name: 'step_by_step_photo_recipe', + desc: '', + args: [], + ); + } + + /// `снята с публикации и перенесена в черновик` + String get diet_has_been_sent_to_draft { + return Intl.message( + 'снята с публикации и перенесена в черновик', + name: 'diet_has_been_sent_to_draft', + desc: '', + args: [], + ); + } } class AppLocalizationDelegate extends LocalizationsDelegate { diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 1b8e2952..3145312f 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -247,5 +247,6 @@ "could_not_launch_video": "Не удалось запустить видео", "ingredients": "Ингредиенты", "step": "Шаг", - "step_by_step_photo_recipe": "Пошаговый фоторецепт" + "step_by_step_photo_recipe": "Пошаговый фоторецепт", + "diet_has_been_sent_to_draft": "снята с публикации и перенесена в черновик" } diff --git a/lib/main.dart b/lib/main.dart index 5b223f8a..7cdeb5fa 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -5,6 +5,7 @@ import 'package:eatvio_app/core/theme/theme_helper.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/notifications_cubit/notifications_cubit.dart'; import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart'; +import 'package:eatvio_app/features/chat/presentation/bloc/chats_cubit/chats_cubit.dart'; import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart'; import 'package:eatvio_app/features/internet_connection_checker/internet_connection_checker_screen.dart'; import 'package:eatvio_app/generated/l10n.dart'; @@ -39,6 +40,7 @@ class MyApp extends StatelessWidget { BlocProvider(create: (context) => sl()), BlocProvider(create: (context) => sl()..loadFeedBlogPostList()), BlocProvider(create: (context) => sl()..getMyProfile()), + BlocProvider(create: (context) => sl()..getChatRoomsWithLoading()), ], child: Portal( child: KeyboardDismisser( diff --git a/pubspec.yaml b/pubspec.yaml index dbff2756..2fe27628 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,10 +1,10 @@ name: eatvio_app description: A new Flutter project. version: 1.0.40+40 -publish_to: "none" +publish_to: 'none' environment: - sdk: ">=3.3.4 <4.0.0" + sdk: '>=3.3.4 <4.0.0' dependencies: flutter: @@ -61,6 +61,7 @@ dependencies: webview_flutter: ^4.10.0 sentry_flutter: ^8.13.0 connectivity_plus: ^6.1.2 + visibility_detector: ^0.4.0+2 dev_dependencies: build_runner: ^2.4.14 @@ -102,4 +103,4 @@ flutter_native_splash: android_12: image: 'assets/splash/splash.png' image_dark: 'assets/splash/splash.png' - branding: 'assets/splash/branding.png' \ No newline at end of file + branding: 'assets/splash/branding.png'