forked from eatvio/eatvio_app
added unread messages and unread chats counters
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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()),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
144
lib/features/chat/presentation/bloc/chats_cubit/chats_cubit.dart
Normal file
144
lib/features/chat/presentation/bloc/chats_cubit/chats_cubit.dart
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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]);
|
||||
},
|
||||
);
|
||||
}
|
||||
109
lib/features/chat/presentation/pages/chats_screen.dart
Normal file
109
lib/features/chat/presentation/pages/chats_screen.dart
Normal 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]);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -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),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user