added unread messages and unread chats counters

This commit is contained in:
Username1548
2025-02-20 16:41:07 +07:00
parent 120af45dd5
commit 93899e316f
45 changed files with 1784 additions and 1884 deletions

View File

@@ -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<String, dynamic>;
final chatsCubit = data['chatsCubit'] as ChatsCubit;
final chatId = data['chatId'] as String;
return ChatScreen(chatId: chatId, chatsCubit: chatsCubit,);
},
),
GoRoute(
path: '/settings-screen',

View File

@@ -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<int> durationValues = [7, 14, 21];

View File

@@ -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<void> init() async {
sl.registerFactory<profilePlans.PlansCubit>(() => profilePlans.PlansCubit(plansRepository: sl()));
sl.registerFactory<DietsCubit>(() => DietsCubit(feedRepository: sl(), dietsRepository: sl()));
sl.registerFactory<CommentsCubit>(() => CommentsCubit(feedRepository: sl()));
sl.registerFactory<ChatSelectCubit>(() => ChatSelectCubit(chatRepository: sl()));
sl.registerFactory<ChatsCubit>(() => ChatsCubit(chatRepository: sl(), wsManager: sl(), profileRepository: sl()));
sl.registerFactory<PlansCubit>(() => PlansCubit(feedRepository: sl(), planRepository: sl()));
sl.registerFactory<SettingsCubit>(() => SettingsCubit(profileRepository: sl(), settingsRepository: sl()));
sl.registerFactory<RegisterConsultantCubit>(() => RegisterConsultantCubit(consultantRepository: sl()));

View File

@@ -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,
);

View File

@@ -3,3 +3,10 @@ enum ProductType {
plan,
recipe
}
enum BaseState {
loading,
error,
initial,
success
}

View File

@@ -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 {

View File

@@ -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,

View File

@@ -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';

View File

@@ -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<BottomNavigationBarWidget> {
),
BottomNavigationBarItem(
label: s.chat,
icon: BlocBuilder<NewMessagesCubit, NewMessagesState>(
builder: (BuildContext context, NewMessagesState state) {
icon: BlocBuilder<ChatsCubit, ChatsState>(
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,
),
),

View File

@@ -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<dynamic>? 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<String, Object?> json) => _$ChatMessageModelFromJson(json);

View File

@@ -26,8 +26,14 @@ mixin _$ChatMessageModel {
List<dynamic>? 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<String, dynamic> toJson() => throw _privateConstructorUsedError;
@@ -50,7 +56,10 @@ abstract class $ChatMessageModelCopyWith<$Res> {
String data,
@JsonKey(name: 'media_data') List<dynamic>? 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<dynamic>? 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<dynamic>? 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<String, dynamic> 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<dynamic>? 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<String, dynamic> 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.

View File

@@ -13,7 +13,10 @@ _$ChatMessageModelImpl _$$ChatMessageModelImplFromJson(
data: json['data'] as String,
mediaData: json['media_data'] as List<dynamic>?,
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<String, dynamic> _$$ChatMessageModelImplToJson(
@@ -23,5 +26,8 @@ Map<String, dynamic> _$$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,
};

View File

@@ -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<String, dynamic> 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<dynamic> 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<String, dynamic> json) => _$LastMessageClassFromJson(json);
ChatMessageModel? parseLastMessage(dynamic json) {
if (json is List) {
return null;
}
return ChatMessageModel.fromJson(json);
}
@freezed

View File

@@ -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<String, dynamic> 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<String, dynamic> 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<String, dynamic> 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<String, dynamic> 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<dynamic> 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<String, dynamic> 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<LastMessageClass> 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<dynamic> 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<dynamic>,
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<dynamic> 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<dynamic>,
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<dynamic> 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<String, dynamic> json) =>
_$$LastMessageClassImplFromJson(json);
@override
@JsonKey(name: "id")
final int id;
@override
@JsonKey(name: "data")
final String data;
final List<dynamic> _mediaData;
@override
@JsonKey(name: "media_data")
List<dynamic> 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<String, dynamic> 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<dynamic> 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<String, dynamic> json) =
_$LastMessageClassImpl.fromJson;
@override
@JsonKey(name: "id")
int get id;
@override
@JsonKey(name: "data")
String get data;
@override
@JsonKey(name: "media_data")
List<dynamic> 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<String, dynamic> json) {
return _Users.fromJson(json);
}

View File

@@ -24,8 +24,9 @@ _$ChatRoomImpl _$$ChatRoomImplFromJson(Map<String, dynamic> 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<String, dynamic>),
unreadMessagesCount: (json['unread_messages_count'] as num).toInt(),
);
Map<String, dynamic> _$$ChatRoomImplToJson(_$ChatRoomImpl instance) =>
@@ -34,30 +35,7 @@ Map<String, dynamic> _$$ChatRoomImplToJson(_$ChatRoomImpl instance) =>
'room_name': instance.roomName,
'last_message': instance.lastMessage,
'users': instance.users,
};
_$LastMessageClassImpl _$$LastMessageClassImplFromJson(
Map<String, dynamic> json) =>
_$LastMessageClassImpl(
id: (json['id'] as num).toInt(),
data: json['data'] as String,
mediaData: json['media_data'] as List<dynamic>,
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<String, dynamic> _$$LastMessageClassImplToJson(
_$LastMessageClassImpl instance) =>
<String, dynamic>{
'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<String, dynamic> json) => _$UsersImpl(

View File

@@ -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<ChatRoomState> {
wsManager.subscribeDirectChatChannel();
wsManager.directChatStream?.listen((event) {
AudioPlayer().play((AssetSource('audio/recive_message.wav')));
final message = ChatMessageModel.fromJson(jsonDecode(event.data)['message']);
List<ChatMessageModel> 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<ChatRoomState> {
});
}
sendMessage({
Future<bool> sendMessage({
required String chatId,
String? data,
List<File>? 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<bool> 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

View File

@@ -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<ChatSelectState> {
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()),
);
}
}
}

View File

@@ -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<ChatRoom> chatRooms,
}) = ChatSelectStateLoaded;
}

