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