View File

@@ -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>(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 extends Object?>(
TResult Function() $default, {
required TResult Function() loading,
required TResult Function(String errorMessage) error,
required TResult Function(List<ChatRoom> chatRooms) loaded,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>(
TResult? Function()? $default, {
TResult? Function()? loading,
TResult? Function(String errorMessage)? error,
TResult? Function(List<ChatRoom> chatRooms)? loaded,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>(
TResult Function()? $default, {
TResult Function()? loading,
TResult Function(String errorMessage)? error,
TResult Function(List<ChatRoom> chatRooms)? loaded,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult map<TResult extends Object?>(
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 extends Object?>(
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 extends Object?>(
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 extends Object?>(
TResult Function() $default, {
required TResult Function() loading,
required TResult Function(String errorMessage) error,
required TResult Function(List<ChatRoom> chatRooms) loaded,
}) {
return $default();
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>(
TResult? Function()? $default, {
TResult? Function()? loading,
TResult? Function(String errorMessage)? error,
TResult? Function(List<ChatRoom> chatRooms)? loaded,
}) {
return $default?.call();
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>(
TResult Function()? $default, {
TResult Function()? loading,
TResult Function(String errorMessage)? error,
TResult Function(List<ChatRoom> chatRooms)? loaded,
required TResult orElse(),
}) {
if ($default != null) {
return $default();
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>(
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 extends Object?>(
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 extends Object?>(
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 extends Object?>(
TResult Function() $default, {
required TResult Function() loading,
required TResult Function(String errorMessage) error,
required TResult Function(List<ChatRoom> chatRooms) loaded,
}) {
return loading();
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>(
TResult? Function()? $default, {
TResult? Function()? loading,
TResult? Function(String errorMessage)? error,
TResult? Function(List<ChatRoom> chatRooms)? loaded,
}) {
return loading?.call();
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>(
TResult Function()? $default, {
TResult Function()? loading,
TResult Function(String errorMessage)? error,
TResult Function(List<ChatRoom> chatRooms)? loaded,
required TResult orElse(),
}) {
if (loading != null) {
return loading();
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>(
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 extends Object?>(
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 extends Object?>(
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 extends Object?>(
TResult Function() $default, {
required TResult Function() loading,
required TResult Function(String errorMessage) error,
required TResult Function(List<ChatRoom> chatRooms) loaded,
}) {
return error(errorMessage);
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>(
TResult? Function()? $default, {
TResult? Function()? loading,
TResult? Function(String errorMessage)? error,
TResult? Function(List<ChatRoom> chatRooms)? loaded,
}) {
return error?.call(errorMessage);
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>(
TResult Function()? $default, {
TResult Function()? loading,
TResult Function(String errorMessage)? error,
TResult Function(List<ChatRoom> chatRooms)? loaded,
required TResult orElse(),
}) {
if (error != null) {
return error(errorMessage);
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>(
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 extends Object?>(
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 extends Object?>(
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<ChatRoom> 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<ChatRoom>,
));
}
}
/// @nodoc
class _$ChatSelectStateLoadedImpl implements ChatSelectStateLoaded {
const _$ChatSelectStateLoadedImpl({required final List<ChatRoom> chatRooms})
: _chatRooms = chatRooms;
final List<ChatRoom> _chatRooms;
@override
List<ChatRoom> 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 extends Object?>(
TResult Function() $default, {
required TResult Function() loading,
required TResult Function(String errorMessage) error,
required TResult Function(List<ChatRoom> chatRooms) loaded,
}) {
return loaded(chatRooms);
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>(
TResult? Function()? $default, {
TResult? Function()? loading,
TResult? Function(String errorMessage)? error,
TResult? Function(List<ChatRoom> chatRooms)? loaded,
}) {
return loaded?.call(chatRooms);
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>(
TResult Function()? $default, {
TResult Function()? loading,
TResult Function(String errorMessage)? error,
TResult Function(List<ChatRoom> chatRooms)? loaded,
required TResult orElse(),
}) {
if (loaded != null) {
return loaded(chatRooms);
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>(
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 extends Object?>(
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 extends Object?>(
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<ChatRoom> chatRooms}) = _$ChatSelectStateLoadedImpl;
List<ChatRoom> 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;
}

View File

@@ -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<ChatsState> {
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<void> 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<void> 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<void> _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<void> close() {
newIncomingMessagesSubscription?.cancel();
return super.close();
}
}

View File

@@ -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<ChatRoom> chatRooms,
@Default(null) String? errorMessage,
}) = _ChatsState;
}

View File

@@ -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>(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<ChatRoom> 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<ChatsState> 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<ChatRoom> 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<ChatRoom>,
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<ChatRoom> 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<ChatRoom>,
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<ChatRoom> chatRooms = const [],
this.errorMessage = null})
: _chatRooms = chatRooms;
@override
@JsonKey()
final BaseState state;
@override
@JsonKey()
final int chatsWithUnreadMessagesCount;
final List<ChatRoom> _chatRooms;
@override
@JsonKey()
List<ChatRoom> 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<ChatRoom> chatRooms,
final String? errorMessage}) = _$ChatsStateImpl;
@override
BaseState get state;
@override
int get chatsWithUnreadMessagesCount;
@override
List<ChatRoom> 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;
}

View File

@@ -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<ChatScreen> {
final scrollController = ScrollController();
final TextEditingController textEditingController = TextEditingController();
final ChatRoomCubit chatRoomCubit = sl<ChatRoomCubit>();
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<ChatScreen> {
(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<ChatRoomCubit>().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<ChatRoomCubit>().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<ChatScreen> {
if (result != null) {
List<File> 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<ChatScreen> {
);
}
_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,

View File

@@ -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<ChatSelectScreen> createState() => _ChatSelectScreenState();
}
class _ChatSelectScreenState extends State<ChatSelectScreen> {
final ChatSelectCubit chatSelectCubit = sl<ChatSelectCubit>();
final searchController = TextEditingController();
Widget build(BuildContext context) {
return BlocProvider<ChatSelectCubit>(
create: (context) => chatSelectCubit..getChatRooms(),
child: BlocBuilder<ChatSelectCubit, ChatSelectState>(
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<ChatSelectCubit>().getChatRooms(),
onChanged: (query) {
if (query.length > 2) {
EasyDebounce.debounce(
'searchChatRoom',
Duration(milliseconds: 300),
() => context.read<ChatSelectCubit>().getChatRooms(query: query),
);
} else {
context.read<ChatSelectCubit>().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]);
},
);
}

View File

@@ -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<ChatsScreen> createState() => _ChatsScreenState();
}
class _ChatsScreenState extends State<ChatsScreen> {
final searchController = TextEditingController();
Widget build(BuildContext context) {
return BlocBuilder<ChatsCubit, ChatsState>(
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<ChatsCubit>().getChatRooms(),
onChanged: (query) {
if (query.length > 2) {
EasyDebounce.debounce(
'searchChatRoom',
Duration(milliseconds: 300),
() => context.read<ChatsCubit>().getChatRooms(query: query),
);
} else {
context.read<ChatsCubit>().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]);
},
);
}
}

View File

@@ -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<dynamic> 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,
),
),
),
],
),
),
);

View File

@@ -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<ChatEntryWidget> {
LastMessageClass? lastMessage;
@override
void initState() {
super.initState();
if (widget.chatRoom.lastMessage is Map<String, dynamic>) {
lastMessage = LastMessageClass.fromJson(widget.chatRoom.lastMessage);
}
}
@override
Widget build(BuildContext context) {
@@ -34,10 +27,13 @@ class _ChatEntryWidgetState extends State<ChatEntryWidget> {
borderRadius: BorderRadius.circular(16),
onTap: () => GoRouter.of(context).pushNamed(
'chatScreen',
extra: widget.chatRoom.id.toString(),
extra: {
'chatId': widget.chatRoom.id.toString(),
'chatsCubit': context.read<ChatsCubit>(),
},
),
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<ChatEntryWidget> {
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),
),
),
],
),
],
),

View File

@@ -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<ChatMessageModel>();
Stream<ChatMessageModel> get newUserMessagesStream => _newUserMessagesController.stream;
ChatRepository({
required this.chatService,
});
void emitNewMessageToChats(ChatMessageModel message) {
_newUserMessagesController.add(message);
}
Future<void> sendMessage({
required String chatId,
String? data,
@@ -66,7 +76,7 @@ class ChatRepository {
} catch (e) {
rethrow;
}
}
}
Future<ChatRoomModel> getChatRoomIdByUserSlug({required String slug}) async {
try {
@@ -75,4 +85,12 @@ class ChatRepository {
rethrow;
}
}
Future<void> markMessagesAsRead(String chatId, List<int> messageIds) async {
try {
await chatService.markMessagesAsRead(id: chatId, body: {'messages_ids': messageIds});
} catch (e) {
rethrow;
}
}
}

View File

@@ -38,4 +38,7 @@ abstract class ChatService {
Future<ChatRoomModel> getChatRoomIdByUserSlug({
@Path() required String user,
});
@POST('/api/chat/{id}/messages/markAsRead')
Future<void> markMessagesAsRead({@Path() required String id, @Body() required Map<String, List<int>> body});
}

View File

@@ -167,6 +167,29 @@ class _ChatService implements ChatService {
return _value;
}
@override
Future<void> markMessagesAsRead({
required String id,
required Map<String, List<int>> body,
}) async {
final _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{};
final _headers = <String, dynamic>{};
final _data = <String, dynamic>{};
_data.addAll(body);
final _options = _setStreamType<void>(
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<void>(_options);
}
RequestOptions _setStreamType<T>(RequestOptions requestOptions) {
if (T != dynamic &&
!(requestOptions.responseType == ResponseType.bytes ||

View File

@@ -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<CreateDietScreenContent> {
final nameController = TextEditingController();
final descriptionController = TextEditingController();
final scaffoldKey = GlobalKey<ScaffoldState>();
@override
void initState() {
@@ -65,49 +66,22 @@ class _CreateDietScreenContentState extends State<CreateDietScreenContent> {
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 ?? 'Диета'} сохраненна в черновиках',
),
);
});

View File

@@ -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<FeedScreenContent> {
2 => AddItemButton(
onTap: state.profile?.roles.contains('consultant') ?? false
? () async {
final result = await GoRouter.of(context).pushNamed<String?>(CreateDietScreen.name);
if (result != null) {
showBottomSheetWithBlur(
userRootNavigator: true,
context: context,
child: SuccessPublishedDietBottomSheet(
title: result,
),
);
}
await GoRouter.of(context).pushNamed(CreateDietScreen.name);
}
: null,
),

View File

@@ -278,8 +278,7 @@ class MyDietsCubit extends Cubit<MyDietsState> {
Future<bool> 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<MyDietsState> {
}
}
Future<String?> publishDiet(int dietId) async {
Future<bool> 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<bool> 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;
}
}
}

View File

@@ -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);

View File

@@ -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,
),
],
);
}
}

View File

@@ -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,
),
],
);
}
}

View File

@@ -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<MyDietWidget> {
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<MyDietWidget> {
children: [
Expanded(
child: CustomOutlinedButton(
onPressed: () {
onPressed: () async {
if (state.selectedStateIndex == 1) {
context.read<MyDietsCubit>().publishDiet(widget.diet.id);
final result = await context.read<MyDietsCubit>().publishDiet(widget.diet.id);
if (result) {
showBottomSheetWithBlur(
context: context,
child: SuccessPublishedDietBottomSheet(title: '${widget.diet.name}'),
);
await context.read<MyDietsCubit>().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<MyDietWidget> {
children: [
Expanded(
child: CustomOutlinedButton(
onPressed: () {
onPressed: () async {
if (state.selectedStateIndex == 0) {
final result = await context.read<MyDietsCubit>().unpublishDiet(widget.diet.id);
if (result) {
showBottomSheetWithBlur(
context: context,
height: 250,
child: DietDraftedBottomSheet(title: '${widget.diet.name}'),
);
await context.read<MyDietsCubit>().loadAllDiets();
} else {
showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время');
}
} else {
GoRouter.of(context).pushNamed(CreateDietScreen.name, extra: widget.diet);
}
@@ -194,50 +214,13 @@ class _MyDietWidgetState extends State<MyDietWidget> {
CustomOutlinedButton(
onPressed: () async {
final result = await context.read<MyDietsCubit>().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<MyDietsCubit>().loadAllDiets();
}
},
height: 52,
width: 52,

View File

@@ -22,381 +22,352 @@ class MessageLookup extends MessageLookupByLibrary {
final messages = _notInlinedMessages(_notInlinedMessages);
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
"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("Ваш вес:")
};
}

View File

@@ -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<S> {

View File

@@ -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": "снята с публикации и перенесена в черновик"
}

View File

@@ -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<NotificationsCubit>(create: (context) => sl<NotificationsCubit>()),
BlocProvider<FeedCubit>(create: (context) => sl<FeedCubit>()..loadFeedBlogPostList()),
BlocProvider<ProfileAvatarCubit>(create: (context) => sl<ProfileAvatarCubit>()..getMyProfile()),
BlocProvider<ChatsCubit>(create: (context) => sl<ChatsCubit>()..getChatRoomsWithLoading()),
],
child: Portal(
child: KeyboardDismisser(

View File

@@ -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'
branding: 'assets/splash/branding.png'