From 7514a9b75ffea40d60ecd397d31a5334a490c6af Mon Sep 17 00:00:00 2001 From: alex Date: Wed, 3 Jul 2024 19:06:51 +0300 Subject: [PATCH 1/3] feat(chat): add chat load --- .../chat/models/chat_messages_model.dart | 5 +- .../models/chat_messages_model.freezed.dart | 53 +++++- .../chat/models/chat_messages_model.g.dart | 2 + lib/features/chat/models/chat_meta_model.dart | 14 ++ .../chat/models/chat_meta_model.freezed.dart | 158 ++++++++++++++++++ .../chat/models/chat_meta_model.g.dart | 17 ++ .../bloc/chat_room_cubit/chat_room_cubit.dart | 44 ++++- .../bloc/chat_room_cubit/chat_room_state.dart | 5 +- .../chat_room_state.freezed.dart | 117 +++++++------ .../chat/presentation/pages/chat_screen.dart | 40 +++-- .../chat/repositories/chat_repository.dart | 4 +- lib/features/chat/services/chat_service.dart | 4 +- .../chat/services/chat_service.g.dart | 7 +- 13 files changed, 380 insertions(+), 90 deletions(-) create mode 100644 lib/features/chat/models/chat_meta_model.dart create mode 100644 lib/features/chat/models/chat_meta_model.freezed.dart create mode 100644 lib/features/chat/models/chat_meta_model.g.dart diff --git a/lib/features/chat/models/chat_messages_model.dart b/lib/features/chat/models/chat_messages_model.dart index fbf6fc05..84a51b48 100644 --- a/lib/features/chat/models/chat_messages_model.dart +++ b/lib/features/chat/models/chat_messages_model.dart @@ -1,4 +1,5 @@ import 'package:eatvio_app/features/chat/models/chat_message_model.dart'; +import 'package:eatvio_app/features/chat/models/chat_meta_model.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; part 'chat_messages_model.freezed.dart'; @@ -8,8 +9,8 @@ part 'chat_messages_model.g.dart'; @freezed class ChatMessagesModel with _$ChatMessagesModel { const factory ChatMessagesModel({ - @JsonKey(name: 'data') - required List messagesList, + @JsonKey(name: 'data') required List messagesList, + required ChatMetaModel meta, }) = _ChatMessagesModel; factory ChatMessagesModel.fromJson(Map json) => _$ChatMessagesModelFromJson(json); diff --git a/lib/features/chat/models/chat_messages_model.freezed.dart b/lib/features/chat/models/chat_messages_model.freezed.dart index 594498a7..a970241c 100644 --- a/lib/features/chat/models/chat_messages_model.freezed.dart +++ b/lib/features/chat/models/chat_messages_model.freezed.dart @@ -22,6 +22,7 @@ ChatMessagesModel _$ChatMessagesModelFromJson(Map json) { mixin _$ChatMessagesModel { @JsonKey(name: 'data') List get messagesList => throw _privateConstructorUsedError; + ChatMetaModel get meta => throw _privateConstructorUsedError; Map toJson() => throw _privateConstructorUsedError; @JsonKey(ignore: true) @@ -35,7 +36,11 @@ abstract class $ChatMessagesModelCopyWith<$Res> { ChatMessagesModel value, $Res Function(ChatMessagesModel) then) = _$ChatMessagesModelCopyWithImpl<$Res, ChatMessagesModel>; @useResult - $Res call({@JsonKey(name: 'data') List messagesList}); + $Res call( + {@JsonKey(name: 'data') List messagesList, + ChatMetaModel meta}); + + $ChatMetaModelCopyWith<$Res> get meta; } /// @nodoc @@ -52,14 +57,27 @@ class _$ChatMessagesModelCopyWithImpl<$Res, $Val extends ChatMessagesModel> @override $Res call({ Object? messagesList = null, + Object? meta = null, }) { return _then(_value.copyWith( messagesList: null == messagesList ? _value.messagesList : messagesList // ignore: cast_nullable_to_non_nullable as List, + meta: null == meta + ? _value.meta + : meta // ignore: cast_nullable_to_non_nullable + as ChatMetaModel, ) as $Val); } + + @override + @pragma('vm:prefer-inline') + $ChatMetaModelCopyWith<$Res> get meta { + return $ChatMetaModelCopyWith<$Res>(_value.meta, (value) { + return _then(_value.copyWith(meta: value) as $Val); + }); + } } /// @nodoc @@ -70,7 +88,12 @@ abstract class _$$ChatMessagesModelImplCopyWith<$Res> __$$ChatMessagesModelImplCopyWithImpl<$Res>; @override @useResult - $Res call({@JsonKey(name: 'data') List messagesList}); + $Res call( + {@JsonKey(name: 'data') List messagesList, + ChatMetaModel meta}); + + @override + $ChatMetaModelCopyWith<$Res> get meta; } /// @nodoc @@ -85,12 +108,17 @@ class __$$ChatMessagesModelImplCopyWithImpl<$Res> @override $Res call({ Object? messagesList = null, + Object? meta = null, }) { return _then(_$ChatMessagesModelImpl( messagesList: null == messagesList ? _value._messagesList : messagesList // ignore: cast_nullable_to_non_nullable as List, + meta: null == meta + ? _value.meta + : meta // ignore: cast_nullable_to_non_nullable + as ChatMetaModel, )); } } @@ -100,7 +128,8 @@ class __$$ChatMessagesModelImplCopyWithImpl<$Res> class _$ChatMessagesModelImpl implements _ChatMessagesModel { const _$ChatMessagesModelImpl( {@JsonKey(name: 'data') - required final List messagesList}) + required final List messagesList, + required this.meta}) : _messagesList = messagesList; factory _$ChatMessagesModelImpl.fromJson(Map json) => @@ -115,9 +144,12 @@ class _$ChatMessagesModelImpl implements _ChatMessagesModel { return EqualUnmodifiableListView(_messagesList); } + @override + final ChatMetaModel meta; + @override String toString() { - return 'ChatMessagesModel(messagesList: $messagesList)'; + return 'ChatMessagesModel(messagesList: $messagesList, meta: $meta)'; } @override @@ -126,13 +158,14 @@ class _$ChatMessagesModelImpl implements _ChatMessagesModel { (other.runtimeType == runtimeType && other is _$ChatMessagesModelImpl && const DeepCollectionEquality() - .equals(other._messagesList, _messagesList)); + .equals(other._messagesList, _messagesList) && + (identical(other.meta, meta) || other.meta == meta)); } @JsonKey(ignore: true) @override int get hashCode => Object.hash( - runtimeType, const DeepCollectionEquality().hash(_messagesList)); + runtimeType, const DeepCollectionEquality().hash(_messagesList), meta); @JsonKey(ignore: true) @override @@ -151,9 +184,9 @@ class _$ChatMessagesModelImpl implements _ChatMessagesModel { abstract class _ChatMessagesModel implements ChatMessagesModel { const factory _ChatMessagesModel( - {@JsonKey(name: 'data') - required final List messagesList}) = - _$ChatMessagesModelImpl; + {@JsonKey(name: 'data') + required final List messagesList, + required final ChatMetaModel meta}) = _$ChatMessagesModelImpl; factory _ChatMessagesModel.fromJson(Map json) = _$ChatMessagesModelImpl.fromJson; @@ -162,6 +195,8 @@ abstract class _ChatMessagesModel implements ChatMessagesModel { @JsonKey(name: 'data') List get messagesList; @override + ChatMetaModel get meta; + @override @JsonKey(ignore: true) _$$ChatMessagesModelImplCopyWith<_$ChatMessagesModelImpl> get copyWith => throw _privateConstructorUsedError; diff --git a/lib/features/chat/models/chat_messages_model.g.dart b/lib/features/chat/models/chat_messages_model.g.dart index 80f78746..6216cd83 100644 --- a/lib/features/chat/models/chat_messages_model.g.dart +++ b/lib/features/chat/models/chat_messages_model.g.dart @@ -12,10 +12,12 @@ _$ChatMessagesModelImpl _$$ChatMessagesModelImplFromJson( messagesList: (json['data'] as List) .map((e) => ChatMessageModel.fromJson(e as Map)) .toList(), + meta: ChatMetaModel.fromJson(json['meta'] as Map), ); Map _$$ChatMessagesModelImplToJson( _$ChatMessagesModelImpl instance) => { 'data': instance.messagesList, + 'meta': instance.meta, }; diff --git a/lib/features/chat/models/chat_meta_model.dart b/lib/features/chat/models/chat_meta_model.dart new file mode 100644 index 00000000..4c4d6cf0 --- /dev/null +++ b/lib/features/chat/models/chat_meta_model.dart @@ -0,0 +1,14 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'chat_meta_model.freezed.dart'; + +part 'chat_meta_model.g.dart'; + +@freezed +class ChatMetaModel with _$ChatMetaModel { + const factory ChatMetaModel({ + @JsonKey(name: "last_page") required int lastPage, + }) = _ChatMetaModel; + + factory ChatMetaModel.fromJson(Map json) => _$ChatMetaModelFromJson(json); +} diff --git a/lib/features/chat/models/chat_meta_model.freezed.dart b/lib/features/chat/models/chat_meta_model.freezed.dart new file mode 100644 index 00000000..11ecb9e2 --- /dev/null +++ b/lib/features/chat/models/chat_meta_model.freezed.dart @@ -0,0 +1,158 @@ +// 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_meta_model.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +ChatMetaModel _$ChatMetaModelFromJson(Map json) { + return _ChatMetaModel.fromJson(json); +} + +/// @nodoc +mixin _$ChatMetaModel { + @JsonKey(name: "last_page") + int get lastPage => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $ChatMetaModelCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ChatMetaModelCopyWith<$Res> { + factory $ChatMetaModelCopyWith( + ChatMetaModel value, $Res Function(ChatMetaModel) then) = + _$ChatMetaModelCopyWithImpl<$Res, ChatMetaModel>; + @useResult + $Res call({@JsonKey(name: "last_page") int lastPage}); +} + +/// @nodoc +class _$ChatMetaModelCopyWithImpl<$Res, $Val extends ChatMetaModel> + implements $ChatMetaModelCopyWith<$Res> { + _$ChatMetaModelCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? lastPage = null, + }) { + return _then(_value.copyWith( + lastPage: null == lastPage + ? _value.lastPage + : lastPage // ignore: cast_nullable_to_non_nullable + as int, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$ChatMetaModelImplCopyWith<$Res> + implements $ChatMetaModelCopyWith<$Res> { + factory _$$ChatMetaModelImplCopyWith( + _$ChatMetaModelImpl value, $Res Function(_$ChatMetaModelImpl) then) = + __$$ChatMetaModelImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({@JsonKey(name: "last_page") int lastPage}); +} + +/// @nodoc +class __$$ChatMetaModelImplCopyWithImpl<$Res> + extends _$ChatMetaModelCopyWithImpl<$Res, _$ChatMetaModelImpl> + implements _$$ChatMetaModelImplCopyWith<$Res> { + __$$ChatMetaModelImplCopyWithImpl( + _$ChatMetaModelImpl _value, $Res Function(_$ChatMetaModelImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? lastPage = null, + }) { + return _then(_$ChatMetaModelImpl( + lastPage: null == lastPage + ? _value.lastPage + : lastPage // ignore: cast_nullable_to_non_nullable + as int, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$ChatMetaModelImpl implements _ChatMetaModel { + const _$ChatMetaModelImpl( + {@JsonKey(name: "last_page") required this.lastPage}); + + factory _$ChatMetaModelImpl.fromJson(Map json) => + _$$ChatMetaModelImplFromJson(json); + + @override + @JsonKey(name: "last_page") + final int lastPage; + + @override + String toString() { + return 'ChatMetaModel(lastPage: $lastPage)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ChatMetaModelImpl && + (identical(other.lastPage, lastPage) || + other.lastPage == lastPage)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, lastPage); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$ChatMetaModelImplCopyWith<_$ChatMetaModelImpl> get copyWith => + __$$ChatMetaModelImplCopyWithImpl<_$ChatMetaModelImpl>(this, _$identity); + + @override + Map toJson() { + return _$$ChatMetaModelImplToJson( + this, + ); + } +} + +abstract class _ChatMetaModel implements ChatMetaModel { + const factory _ChatMetaModel( + {@JsonKey(name: "last_page") required final int lastPage}) = + _$ChatMetaModelImpl; + + factory _ChatMetaModel.fromJson(Map json) = + _$ChatMetaModelImpl.fromJson; + + @override + @JsonKey(name: "last_page") + int get lastPage; + @override + @JsonKey(ignore: true) + _$$ChatMetaModelImplCopyWith<_$ChatMetaModelImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/features/chat/models/chat_meta_model.g.dart b/lib/features/chat/models/chat_meta_model.g.dart new file mode 100644 index 00000000..7126e2dd --- /dev/null +++ b/lib/features/chat/models/chat_meta_model.g.dart @@ -0,0 +1,17 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'chat_meta_model.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$ChatMetaModelImpl _$$ChatMetaModelImplFromJson(Map json) => + _$ChatMetaModelImpl( + lastPage: (json['last_page'] as num).toInt(), + ); + +Map _$$ChatMetaModelImplToJson(_$ChatMetaModelImpl instance) => + { + 'last_page': instance.lastPage, + }; diff --git a/lib/features/chat/presentation/bloc/chat_room_cubit/chat_room_cubit.dart b/lib/features/chat/presentation/bloc/chat_room_cubit/chat_room_cubit.dart index aff8fa8f..712adf04 100644 --- a/lib/features/chat/presentation/bloc/chat_room_cubit/chat_room_cubit.dart +++ b/lib/features/chat/presentation/bloc/chat_room_cubit/chat_room_cubit.dart @@ -14,6 +14,9 @@ import 'package:flutter_bloc/flutter_bloc.dart'; class ChatRoomCubit extends Cubit { final ChatRepository chatRepository; + int currentPage = 1; + late final int lastPage; + late final PusherChannelsClient client; late final PrivateChannel privateChannel; @@ -21,11 +24,40 @@ class ChatRoomCubit extends Cubit { required this.chatRepository, }) : super(ChatRoomState.loading()); + loadNextPage(String chatId) async { + if (currentPage < lastPage) { + emit(ChatRoomState.loaded( + chatRoom: (state as ChatRoomStateLoaded).chatRoom, + chatMessages: (state as ChatRoomStateLoaded).chatMessages, + isTyping: (state as ChatRoomStateLoaded).isTyping, + isLoading: true, + )); + + try { + final chatMessages = await chatRepository.getChatMessages(chatId, currentPage + 1); + + emit(ChatRoomState.loaded( + chatRoom: (state as ChatRoomStateLoaded).chatRoom, + chatMessages: [...(state as ChatRoomStateLoaded).chatMessages, ...chatMessages.messagesList], + isTyping: (state as ChatRoomStateLoaded).isTyping, + isLoading: false, + )); + + currentPage++; + + } catch (e) { + print(e); + emit(ChatRoomState.error(errorMessage: 'Ошибка получеия данных')); + } + } + } + initChat(String chatId) async { try { final chatRoom = await chatRepository.getChatRoom(chatId); - final chatMessages = await chatRepository.getChatMessages(chatId); + final chatMessages = await chatRepository.getChatMessages(chatId, currentPage); + lastPage = chatMessages.meta.lastPage; final initCubit = sl(); @@ -38,8 +70,9 @@ class ChatRoomCubit extends Cubit { emit(ChatRoomState.loaded( chatRoom: chatRoom, - chatMessages: chatMessages, + chatMessages: chatMessages.messagesList, isTyping: false, + isLoading: false, )); } catch (e) { emit(ChatRoomState.error(errorMessage: 'Ошибка получеия данных')); @@ -67,13 +100,14 @@ class ChatRoomCubit extends Cubit { privateChannel.bind('App\\Events\\ChatMessageUpdated').listen((event) { List messagesList = [ ChatMessageModel.fromJson(jsonDecode(event.data)['message']), - ...(state as ChatRoomStateLoaded).chatMessages.messagesList, + ...(state as ChatRoomStateLoaded).chatMessages, ]; emit( ChatRoomState.loaded( chatRoom: (state as ChatRoomStateLoaded).chatRoom, - chatMessages: (state as ChatRoomStateLoaded).chatMessages.copyWith(messagesList: messagesList), + chatMessages: messagesList, isTyping: (state as ChatRoomStateLoaded).isTyping, + isLoading: false, ), ); }); @@ -84,6 +118,7 @@ class ChatRoomCubit extends Cubit { chatRoom: (state as ChatRoomStateLoaded).chatRoom, chatMessages: (state as ChatRoomStateLoaded).chatMessages, isTyping: true, + isLoading: false, ), ); EasyDebounce.debounce( @@ -94,6 +129,7 @@ class ChatRoomCubit extends Cubit { chatRoom: (state as ChatRoomStateLoaded).chatRoom, chatMessages: (state as ChatRoomStateLoaded).chatMessages, isTyping: false, + isLoading: false, ), ), ); diff --git a/lib/features/chat/presentation/bloc/chat_room_cubit/chat_room_state.dart b/lib/features/chat/presentation/bloc/chat_room_cubit/chat_room_state.dart index dbf82382..24113e62 100644 --- a/lib/features/chat/presentation/bloc/chat_room_cubit/chat_room_state.dart +++ b/lib/features/chat/presentation/bloc/chat_room_cubit/chat_room_state.dart @@ -1,4 +1,4 @@ -import 'package:eatvio_app/features/chat/models/chat_messages_model.dart'; +import 'package:eatvio_app/features/chat/models/chat_message_model.dart'; import 'package:eatvio_app/features/chat/models/chat_room_model.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; @@ -12,8 +12,9 @@ abstract class ChatRoomState with _$ChatRoomState { const factory ChatRoomState.loaded({ required ChatRoomModel chatRoom, - required ChatMessagesModel chatMessages, + required List chatMessages, required bool isTyping, + required bool isLoading, }) = ChatRoomStateLoaded; const factory ChatRoomState.error({ diff --git a/lib/features/chat/presentation/bloc/chat_room_cubit/chat_room_state.freezed.dart b/lib/features/chat/presentation/bloc/chat_room_cubit/chat_room_state.freezed.dart index 4831cccb..43bf9df9 100644 --- a/lib/features/chat/presentation/bloc/chat_room_cubit/chat_room_state.freezed.dart +++ b/lib/features/chat/presentation/bloc/chat_room_cubit/chat_room_state.freezed.dart @@ -21,7 +21,7 @@ mixin _$ChatRoomState { TResult Function() $default, { required TResult Function() loading, required TResult Function(ChatRoomModel chatRoom, - ChatMessagesModel chatMessages, bool isTyping) + List chatMessages, bool isTyping, bool isLoading) loaded, required TResult Function(String errorMessage) error, }) => @@ -30,8 +30,8 @@ mixin _$ChatRoomState { TResult? whenOrNull( TResult? Function()? $default, { TResult? Function()? loading, - TResult? Function(ChatRoomModel chatRoom, ChatMessagesModel chatMessages, - bool isTyping)? + TResult? Function(ChatRoomModel chatRoom, + List chatMessages, bool isTyping, bool isLoading)? loaded, TResult? Function(String errorMessage)? error, }) => @@ -40,8 +40,8 @@ mixin _$ChatRoomState { TResult maybeWhen( TResult Function()? $default, { TResult Function()? loading, - TResult Function(ChatRoomModel chatRoom, ChatMessagesModel chatMessages, - bool isTyping)? + TResult Function(ChatRoomModel chatRoom, + List chatMessages, bool isTyping, bool isLoading)? loaded, TResult Function(String errorMessage)? error, required TResult orElse(), @@ -133,7 +133,7 @@ class _$ChatRoomStateImpl implements _ChatRoomState { TResult Function() $default, { required TResult Function() loading, required TResult Function(ChatRoomModel chatRoom, - ChatMessagesModel chatMessages, bool isTyping) + List chatMessages, bool isTyping, bool isLoading) loaded, required TResult Function(String errorMessage) error, }) { @@ -145,8 +145,8 @@ class _$ChatRoomStateImpl implements _ChatRoomState { TResult? whenOrNull( TResult? Function()? $default, { TResult? Function()? loading, - TResult? Function(ChatRoomModel chatRoom, ChatMessagesModel chatMessages, - bool isTyping)? + TResult? Function(ChatRoomModel chatRoom, + List chatMessages, bool isTyping, bool isLoading)? loaded, TResult? Function(String errorMessage)? error, }) { @@ -158,8 +158,8 @@ class _$ChatRoomStateImpl implements _ChatRoomState { TResult maybeWhen( TResult Function()? $default, { TResult Function()? loading, - TResult Function(ChatRoomModel chatRoom, ChatMessagesModel chatMessages, - bool isTyping)? + TResult Function(ChatRoomModel chatRoom, + List chatMessages, bool isTyping, bool isLoading)? loaded, TResult Function(String errorMessage)? error, required TResult orElse(), @@ -254,7 +254,7 @@ class _$ChatRoomStateLoadingImpl implements ChatRoomStateLoading { TResult Function() $default, { required TResult Function() loading, required TResult Function(ChatRoomModel chatRoom, - ChatMessagesModel chatMessages, bool isTyping) + List chatMessages, bool isTyping, bool isLoading) loaded, required TResult Function(String errorMessage) error, }) { @@ -266,8 +266,8 @@ class _$ChatRoomStateLoadingImpl implements ChatRoomStateLoading { TResult? whenOrNull( TResult? Function()? $default, { TResult? Function()? loading, - TResult? Function(ChatRoomModel chatRoom, ChatMessagesModel chatMessages, - bool isTyping)? + TResult? Function(ChatRoomModel chatRoom, + List chatMessages, bool isTyping, bool isLoading)? loaded, TResult? Function(String errorMessage)? error, }) { @@ -279,8 +279,8 @@ class _$ChatRoomStateLoadingImpl implements ChatRoomStateLoading { TResult maybeWhen( TResult Function()? $default, { TResult Function()? loading, - TResult Function(ChatRoomModel chatRoom, ChatMessagesModel chatMessages, - bool isTyping)? + TResult Function(ChatRoomModel chatRoom, + List chatMessages, bool isTyping, bool isLoading)? loaded, TResult Function(String errorMessage)? error, required TResult orElse(), @@ -340,10 +340,12 @@ abstract class _$$ChatRoomStateLoadedImplCopyWith<$Res> { __$$ChatRoomStateLoadedImplCopyWithImpl<$Res>; @useResult $Res call( - {ChatRoomModel chatRoom, ChatMessagesModel chatMessages, bool isTyping}); + {ChatRoomModel chatRoom, + List chatMessages, + bool isTyping, + bool isLoading}); $ChatRoomModelCopyWith<$Res> get chatRoom; - $ChatMessagesModelCopyWith<$Res> get chatMessages; } /// @nodoc @@ -360,6 +362,7 @@ class __$$ChatRoomStateLoadedImplCopyWithImpl<$Res> Object? chatRoom = null, Object? chatMessages = null, Object? isTyping = null, + Object? isLoading = null, }) { return _then(_$ChatRoomStateLoadedImpl( chatRoom: null == chatRoom @@ -367,13 +370,17 @@ class __$$ChatRoomStateLoadedImplCopyWithImpl<$Res> : chatRoom // ignore: cast_nullable_to_non_nullable as ChatRoomModel, chatMessages: null == chatMessages - ? _value.chatMessages + ? _value._chatMessages : chatMessages // ignore: cast_nullable_to_non_nullable - as ChatMessagesModel, + as List, isTyping: null == isTyping ? _value.isTyping : isTyping // ignore: cast_nullable_to_non_nullable as bool, + isLoading: null == isLoading + ? _value.isLoading + : isLoading // ignore: cast_nullable_to_non_nullable + as bool, )); } @@ -384,14 +391,6 @@ class __$$ChatRoomStateLoadedImplCopyWithImpl<$Res> return _then(_value.copyWith(chatRoom: value)); }); } - - @override - @pragma('vm:prefer-inline') - $ChatMessagesModelCopyWith<$Res> get chatMessages { - return $ChatMessagesModelCopyWith<$Res>(_value.chatMessages, (value) { - return _then(_value.copyWith(chatMessages: value)); - }); - } } /// @nodoc @@ -399,19 +398,29 @@ class __$$ChatRoomStateLoadedImplCopyWithImpl<$Res> class _$ChatRoomStateLoadedImpl implements ChatRoomStateLoaded { const _$ChatRoomStateLoadedImpl( {required this.chatRoom, - required this.chatMessages, - required this.isTyping}); + required final List chatMessages, + required this.isTyping, + required this.isLoading}) + : _chatMessages = chatMessages; @override final ChatRoomModel chatRoom; + final List _chatMessages; @override - final ChatMessagesModel chatMessages; + List get chatMessages { + if (_chatMessages is EqualUnmodifiableListView) return _chatMessages; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_chatMessages); + } + @override final bool isTyping; + @override + final bool isLoading; @override String toString() { - return 'ChatRoomState.loaded(chatRoom: $chatRoom, chatMessages: $chatMessages, isTyping: $isTyping)'; + return 'ChatRoomState.loaded(chatRoom: $chatRoom, chatMessages: $chatMessages, isTyping: $isTyping, isLoading: $isLoading)'; } @override @@ -421,15 +430,17 @@ class _$ChatRoomStateLoadedImpl implements ChatRoomStateLoaded { other is _$ChatRoomStateLoadedImpl && (identical(other.chatRoom, chatRoom) || other.chatRoom == chatRoom) && - (identical(other.chatMessages, chatMessages) || - other.chatMessages == chatMessages) && + const DeepCollectionEquality() + .equals(other._chatMessages, _chatMessages) && (identical(other.isTyping, isTyping) || - other.isTyping == isTyping)); + other.isTyping == isTyping) && + (identical(other.isLoading, isLoading) || + other.isLoading == isLoading)); } @override - int get hashCode => - Object.hash(runtimeType, chatRoom, chatMessages, isTyping); + int get hashCode => Object.hash(runtimeType, chatRoom, + const DeepCollectionEquality().hash(_chatMessages), isTyping, isLoading); @JsonKey(ignore: true) @override @@ -444,11 +455,11 @@ class _$ChatRoomStateLoadedImpl implements ChatRoomStateLoaded { TResult Function() $default, { required TResult Function() loading, required TResult Function(ChatRoomModel chatRoom, - ChatMessagesModel chatMessages, bool isTyping) + List chatMessages, bool isTyping, bool isLoading) loaded, required TResult Function(String errorMessage) error, }) { - return loaded(chatRoom, chatMessages, isTyping); + return loaded(chatRoom, chatMessages, isTyping, isLoading); } @override @@ -456,12 +467,12 @@ class _$ChatRoomStateLoadedImpl implements ChatRoomStateLoaded { TResult? whenOrNull( TResult? Function()? $default, { TResult? Function()? loading, - TResult? Function(ChatRoomModel chatRoom, ChatMessagesModel chatMessages, - bool isTyping)? + TResult? Function(ChatRoomModel chatRoom, + List chatMessages, bool isTyping, bool isLoading)? loaded, TResult? Function(String errorMessage)? error, }) { - return loaded?.call(chatRoom, chatMessages, isTyping); + return loaded?.call(chatRoom, chatMessages, isTyping, isLoading); } @override @@ -469,14 +480,14 @@ class _$ChatRoomStateLoadedImpl implements ChatRoomStateLoaded { TResult maybeWhen( TResult Function()? $default, { TResult Function()? loading, - TResult Function(ChatRoomModel chatRoom, ChatMessagesModel chatMessages, - bool isTyping)? + TResult Function(ChatRoomModel chatRoom, + List chatMessages, bool isTyping, bool isLoading)? loaded, TResult Function(String errorMessage)? error, required TResult orElse(), }) { if (loaded != null) { - return loaded(chatRoom, chatMessages, isTyping); + return loaded(chatRoom, chatMessages, isTyping, isLoading); } return orElse(); } @@ -522,12 +533,14 @@ class _$ChatRoomStateLoadedImpl implements ChatRoomStateLoaded { abstract class ChatRoomStateLoaded implements ChatRoomState { const factory ChatRoomStateLoaded( {required final ChatRoomModel chatRoom, - required final ChatMessagesModel chatMessages, - required final bool isTyping}) = _$ChatRoomStateLoadedImpl; + required final List chatMessages, + required final bool isTyping, + required final bool isLoading}) = _$ChatRoomStateLoadedImpl; ChatRoomModel get chatRoom; - ChatMessagesModel get chatMessages; + List get chatMessages; bool get isTyping; + bool get isLoading; @JsonKey(ignore: true) _$$ChatRoomStateLoadedImplCopyWith<_$ChatRoomStateLoadedImpl> get copyWith => throw _privateConstructorUsedError; @@ -602,7 +615,7 @@ class _$ChatRoomStateErrorImpl implements ChatRoomStateError { TResult Function() $default, { required TResult Function() loading, required TResult Function(ChatRoomModel chatRoom, - ChatMessagesModel chatMessages, bool isTyping) + List chatMessages, bool isTyping, bool isLoading) loaded, required TResult Function(String errorMessage) error, }) { @@ -614,8 +627,8 @@ class _$ChatRoomStateErrorImpl implements ChatRoomStateError { TResult? whenOrNull( TResult? Function()? $default, { TResult? Function()? loading, - TResult? Function(ChatRoomModel chatRoom, ChatMessagesModel chatMessages, - bool isTyping)? + TResult? Function(ChatRoomModel chatRoom, + List chatMessages, bool isTyping, bool isLoading)? loaded, TResult? Function(String errorMessage)? error, }) { @@ -627,8 +640,8 @@ class _$ChatRoomStateErrorImpl implements ChatRoomStateError { TResult maybeWhen( TResult Function()? $default, { TResult Function()? loading, - TResult Function(ChatRoomModel chatRoom, ChatMessagesModel chatMessages, - bool isTyping)? + TResult Function(ChatRoomModel chatRoom, + List chatMessages, bool isTyping, bool isLoading)? loaded, TResult Function(String errorMessage)? error, required TResult orElse(), diff --git a/lib/features/chat/presentation/pages/chat_screen.dart b/lib/features/chat/presentation/pages/chat_screen.dart index b25be66d..7d9528fa 100644 --- a/lib/features/chat/presentation/pages/chat_screen.dart +++ b/lib/features/chat/presentation/pages/chat_screen.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + 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/widgets/chat_bubble_widget.dart'; @@ -20,19 +22,15 @@ class ChatScreen extends StatefulWidget { class _ChatScreenState extends State { final scrollController = ScrollController(); + bool isLoading = false; Widget build(BuildContext context) { return BlocProvider( create: (BuildContext context) => sl()..initChat(widget.chatId), child: BlocBuilder( builder: (context, state) { - if (state is ChatRoomStateLoading) { - return Scaffold( - body: Center( - child: CupertinoActivityIndicator(), - ), - ); - } else if (state is ChatRoomStateLoaded) { + setupScrollController(context); + if (state is ChatRoomStateLoaded) { return _buildChat(context, state); } else if (state is ChatRoomStateError) { return Scaffold( @@ -55,16 +53,16 @@ class _ChatScreenState extends State { void setupScrollController(BuildContext context) { scrollController.addListener(() { + /// todo change rule for request new page if (scrollController.position.atEdge) { if (scrollController.position.pixels != 0) { - context.read(); + context.read().loadNextPage(widget.chatId); } } }); } _buildChat(BuildContext context, ChatRoomStateLoaded state) { - mediaQueryData = MediaQuery.of(context); return Scaffold( resizeToAvoidBottomInset: true, backgroundColor: PrimaryColors().gray50, @@ -136,12 +134,24 @@ class _ChatScreenState extends State { padding: EdgeInsets.symmetric(vertical: 16), sliver: SliverList( delegate: SliverChildBuilderDelegate( - (context, index) => ChatBubbleWidget( - text: state.chatMessages.messagesList[index].data.trim(), - isCurrentUser: state.chatRoom.users['to_chat_user']!.id != state.chatMessages.messagesList[index].authorId, - mediaData: state.chatMessages.messagesList[index].mediaData ?? [], - ), - childCount: state.chatMessages.messagesList.length, + (context, index) { + if (index < state.chatMessages.length) { + return ChatBubbleWidget( + text: state.chatMessages[index].data.trim(), + isCurrentUser: state.chatRoom.users['to_chat_user']!.id != state.chatMessages[index].authorId, + mediaData: state.chatMessages[index].mediaData ?? [], + ); + } else { + Timer(const Duration(milliseconds: 30), () { + scrollController.jumpTo(scrollController.position.maxScrollExtent); + }); + return Padding( + padding: const EdgeInsets.all(8), + child: Center(child: CupertinoActivityIndicator()), + ); + } + }, + childCount: state.chatMessages.length + (state.isLoading ? 1 : 0), ), ), ), diff --git a/lib/features/chat/repositories/chat_repository.dart b/lib/features/chat/repositories/chat_repository.dart index 38383f23..dbc67609 100644 --- a/lib/features/chat/repositories/chat_repository.dart +++ b/lib/features/chat/repositories/chat_repository.dart @@ -19,9 +19,9 @@ class ChatRepository { } } - Future getChatMessages(String chatId) async { + Future getChatMessages(String chatId, int page) async { try { - return (await chatService.getChatMessages(chatId)); + return (await chatService.getChatMessages(chatId, page)); } catch (e) { print(e); rethrow; diff --git a/lib/features/chat/services/chat_service.dart b/lib/features/chat/services/chat_service.dart index 3dd4a123..fa81a008 100644 --- a/lib/features/chat/services/chat_service.dart +++ b/lib/features/chat/services/chat_service.dart @@ -13,8 +13,8 @@ abstract class ChatService { @GET('/chat/show/{id}') Future> getChatRoom(@Path() String id); - @GET('/chat/show/{id}/messages') - Future getChatMessages(@Path() String id); + @GET('/chat/show/{id}/messages?page={page}') + Future getChatMessages(@Path() String id, @Path() int page); @GET('/profile/ws/params') Future getWebsocket(); diff --git a/lib/features/chat/services/chat_service.g.dart b/lib/features/chat/services/chat_service.g.dart index f4f04d1d..be6ed456 100644 --- a/lib/features/chat/services/chat_service.g.dart +++ b/lib/features/chat/services/chat_service.g.dart @@ -47,7 +47,10 @@ class _ChatService implements ChatService { } @override - Future getChatMessages(String id) async { + Future getChatMessages( + String id, + int page, + ) async { final _extra = {}; final queryParameters = {}; final _headers = {}; @@ -60,7 +63,7 @@ class _ChatService implements ChatService { ) .compose( _dio.options, - '/chat/show/${id}/messages', + '/chat/show/${id}/messages?page=${page}', queryParameters: queryParameters, data: _data, ) From 5f88f49c71891574b9f5cc63c8cb8952e976469e Mon Sep 17 00:00:00 2001 From: alex Date: Fri, 5 Jul 2024 22:28:03 +0300 Subject: [PATCH 2/3] feat(chat): add chat lazy loading, send message and websocket init for notifications and new chat messages --- lib/app_router.dart | 3 + .../initialization_cubit.dart | 36 ++++++++ .../bloc/chat_room_cubit/chat_room_cubit.dart | 52 ++++++----- .../chat/presentation/pages/chat_screen.dart | 91 ++++++++++--------- .../bloc/chat_select_bloc.dart | 21 +++-- .../chat/repositories/chat_repository.dart | 23 ++++- lib/features/chat/services/chat_service.dart | 18 +++- .../chat/services/chat_service.g.dart | 53 ++++++++++- lib/locator_service.dart | 2 +- lib/main.dart | 2 +- pubspec.yaml | 28 +----- 11 files changed, 216 insertions(+), 113 deletions(-) diff --git a/lib/app_router.dart b/lib/app_router.dart index 30cda0b9..b8f7008e 100644 --- a/lib/app_router.dart +++ b/lib/app_router.dart @@ -1,3 +1,4 @@ +import 'package:eatvio_app/core/initialization_cubit/initialization_cubit.dart'; import 'package:eatvio_app/core/token_storage.dart'; import 'package:eatvio_app/features/chat/presentation/pages/chat_screen.dart'; import 'package:eatvio_app/features/chat/presentation/pages/chat_select_screen/chat_select_screen.dart'; @@ -32,6 +33,7 @@ import 'package:eatvio_app/presentation/registration/weight_screen/weight_screen import 'package:eatvio_app/widgets/bottom_navigation_bar_widget.dart'; import 'package:eatvio_app/features/chat/presentation/pages/photo_view_screen.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; class AppRouter { @@ -46,6 +48,7 @@ class AppRouter { name: 'authScreen', redirect: (context, state) async { if (await TokenStorage.haveTokenInStorage()) { + await context.read().runInitialization(); return '/my-profile-screen'; } return null; diff --git a/lib/core/initialization_cubit/initialization_cubit.dart b/lib/core/initialization_cubit/initialization_cubit.dart index 4db6546f..89b67949 100644 --- a/lib/core/initialization_cubit/initialization_cubit.dart +++ b/lib/core/initialization_cubit/initialization_cubit.dart @@ -9,6 +9,38 @@ class InitializationCubit extends Cubit { InitializationCubit({required this.chatRepository}) : super(const InitializationState.loading()); + Future subscribeNotifications(int userId, PusherChannelsClient client) async { + final privateNotificationChannel = await client.privateChannel( + 'private-message-events.$userId', + authorizationDelegate: EndpointAuthorizableChannelTokenAuthorizationDelegate.forPrivateChannel( + authorizationEndpoint: Uri.parse('https://mobile-stage.eatvio.ru/broadcasting/auth'), + headers: { + 'Authorization': 'Bearer ${await TokenStorage.getTokenFromStorage()}', + }, + ), + ); + + privateNotificationChannel.subscribe(); + + privateNotificationChannel.bindToAll().listen((event) {}); + } + + Future subscribeChatMessages(int userId, PusherChannelsClient client) async { + final privateMessagesChannel = await client.privateChannel( + 'private-new-chat-messages.$userId', + authorizationDelegate: EndpointAuthorizableChannelTokenAuthorizationDelegate.forPrivateChannel( + authorizationEndpoint: Uri.parse('https://mobile-stage.eatvio.ru/broadcasting/auth'), + headers: { + 'Authorization': 'Bearer ${await TokenStorage.getTokenFromStorage()}', + }, + ), + ); + + privateMessagesChannel.subscribe(); + + privateMessagesChannel.bindToAll().listen((event) {}); + } + Future runInitialization() async { try { await TokenStorage.haveTokenInStorage(); @@ -40,6 +72,10 @@ class InitializationCubit extends Cubit { ), ); await client.connect(); + + await subscribeNotifications(wsUrl.userId, client); + await subscribeChatMessages(wsUrl.userId, client); + emit(InitializationState.success( client: client, )); diff --git a/lib/features/chat/presentation/bloc/chat_room_cubit/chat_room_cubit.dart b/lib/features/chat/presentation/bloc/chat_room_cubit/chat_room_cubit.dart index 712adf04..96e2bd2c 100644 --- a/lib/features/chat/presentation/bloc/chat_room_cubit/chat_room_cubit.dart +++ b/lib/features/chat/presentation/bloc/chat_room_cubit/chat_room_cubit.dart @@ -1,4 +1,5 @@ import 'dart:convert'; +import 'dart:io'; import 'package:dart_pusher_channels/dart_pusher_channels.dart'; import 'package:easy_debounce/easy_debounce.dart'; @@ -8,43 +9,47 @@ import 'package:eatvio_app/core/token_storage.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'; -import 'package:eatvio_app/locator_service.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; class ChatRoomCubit extends Cubit { final ChatRepository chatRepository; + final InitializationCubit initializationCubit; int currentPage = 1; late final int lastPage; + bool isLoading = false; late final PusherChannelsClient client; - late final PrivateChannel privateChannel; + late final PrivateChannel privateChatChannel; ChatRoomCubit({ required this.chatRepository, + required this.initializationCubit, }) : super(ChatRoomState.loading()); loadNextPage(String chatId) async { - if (currentPage < lastPage) { + if (currentPage < lastPage && !isLoading) { + isLoading = true; + emit(ChatRoomState.loaded( chatRoom: (state as ChatRoomStateLoaded).chatRoom, chatMessages: (state as ChatRoomStateLoaded).chatMessages, isTyping: (state as ChatRoomStateLoaded).isTyping, - isLoading: true, + isLoading: isLoading, )); try { final chatMessages = await chatRepository.getChatMessages(chatId, currentPage + 1); + isLoading = false; emit(ChatRoomState.loaded( chatRoom: (state as ChatRoomStateLoaded).chatRoom, chatMessages: [...(state as ChatRoomStateLoaded).chatMessages, ...chatMessages.messagesList], isTyping: (state as ChatRoomStateLoaded).isTyping, - isLoading: false, + isLoading: isLoading, )); currentPage++; - } catch (e) { print(e); emit(ChatRoomState.error(errorMessage: 'Ошибка получеия данных')); @@ -59,20 +64,13 @@ class ChatRoomCubit extends Cubit { final chatMessages = await chatRepository.getChatMessages(chatId, currentPage); lastPage = chatMessages.meta.lastPage; - final initCubit = sl(); - - if (initCubit.state is InitializationStateSuccess) { - await subscribeToChatChannel(initCubit: initCubit, chatId: chatId); - } else { - await initCubit.runInitialization(); - await subscribeToChatChannel(initCubit: initCubit, chatId: chatId); - } + await subscribeToChatChannel(initCubit: initializationCubit, chatId: chatId); emit(ChatRoomState.loaded( chatRoom: chatRoom, chatMessages: chatMessages.messagesList, isTyping: false, - isLoading: false, + isLoading: isLoading, )); } catch (e) { emit(ChatRoomState.error(errorMessage: 'Ошибка получеия данных')); @@ -85,7 +83,7 @@ class ChatRoomCubit extends Cubit { }) async { client = (initCubit.state as InitializationStateSuccess).client; - privateChannel = await client.privateChannel( + privateChatChannel = await client.privateChannel( 'private-direct-chat.$chatId', authorizationDelegate: EndpointAuthorizableChannelTokenAuthorizationDelegate.forPrivateChannel( authorizationEndpoint: Uri.parse('https://mobile-stage.eatvio.ru/broadcasting/auth'), @@ -95,9 +93,9 @@ class ChatRoomCubit extends Cubit { ), ); - privateChannel.subscribe(); + privateChatChannel.subscribe(); - privateChannel.bind('App\\Events\\ChatMessageUpdated').listen((event) { + privateChatChannel.bind('App\\Events\\ChatMessageUpdated').listen((event) { List messagesList = [ ChatMessageModel.fromJson(jsonDecode(event.data)['message']), ...(state as ChatRoomStateLoaded).chatMessages, @@ -107,18 +105,18 @@ class ChatRoomCubit extends Cubit { chatRoom: (state as ChatRoomStateLoaded).chatRoom, chatMessages: messagesList, isTyping: (state as ChatRoomStateLoaded).isTyping, - isLoading: false, + isLoading: isLoading, ), ); }); - privateChannel.bind('client-typing').listen((event) { + privateChatChannel.bind('client-typing').listen((event) { emit( ChatRoomState.loaded( chatRoom: (state as ChatRoomStateLoaded).chatRoom, chatMessages: (state as ChatRoomStateLoaded).chatMessages, isTyping: true, - isLoading: false, + isLoading: isLoading, ), ); EasyDebounce.debounce( @@ -129,16 +127,24 @@ class ChatRoomCubit extends Cubit { chatRoom: (state as ChatRoomStateLoaded).chatRoom, chatMessages: (state as ChatRoomStateLoaded).chatMessages, isTyping: false, - isLoading: false, + isLoading: isLoading, ), ), ); }); } + sendMessage({ + required String chatId, + required String data, + required List filesList, + }) async { + await chatRepository.sendMessage(chatId: chatId, data: data); + } + @override Future close() async { - privateChannel.unsubscribe(); + privateChatChannel.unsubscribe(); super.close(); } } diff --git a/lib/features/chat/presentation/pages/chat_screen.dart b/lib/features/chat/presentation/pages/chat_screen.dart index 7d9528fa..258b7317 100644 --- a/lib/features/chat/presentation/pages/chat_screen.dart +++ b/lib/features/chat/presentation/pages/chat_screen.dart @@ -1,5 +1,3 @@ -import 'dart:async'; - 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/widgets/chat_bubble_widget.dart'; @@ -7,6 +5,7 @@ import 'package:eatvio_app/locator_service.dart'; import 'package:flutter/cupertino.dart'; import 'package:eatvio_app/core/app_export.dart'; import 'package:flutter/material.dart'; +import 'package:lazy_load_scrollview/lazy_load_scrollview.dart'; class ChatScreen extends StatefulWidget { final String chatId; @@ -22,14 +21,13 @@ class ChatScreen extends StatefulWidget { class _ChatScreenState extends State { final scrollController = ScrollController(); - bool isLoading = false; + final TextEditingController textEditingController = TextEditingController(); Widget build(BuildContext context) { return BlocProvider( create: (BuildContext context) => sl()..initChat(widget.chatId), child: BlocBuilder( builder: (context, state) { - setupScrollController(context); if (state is ChatRoomStateLoaded) { return _buildChat(context, state); } else if (state is ChatRoomStateError) { @@ -51,17 +49,6 @@ class _ChatScreenState extends State { ); } - void setupScrollController(BuildContext context) { - scrollController.addListener(() { - /// todo change rule for request new page - if (scrollController.position.atEdge) { - if (scrollController.position.pixels != 0) { - context.read().loadNextPage(widget.chatId); - } - } - }); - } - _buildChat(BuildContext context, ChatRoomStateLoaded state) { return Scaffold( resizeToAvoidBottomInset: true, @@ -125,37 +112,39 @@ class _ChatScreenState extends State { child: Container( color: Color(0xFFF0F2F5), width: double.maxFinite, - child: CustomScrollView( - physics: BouncingScrollPhysics(), - controller: scrollController, - reverse: true, - slivers: [ - SliverPadding( - padding: EdgeInsets.symmetric(vertical: 16), - sliver: SliverList( - delegate: SliverChildBuilderDelegate( - (context, index) { - if (index < state.chatMessages.length) { - return ChatBubbleWidget( - text: state.chatMessages[index].data.trim(), - isCurrentUser: state.chatRoom.users['to_chat_user']!.id != state.chatMessages[index].authorId, - mediaData: state.chatMessages[index].mediaData ?? [], - ); - } else { - Timer(const Duration(milliseconds: 30), () { - scrollController.jumpTo(scrollController.position.maxScrollExtent); - }); - return Padding( - padding: const EdgeInsets.all(8), - child: Center(child: CupertinoActivityIndicator()), - ); - } - }, - childCount: state.chatMessages.length + (state.isLoading ? 1 : 0), + child: LazyLoadScrollView( + onEndOfPage: () => context.read().loadNextPage(widget.chatId), + isLoading: state.isLoading, + scrollOffset: MediaQuery.of(context).size.height.toInt(), + child: CustomScrollView( + physics: BouncingScrollPhysics(), + controller: scrollController, + reverse: true, + slivers: [ + SliverPadding( + padding: EdgeInsets.symmetric(vertical: 16), + sliver: SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) { + if (index < state.chatMessages.length) { + return ChatBubbleWidget( + text: state.chatMessages[index].data.trim(), + isCurrentUser: state.chatRoom.users['to_chat_user']!.id != state.chatMessages[index].authorId, + mediaData: state.chatMessages[index].mediaData ?? [], + ); + } else { + return Padding( + padding: const EdgeInsets.all(8), + child: Center(child: CupertinoActivityIndicator()), + ); + } + }, + childCount: state.chatMessages.length + (state.isLoading ? 1 : 0), + ), ), ), - ), - ], + ], + ), ), ), ), @@ -182,6 +171,7 @@ class _ChatScreenState extends State { SizedBox( width: mediaQueryData.size.width - 96, child: TextFormField( + controller: textEditingController, maxLines: 5, minLines: 1, keyboardType: TextInputType.multiline, @@ -228,7 +218,18 @@ class _ChatScreenState extends State { SizedBox(width: 8), InkWell( borderRadius: BorderRadiusStyle.circleBorder17, - onTap: () {}, + onTap: () { + if (textEditingController.text.isNotEmpty) { + context.read().sendMessage( + chatId: widget.chatId, + data: textEditingController.text.trim(), + filesList: [], + ); + setState(() { + textEditingController.clear(); + }); + } + }, child: Ink( height: 32, width: 32, diff --git a/lib/features/chat/presentation/pages/chat_select_screen/bloc/chat_select_bloc.dart b/lib/features/chat/presentation/pages/chat_select_screen/bloc/chat_select_bloc.dart index 9bd2845c..a055b5ef 100644 --- a/lib/features/chat/presentation/pages/chat_select_screen/bloc/chat_select_bloc.dart +++ b/lib/features/chat/presentation/pages/chat_select_screen/bloc/chat_select_bloc.dart @@ -13,6 +13,7 @@ import '/core/app_export.dart'; import 'package:go_router/go_router.dart'; part 'chat_select_event.dart'; + part 'chat_select_state.dart'; /// A bloc that manages the state of a ChatSelect according to the event that is dispatched to it. @@ -68,15 +69,19 @@ class ChatSelectBloc extends Bloc { if (data is Map && data.containsKey('chatRooms')) { if (state is ChatSelectLoadedState) { var currentState = state as ChatSelectLoadedState; - emit(currentState.copyWith( - chatSelectModelObj: currentState.chatSelectModelObj, - chatRooms: currentState.chatRooms, - ),); + emit( + currentState.copyWith( + chatSelectModelObj: currentState.chatSelectModelObj, + chatRooms: currentState.chatRooms, + ), + ); } else { - emit(ChatSelectLoadedState( - chatRooms: fillChatEntryList(data['chatRooms']), - //chatRooms: [], - ),); + emit( + ChatSelectLoadedState( + chatRooms: fillChatEntryList(data['chatRooms']), + //chatRooms: [], + ), + ); } } else { // Один или несколько ключевых полей отсутствуют, обрабатываем ошибку diff --git a/lib/features/chat/repositories/chat_repository.dart b/lib/features/chat/repositories/chat_repository.dart index dbc67609..c35847f2 100644 --- a/lib/features/chat/repositories/chat_repository.dart +++ b/lib/features/chat/repositories/chat_repository.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + 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/websocket_info_model.dart'; @@ -10,9 +12,26 @@ class ChatRepository { required this.chatService, }); + Future sendMessage({ + required String chatId, + String? data, + List? filesList, + }) async { + try { + await chatService.sendMessage( + id: chatId, + data: data, + filesList: filesList, + ); + } catch (e) { + print(e); + rethrow; + } + } + Future getChatRoom(String chatId) async { try { - return (await chatService.getChatRoom(chatId))['chatRoom']!; + return (await chatService.getChatRoom(id: chatId))['chatRoom']!; } catch (e) { print(e); rethrow; @@ -21,7 +40,7 @@ class ChatRepository { Future getChatMessages(String chatId, int page) async { try { - return (await chatService.getChatMessages(chatId, page)); + return (await chatService.getChatMessages(id: chatId, page: page)); } catch (e) { print(e); rethrow; diff --git a/lib/features/chat/services/chat_service.dart b/lib/features/chat/services/chat_service.dart index fa81a008..675d6806 100644 --- a/lib/features/chat/services/chat_service.dart +++ b/lib/features/chat/services/chat_service.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:eatvio_app/features/chat/models/chat_messages_model.dart'; import 'package:eatvio_app/features/chat/models/chat_room_model.dart'; import 'package:dio/dio.dart'; @@ -11,10 +13,22 @@ abstract class ChatService { factory ChatService(Dio dio, {String baseUrl}) = _ChatService; @GET('/chat/show/{id}') - Future> getChatRoom(@Path() String id); + Future> getChatRoom({ + @Path() required String id, + }); + + @POST('/chat/{id}/message/send') + Future sendMessage({ + @Path() required String id, + @Part() String? data, + @Part() List? filesList, + }); @GET('/chat/show/{id}/messages?page={page}') - Future getChatMessages(@Path() String id, @Path() int page); + Future getChatMessages({ + @Path() required String id, + @Path() required int page, + }); @GET('/profile/ws/params') Future getWebsocket(); diff --git a/lib/features/chat/services/chat_service.g.dart b/lib/features/chat/services/chat_service.g.dart index be6ed456..65eb7c5e 100644 --- a/lib/features/chat/services/chat_service.g.dart +++ b/lib/features/chat/services/chat_service.g.dart @@ -19,7 +19,7 @@ class _ChatService implements ChatService { String? baseUrl; @override - Future> getChatRoom(String id) async { + Future> getChatRoom({required String id}) async { final _extra = {}; final queryParameters = {}; final _headers = {}; @@ -47,10 +47,53 @@ class _ChatService implements ChatService { } @override - Future getChatMessages( - String id, - int page, - ) async { + Future sendMessage({ + required String id, + String? data, + List? filesList, + }) async { + final _extra = {}; + final queryParameters = {}; + queryParameters.removeWhere((k, v) => v == null); + final _headers = {}; + final _data = FormData(); + if (data != null) { + _data.fields.add(MapEntry( + 'data', + data, + )); + } + if (filesList != null) { + _data.files.addAll(filesList.map((i) => MapEntry( + 'filesList', + MultipartFile.fromFileSync( + i.path, + filename: i.path.split(Platform.pathSeparator).last, + )))); + } + await _dio.fetch(_setStreamType(Options( + method: 'POST', + headers: _headers, + extra: _extra, + ) + .compose( + _dio.options, + '/chat/${id}/message/send', + queryParameters: queryParameters, + data: _data, + ) + .copyWith( + baseUrl: _combineBaseUrls( + _dio.options.baseUrl, + baseUrl, + )))); + } + + @override + Future getChatMessages({ + required String id, + required int page, + }) async { final _extra = {}; final queryParameters = {}; final _headers = {}; diff --git a/lib/locator_service.dart b/lib/locator_service.dart index fb2637ec..5a924d20 100644 --- a/lib/locator_service.dart +++ b/lib/locator_service.dart @@ -18,7 +18,7 @@ Future init() async { // BLoC / Cubit sl.registerLazySingleton(() => InitializationCubit(chatRepository: sl())); - sl.registerFactory(() => ChatRoomCubit(chatRepository: sl())); + sl.registerFactory(() => ChatRoomCubit(chatRepository: sl(), initializationCubit: sl())); sl.registerFactory(() => FeedCubit(feedRepository: sl())); sl.registerFactory(() => BlogPostCubit(blogPostRepository: sl())); diff --git a/lib/main.dart b/lib/main.dart index 684e227e..3f285adc 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -42,7 +42,7 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) { return MultiBlocProvider( providers: [ - BlocProvider(create: (context) => InitializationCubit(chatRepository: sl())), + BlocProvider(create: (context) => sl()), BlocProvider(create: (context) => ThemeBloc(ThemeState(themeType: PrefUtils().getThemeData()))), BlocProvider(create: (context) => sl()..loadFeedBlogPostList()), BlocProvider(create: (context) => MyProfileBloc(MyProfileState(myProfileModelObj: MyProfileModel()))..add(MyProfileInitialEvent())), diff --git a/pubspec.yaml b/pubspec.yaml index 256f8923..87ed29e8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -13,61 +13,37 @@ dependencies: flutter_localizations: sdk: flutter - # BLoC flutter_bloc: ^8.1.6 - # local storage shared_preferences: ^2.2.3 hive: ^2.2.3 hive_flutter: ^1.1.0 - # cached_network_image: ^3.3.1 - # flutter_svg: ^2.0.10 - # equatable: ^2.0.5 - # freezed_annotation: ^2.4.2 - # json_annotation: ^4.9.0 - # validators: ^3.0.0 - # isar_flutter_libs: ^3.1.0 - # path_provider: ^2.1.3 - # fl_chart: ^0.68.0 - # flutter_spinkit: ^5.2.1 - # flutter_dotenv: ^5.1.0 - # carousel_slider: ^4.2.1 - # flutter_form_builder: ^9.3.0 - # image_picker: ^1.1.2 - # flutter_typeahead: ^5.2.0 - # navigation go_router: ^14.2.0 - # keyboard_dismisser: ^3.0.0 - # service generator retrofit: ^4.1.0 - # di get_it: ^7.7.0 - # http request dio: ^5.5.0 - # pusher dart_pusher_channels: ^1.2.2 - # photo gallery viewer + dismissible page photo_view: ^0.15.0 dismissible_page: ^1.0.2 - # url launcher url_launcher: ^6.3.0 - # debounce easy_debounce: ^2.0.3 + lazy_load_scrollview: ^1.3.0 + badges: ^3.1.2 dev_dependencies: build_runner: ^2.4.11 From f79b9f03ae9e4343f78cb93f506a5f60093d072d Mon Sep 17 00:00:00 2001 From: alex Date: Tue, 9 Jul 2024 11:17:32 +0300 Subject: [PATCH 3/3] feat(chat): add badges --- lib/app_router.dart | 2 +- .../initialization_cubit.dart | 11 +- .../new_messages_cubit.dart | 13 + .../new_messages_state.dart | 18 + .../new_messages_state.freezed.dart | 616 +++++++++++++++++ .../notifications_cubit.dart | 13 + .../notifications_state.dart | 18 + .../notifications_state.freezed.dart | 621 ++++++++++++++++++ .../widgets/bottom_navigation_bar_widget.dart | 77 ++- .../chat/presentation/pages/chat_screen.dart | 4 +- lib/locator_service.dart | 4 + lib/main.dart | 5 + 12 files changed, 1372 insertions(+), 30 deletions(-) create mode 100644 lib/features/bottom_navigation_bar/presentation/bloc/new_messages_cubit/new_messages_cubit.dart create mode 100644 lib/features/bottom_navigation_bar/presentation/bloc/new_messages_cubit/new_messages_state.dart create mode 100644 lib/features/bottom_navigation_bar/presentation/bloc/new_messages_cubit/new_messages_state.freezed.dart create mode 100644 lib/features/bottom_navigation_bar/presentation/bloc/notifications_cubit/notifications_cubit.dart create mode 100644 lib/features/bottom_navigation_bar/presentation/bloc/notifications_cubit/notifications_state.dart create mode 100644 lib/features/bottom_navigation_bar/presentation/bloc/notifications_cubit/notifications_state.freezed.dart rename lib/{ => features/bottom_navigation_bar/presentation}/widgets/bottom_navigation_bar_widget.dart (60%) diff --git a/lib/app_router.dart b/lib/app_router.dart index b8f7008e..02c517f7 100644 --- a/lib/app_router.dart +++ b/lib/app_router.dart @@ -30,7 +30,7 @@ import 'package:eatvio_app/presentation/registration/language_select_screen/lang import 'package:eatvio_app/presentation/registration/medicine_intake_screen/medicine_intake_screen.dart'; import 'package:eatvio_app/presentation/registration/name_screen/name_screen.dart'; import 'package:eatvio_app/presentation/registration/weight_screen/weight_screen.dart'; -import 'package:eatvio_app/widgets/bottom_navigation_bar_widget.dart'; +import 'package:eatvio_app/features/bottom_navigation_bar/presentation/widgets/bottom_navigation_bar_widget.dart'; import 'package:eatvio_app/features/chat/presentation/pages/photo_view_screen.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; diff --git a/lib/core/initialization_cubit/initialization_cubit.dart b/lib/core/initialization_cubit/initialization_cubit.dart index 89b67949..a7034e21 100644 --- a/lib/core/initialization_cubit/initialization_cubit.dart +++ b/lib/core/initialization_cubit/initialization_cubit.dart @@ -2,7 +2,10 @@ import 'package:dart_pusher_channels/dart_pusher_channels.dart'; import 'package:eatvio_app/core/app_export.dart'; import 'package:eatvio_app/core/initialization_cubit/initialization_state.dart'; import 'package:eatvio_app/core/token_storage.dart'; +import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/new_messages_cubit/new_messages_cubit.dart'; +import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/notifications_cubit/notifications_cubit.dart'; import 'package:eatvio_app/features/chat/repositories/chat_repository.dart'; +import 'package:eatvio_app/locator_service.dart'; class InitializationCubit extends Cubit { final ChatRepository chatRepository; @@ -22,7 +25,9 @@ class InitializationCubit extends Cubit { privateNotificationChannel.subscribe(); - privateNotificationChannel.bindToAll().listen((event) {}); + privateNotificationChannel.bindToAll().listen((event) { + sl().incrementCounter(); + }); } Future subscribeChatMessages(int userId, PusherChannelsClient client) async { @@ -38,7 +43,9 @@ class InitializationCubit extends Cubit { privateMessagesChannel.subscribe(); - privateMessagesChannel.bindToAll().listen((event) {}); + privateMessagesChannel.bindToAll().listen((event) { + sl().incrementCounter(); + }); } Future runInitialization() async { diff --git a/lib/features/bottom_navigation_bar/presentation/bloc/new_messages_cubit/new_messages_cubit.dart b/lib/features/bottom_navigation_bar/presentation/bloc/new_messages_cubit/new_messages_cubit.dart new file mode 100644 index 00000000..8b2a0f5a --- /dev/null +++ b/lib/features/bottom_navigation_bar/presentation/bloc/new_messages_cubit/new_messages_cubit.dart @@ -0,0 +1,13 @@ +import 'package:eatvio_app/core/app_export.dart'; +import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/new_messages_cubit/new_messages_state.dart'; + +class NewMessagesCubit extends Cubit { + int _messagesCount = 0; + + NewMessagesCubit() : super(const NewMessagesState.empty()); + + incrementCounter() { + _messagesCount++; + emit(NewMessagesState.loaded(messagesCount: _messagesCount)); + } +} diff --git a/lib/features/bottom_navigation_bar/presentation/bloc/new_messages_cubit/new_messages_state.dart b/lib/features/bottom_navigation_bar/presentation/bloc/new_messages_cubit/new_messages_state.dart new file mode 100644 index 00000000..4ed48569 --- /dev/null +++ b/lib/features/bottom_navigation_bar/presentation/bloc/new_messages_cubit/new_messages_state.dart @@ -0,0 +1,18 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'new_messages_state.freezed.dart'; + +@freezed +abstract class NewMessagesState with _$NewMessagesState { + const factory NewMessagesState() = _NewMessagesState; + + const factory NewMessagesState.empty() = NewMessagesStateEmpty; + + const factory NewMessagesState.loaded({ + required int messagesCount, + }) = NewMessagesStateLoaded; + + const factory NewMessagesState.error({ + required String errorMessage, + }) = NewMessagesStateError; +} diff --git a/lib/features/bottom_navigation_bar/presentation/bloc/new_messages_cubit/new_messages_state.freezed.dart b/lib/features/bottom_navigation_bar/presentation/bloc/new_messages_cubit/new_messages_state.freezed.dart new file mode 100644 index 00000000..02a1c4bd --- /dev/null +++ b/lib/features/bottom_navigation_bar/presentation/bloc/new_messages_cubit/new_messages_state.freezed.dart @@ -0,0 +1,616 @@ +// 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 'new_messages_state.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +/// @nodoc +mixin _$NewMessagesState { + @optionalTypeArgs + TResult when( + TResult Function() $default, { + required TResult Function() empty, + required TResult Function(int messagesCount) loaded, + required TResult Function(String errorMessage) error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull( + TResult? Function()? $default, { + TResult? Function()? empty, + TResult? Function(int messagesCount)? loaded, + TResult? Function(String errorMessage)? error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen( + TResult Function()? $default, { + TResult Function()? empty, + TResult Function(int messagesCount)? loaded, + TResult Function(String errorMessage)? error, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map( + TResult Function(_NewMessagesState value) $default, { + required TResult Function(NewMessagesStateEmpty value) empty, + required TResult Function(NewMessagesStateLoaded value) loaded, + required TResult Function(NewMessagesStateError value) error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull( + TResult? Function(_NewMessagesState value)? $default, { + TResult? Function(NewMessagesStateEmpty value)? empty, + TResult? Function(NewMessagesStateLoaded value)? loaded, + TResult? Function(NewMessagesStateError value)? error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap( + TResult Function(_NewMessagesState value)? $default, { + TResult Function(NewMessagesStateEmpty value)? empty, + TResult Function(NewMessagesStateLoaded value)? loaded, + TResult Function(NewMessagesStateError value)? error, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $NewMessagesStateCopyWith<$Res> { + factory $NewMessagesStateCopyWith( + NewMessagesState value, $Res Function(NewMessagesState) then) = + _$NewMessagesStateCopyWithImpl<$Res, NewMessagesState>; +} + +/// @nodoc +class _$NewMessagesStateCopyWithImpl<$Res, $Val extends NewMessagesState> + implements $NewMessagesStateCopyWith<$Res> { + _$NewMessagesStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; +} + +/// @nodoc +abstract class _$$NewMessagesStateImplCopyWith<$Res> { + factory _$$NewMessagesStateImplCopyWith(_$NewMessagesStateImpl value, + $Res Function(_$NewMessagesStateImpl) then) = + __$$NewMessagesStateImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$NewMessagesStateImplCopyWithImpl<$Res> + extends _$NewMessagesStateCopyWithImpl<$Res, _$NewMessagesStateImpl> + implements _$$NewMessagesStateImplCopyWith<$Res> { + __$$NewMessagesStateImplCopyWithImpl(_$NewMessagesStateImpl _value, + $Res Function(_$NewMessagesStateImpl) _then) + : super(_value, _then); +} + +/// @nodoc + +class _$NewMessagesStateImpl implements _NewMessagesState { + const _$NewMessagesStateImpl(); + + @override + String toString() { + return 'NewMessagesState()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$NewMessagesStateImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when( + TResult Function() $default, { + required TResult Function() empty, + required TResult Function(int messagesCount) loaded, + required TResult Function(String errorMessage) error, + }) { + return $default(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull( + TResult? Function()? $default, { + TResult? Function()? empty, + TResult? Function(int messagesCount)? loaded, + TResult? Function(String errorMessage)? error, + }) { + return $default?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen( + TResult Function()? $default, { + TResult Function()? empty, + TResult Function(int messagesCount)? loaded, + TResult Function(String errorMessage)? error, + required TResult orElse(), + }) { + if ($default != null) { + return $default(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map( + TResult Function(_NewMessagesState value) $default, { + required TResult Function(NewMessagesStateEmpty value) empty, + required TResult Function(NewMessagesStateLoaded value) loaded, + required TResult Function(NewMessagesStateError value) error, + }) { + return $default(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull( + TResult? Function(_NewMessagesState value)? $default, { + TResult? Function(NewMessagesStateEmpty value)? empty, + TResult? Function(NewMessagesStateLoaded value)? loaded, + TResult? Function(NewMessagesStateError value)? error, + }) { + return $default?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap( + TResult Function(_NewMessagesState value)? $default, { + TResult Function(NewMessagesStateEmpty value)? empty, + TResult Function(NewMessagesStateLoaded value)? loaded, + TResult Function(NewMessagesStateError value)? error, + required TResult orElse(), + }) { + if ($default != null) { + return $default(this); + } + return orElse(); + } +} + +abstract class _NewMessagesState implements NewMessagesState { + const factory _NewMessagesState() = _$NewMessagesStateImpl; +} + +/// @nodoc +abstract class _$$NewMessagesStateEmptyImplCopyWith<$Res> { + factory _$$NewMessagesStateEmptyImplCopyWith( + _$NewMessagesStateEmptyImpl value, + $Res Function(_$NewMessagesStateEmptyImpl) then) = + __$$NewMessagesStateEmptyImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$NewMessagesStateEmptyImplCopyWithImpl<$Res> + extends _$NewMessagesStateCopyWithImpl<$Res, _$NewMessagesStateEmptyImpl> + implements _$$NewMessagesStateEmptyImplCopyWith<$Res> { + __$$NewMessagesStateEmptyImplCopyWithImpl(_$NewMessagesStateEmptyImpl _value, + $Res Function(_$NewMessagesStateEmptyImpl) _then) + : super(_value, _then); +} + +/// @nodoc + +class _$NewMessagesStateEmptyImpl implements NewMessagesStateEmpty { + const _$NewMessagesStateEmptyImpl(); + + @override + String toString() { + return 'NewMessagesState.empty()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$NewMessagesStateEmptyImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when( + TResult Function() $default, { + required TResult Function() empty, + required TResult Function(int messagesCount) loaded, + required TResult Function(String errorMessage) error, + }) { + return empty(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull( + TResult? Function()? $default, { + TResult? Function()? empty, + TResult? Function(int messagesCount)? loaded, + TResult? Function(String errorMessage)? error, + }) { + return empty?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen( + TResult Function()? $default, { + TResult Function()? empty, + TResult Function(int messagesCount)? loaded, + TResult Function(String errorMessage)? error, + required TResult orElse(), + }) { + if (empty != null) { + return empty(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map( + TResult Function(_NewMessagesState value) $default, { + required TResult Function(NewMessagesStateEmpty value) empty, + required TResult Function(NewMessagesStateLoaded value) loaded, + required TResult Function(NewMessagesStateError value) error, + }) { + return empty(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull( + TResult? Function(_NewMessagesState value)? $default, { + TResult? Function(NewMessagesStateEmpty value)? empty, + TResult? Function(NewMessagesStateLoaded value)? loaded, + TResult? Function(NewMessagesStateError value)? error, + }) { + return empty?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap( + TResult Function(_NewMessagesState value)? $default, { + TResult Function(NewMessagesStateEmpty value)? empty, + TResult Function(NewMessagesStateLoaded value)? loaded, + TResult Function(NewMessagesStateError value)? error, + required TResult orElse(), + }) { + if (empty != null) { + return empty(this); + } + return orElse(); + } +} + +abstract class NewMessagesStateEmpty implements NewMessagesState { + const factory NewMessagesStateEmpty() = _$NewMessagesStateEmptyImpl; +} + +/// @nodoc +abstract class _$$NewMessagesStateLoadedImplCopyWith<$Res> { + factory _$$NewMessagesStateLoadedImplCopyWith( + _$NewMessagesStateLoadedImpl value, + $Res Function(_$NewMessagesStateLoadedImpl) then) = + __$$NewMessagesStateLoadedImplCopyWithImpl<$Res>; + @useResult + $Res call({int messagesCount}); +} + +/// @nodoc +class __$$NewMessagesStateLoadedImplCopyWithImpl<$Res> + extends _$NewMessagesStateCopyWithImpl<$Res, _$NewMessagesStateLoadedImpl> + implements _$$NewMessagesStateLoadedImplCopyWith<$Res> { + __$$NewMessagesStateLoadedImplCopyWithImpl( + _$NewMessagesStateLoadedImpl _value, + $Res Function(_$NewMessagesStateLoadedImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? messagesCount = null, + }) { + return _then(_$NewMessagesStateLoadedImpl( + messagesCount: null == messagesCount + ? _value.messagesCount + : messagesCount // ignore: cast_nullable_to_non_nullable + as int, + )); + } +} + +/// @nodoc + +class _$NewMessagesStateLoadedImpl implements NewMessagesStateLoaded { + const _$NewMessagesStateLoadedImpl({required this.messagesCount}); + + @override + final int messagesCount; + + @override + String toString() { + return 'NewMessagesState.loaded(messagesCount: $messagesCount)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$NewMessagesStateLoadedImpl && + (identical(other.messagesCount, messagesCount) || + other.messagesCount == messagesCount)); + } + + @override + int get hashCode => Object.hash(runtimeType, messagesCount); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$NewMessagesStateLoadedImplCopyWith<_$NewMessagesStateLoadedImpl> + get copyWith => __$$NewMessagesStateLoadedImplCopyWithImpl< + _$NewMessagesStateLoadedImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when( + TResult Function() $default, { + required TResult Function() empty, + required TResult Function(int messagesCount) loaded, + required TResult Function(String errorMessage) error, + }) { + return loaded(messagesCount); + } + + @override + @optionalTypeArgs + TResult? whenOrNull( + TResult? Function()? $default, { + TResult? Function()? empty, + TResult? Function(int messagesCount)? loaded, + TResult? Function(String errorMessage)? error, + }) { + return loaded?.call(messagesCount); + } + + @override + @optionalTypeArgs + TResult maybeWhen( + TResult Function()? $default, { + TResult Function()? empty, + TResult Function(int messagesCount)? loaded, + TResult Function(String errorMessage)? error, + required TResult orElse(), + }) { + if (loaded != null) { + return loaded(messagesCount); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map( + TResult Function(_NewMessagesState value) $default, { + required TResult Function(NewMessagesStateEmpty value) empty, + required TResult Function(NewMessagesStateLoaded value) loaded, + required TResult Function(NewMessagesStateError value) error, + }) { + return loaded(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull( + TResult? Function(_NewMessagesState value)? $default, { + TResult? Function(NewMessagesStateEmpty value)? empty, + TResult? Function(NewMessagesStateLoaded value)? loaded, + TResult? Function(NewMessagesStateError value)? error, + }) { + return loaded?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap( + TResult Function(_NewMessagesState value)? $default, { + TResult Function(NewMessagesStateEmpty value)? empty, + TResult Function(NewMessagesStateLoaded value)? loaded, + TResult Function(NewMessagesStateError value)? error, + required TResult orElse(), + }) { + if (loaded != null) { + return loaded(this); + } + return orElse(); + } +} + +abstract class NewMessagesStateLoaded implements NewMessagesState { + const factory NewMessagesStateLoaded({required final int messagesCount}) = + _$NewMessagesStateLoadedImpl; + + int get messagesCount; + @JsonKey(ignore: true) + _$$NewMessagesStateLoadedImplCopyWith<_$NewMessagesStateLoadedImpl> + get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$NewMessagesStateErrorImplCopyWith<$Res> { + factory _$$NewMessagesStateErrorImplCopyWith( + _$NewMessagesStateErrorImpl value, + $Res Function(_$NewMessagesStateErrorImpl) then) = + __$$NewMessagesStateErrorImplCopyWithImpl<$Res>; + @useResult + $Res call({String errorMessage}); +} + +/// @nodoc +class __$$NewMessagesStateErrorImplCopyWithImpl<$Res> + extends _$NewMessagesStateCopyWithImpl<$Res, _$NewMessagesStateErrorImpl> + implements _$$NewMessagesStateErrorImplCopyWith<$Res> { + __$$NewMessagesStateErrorImplCopyWithImpl(_$NewMessagesStateErrorImpl _value, + $Res Function(_$NewMessagesStateErrorImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? errorMessage = null, + }) { + return _then(_$NewMessagesStateErrorImpl( + errorMessage: null == errorMessage + ? _value.errorMessage + : errorMessage // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc + +class _$NewMessagesStateErrorImpl implements NewMessagesStateError { + const _$NewMessagesStateErrorImpl({required this.errorMessage}); + + @override + final String errorMessage; + + @override + String toString() { + return 'NewMessagesState.error(errorMessage: $errorMessage)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$NewMessagesStateErrorImpl && + (identical(other.errorMessage, errorMessage) || + other.errorMessage == errorMessage)); + } + + @override + int get hashCode => Object.hash(runtimeType, errorMessage); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$NewMessagesStateErrorImplCopyWith<_$NewMessagesStateErrorImpl> + get copyWith => __$$NewMessagesStateErrorImplCopyWithImpl< + _$NewMessagesStateErrorImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when( + TResult Function() $default, { + required TResult Function() empty, + required TResult Function(int messagesCount) loaded, + required TResult Function(String errorMessage) error, + }) { + return error(errorMessage); + } + + @override + @optionalTypeArgs + TResult? whenOrNull( + TResult? Function()? $default, { + TResult? Function()? empty, + TResult? Function(int messagesCount)? loaded, + TResult? Function(String errorMessage)? error, + }) { + return error?.call(errorMessage); + } + + @override + @optionalTypeArgs + TResult maybeWhen( + TResult Function()? $default, { + TResult Function()? empty, + TResult Function(int messagesCount)? loaded, + TResult Function(String errorMessage)? error, + required TResult orElse(), + }) { + if (error != null) { + return error(errorMessage); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map( + TResult Function(_NewMessagesState value) $default, { + required TResult Function(NewMessagesStateEmpty value) empty, + required TResult Function(NewMessagesStateLoaded value) loaded, + required TResult Function(NewMessagesStateError value) error, + }) { + return error(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull( + TResult? Function(_NewMessagesState value)? $default, { + TResult? Function(NewMessagesStateEmpty value)? empty, + TResult? Function(NewMessagesStateLoaded value)? loaded, + TResult? Function(NewMessagesStateError value)? error, + }) { + return error?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap( + TResult Function(_NewMessagesState value)? $default, { + TResult Function(NewMessagesStateEmpty value)? empty, + TResult Function(NewMessagesStateLoaded value)? loaded, + TResult Function(NewMessagesStateError value)? error, + required TResult orElse(), + }) { + if (error != null) { + return error(this); + } + return orElse(); + } +} + +abstract class NewMessagesStateError implements NewMessagesState { + const factory NewMessagesStateError({required final String errorMessage}) = + _$NewMessagesStateErrorImpl; + + String get errorMessage; + @JsonKey(ignore: true) + _$$NewMessagesStateErrorImplCopyWith<_$NewMessagesStateErrorImpl> + get copyWith => throw _privateConstructorUsedError; +} diff --git a/lib/features/bottom_navigation_bar/presentation/bloc/notifications_cubit/notifications_cubit.dart b/lib/features/bottom_navigation_bar/presentation/bloc/notifications_cubit/notifications_cubit.dart new file mode 100644 index 00000000..ba5634cc --- /dev/null +++ b/lib/features/bottom_navigation_bar/presentation/bloc/notifications_cubit/notifications_cubit.dart @@ -0,0 +1,13 @@ +import 'package:eatvio_app/core/app_export.dart'; +import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/notifications_cubit/notifications_state.dart'; + +class NotificationsCubit extends Cubit { + int _notificationsCount = 0; + + NotificationsCubit() : super(const NotificationsState.empty()); + + incrementCounter() { + _notificationsCount++; + emit(NotificationsState.loaded(notificationsCount: _notificationsCount)); + } +} diff --git a/lib/features/bottom_navigation_bar/presentation/bloc/notifications_cubit/notifications_state.dart b/lib/features/bottom_navigation_bar/presentation/bloc/notifications_cubit/notifications_state.dart new file mode 100644 index 00000000..0f20a051 --- /dev/null +++ b/lib/features/bottom_navigation_bar/presentation/bloc/notifications_cubit/notifications_state.dart @@ -0,0 +1,18 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'notifications_state.freezed.dart'; + +@freezed +abstract class NotificationsState with _$NotificationsState { + const factory NotificationsState() = _NotificationsState; + + const factory NotificationsState.empty() = NotificationsStateEmpty; + + const factory NotificationsState.loaded({ + required int notificationsCount, + }) = NotificationsStateLoaded; + + const factory NotificationsState.error({ + required String errorMessage, + }) = NotificationsStateError; +} diff --git a/lib/features/bottom_navigation_bar/presentation/bloc/notifications_cubit/notifications_state.freezed.dart b/lib/features/bottom_navigation_bar/presentation/bloc/notifications_cubit/notifications_state.freezed.dart new file mode 100644 index 00000000..0deeebbd --- /dev/null +++ b/lib/features/bottom_navigation_bar/presentation/bloc/notifications_cubit/notifications_state.freezed.dart @@ -0,0 +1,621 @@ +// 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 'notifications_state.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +/// @nodoc +mixin _$NotificationsState { + @optionalTypeArgs + TResult when( + TResult Function() $default, { + required TResult Function() empty, + required TResult Function(int notificationsCount) loaded, + required TResult Function(String errorMessage) error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull( + TResult? Function()? $default, { + TResult? Function()? empty, + TResult? Function(int notificationsCount)? loaded, + TResult? Function(String errorMessage)? error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen( + TResult Function()? $default, { + TResult Function()? empty, + TResult Function(int notificationsCount)? loaded, + TResult Function(String errorMessage)? error, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map( + TResult Function(_NotificationsState value) $default, { + required TResult Function(NotificationsStateEmpty value) empty, + required TResult Function(NotificationsStateLoaded value) loaded, + required TResult Function(NotificationsStateError value) error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull( + TResult? Function(_NotificationsState value)? $default, { + TResult? Function(NotificationsStateEmpty value)? empty, + TResult? Function(NotificationsStateLoaded value)? loaded, + TResult? Function(NotificationsStateError value)? error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap( + TResult Function(_NotificationsState value)? $default, { + TResult Function(NotificationsStateEmpty value)? empty, + TResult Function(NotificationsStateLoaded value)? loaded, + TResult Function(NotificationsStateError value)? error, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $NotificationsStateCopyWith<$Res> { + factory $NotificationsStateCopyWith( + NotificationsState value, $Res Function(NotificationsState) then) = + _$NotificationsStateCopyWithImpl<$Res, NotificationsState>; +} + +/// @nodoc +class _$NotificationsStateCopyWithImpl<$Res, $Val extends NotificationsState> + implements $NotificationsStateCopyWith<$Res> { + _$NotificationsStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; +} + +/// @nodoc +abstract class _$$NotificationsStateImplCopyWith<$Res> { + factory _$$NotificationsStateImplCopyWith(_$NotificationsStateImpl value, + $Res Function(_$NotificationsStateImpl) then) = + __$$NotificationsStateImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$NotificationsStateImplCopyWithImpl<$Res> + extends _$NotificationsStateCopyWithImpl<$Res, _$NotificationsStateImpl> + implements _$$NotificationsStateImplCopyWith<$Res> { + __$$NotificationsStateImplCopyWithImpl(_$NotificationsStateImpl _value, + $Res Function(_$NotificationsStateImpl) _then) + : super(_value, _then); +} + +/// @nodoc + +class _$NotificationsStateImpl implements _NotificationsState { + const _$NotificationsStateImpl(); + + @override + String toString() { + return 'NotificationsState()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$NotificationsStateImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when( + TResult Function() $default, { + required TResult Function() empty, + required TResult Function(int notificationsCount) loaded, + required TResult Function(String errorMessage) error, + }) { + return $default(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull( + TResult? Function()? $default, { + TResult? Function()? empty, + TResult? Function(int notificationsCount)? loaded, + TResult? Function(String errorMessage)? error, + }) { + return $default?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen( + TResult Function()? $default, { + TResult Function()? empty, + TResult Function(int notificationsCount)? loaded, + TResult Function(String errorMessage)? error, + required TResult orElse(), + }) { + if ($default != null) { + return $default(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map( + TResult Function(_NotificationsState value) $default, { + required TResult Function(NotificationsStateEmpty value) empty, + required TResult Function(NotificationsStateLoaded value) loaded, + required TResult Function(NotificationsStateError value) error, + }) { + return $default(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull( + TResult? Function(_NotificationsState value)? $default, { + TResult? Function(NotificationsStateEmpty value)? empty, + TResult? Function(NotificationsStateLoaded value)? loaded, + TResult? Function(NotificationsStateError value)? error, + }) { + return $default?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap( + TResult Function(_NotificationsState value)? $default, { + TResult Function(NotificationsStateEmpty value)? empty, + TResult Function(NotificationsStateLoaded value)? loaded, + TResult Function(NotificationsStateError value)? error, + required TResult orElse(), + }) { + if ($default != null) { + return $default(this); + } + return orElse(); + } +} + +abstract class _NotificationsState implements NotificationsState { + const factory _NotificationsState() = _$NotificationsStateImpl; +} + +/// @nodoc +abstract class _$$NotificationsStateEmptyImplCopyWith<$Res> { + factory _$$NotificationsStateEmptyImplCopyWith( + _$NotificationsStateEmptyImpl value, + $Res Function(_$NotificationsStateEmptyImpl) then) = + __$$NotificationsStateEmptyImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$NotificationsStateEmptyImplCopyWithImpl<$Res> + extends _$NotificationsStateCopyWithImpl<$Res, + _$NotificationsStateEmptyImpl> + implements _$$NotificationsStateEmptyImplCopyWith<$Res> { + __$$NotificationsStateEmptyImplCopyWithImpl( + _$NotificationsStateEmptyImpl _value, + $Res Function(_$NotificationsStateEmptyImpl) _then) + : super(_value, _then); +} + +/// @nodoc + +class _$NotificationsStateEmptyImpl implements NotificationsStateEmpty { + const _$NotificationsStateEmptyImpl(); + + @override + String toString() { + return 'NotificationsState.empty()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$NotificationsStateEmptyImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when( + TResult Function() $default, { + required TResult Function() empty, + required TResult Function(int notificationsCount) loaded, + required TResult Function(String errorMessage) error, + }) { + return empty(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull( + TResult? Function()? $default, { + TResult? Function()? empty, + TResult? Function(int notificationsCount)? loaded, + TResult? Function(String errorMessage)? error, + }) { + return empty?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen( + TResult Function()? $default, { + TResult Function()? empty, + TResult Function(int notificationsCount)? loaded, + TResult Function(String errorMessage)? error, + required TResult orElse(), + }) { + if (empty != null) { + return empty(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map( + TResult Function(_NotificationsState value) $default, { + required TResult Function(NotificationsStateEmpty value) empty, + required TResult Function(NotificationsStateLoaded value) loaded, + required TResult Function(NotificationsStateError value) error, + }) { + return empty(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull( + TResult? Function(_NotificationsState value)? $default, { + TResult? Function(NotificationsStateEmpty value)? empty, + TResult? Function(NotificationsStateLoaded value)? loaded, + TResult? Function(NotificationsStateError value)? error, + }) { + return empty?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap( + TResult Function(_NotificationsState value)? $default, { + TResult Function(NotificationsStateEmpty value)? empty, + TResult Function(NotificationsStateLoaded value)? loaded, + TResult Function(NotificationsStateError value)? error, + required TResult orElse(), + }) { + if (empty != null) { + return empty(this); + } + return orElse(); + } +} + +abstract class NotificationsStateEmpty implements NotificationsState { + const factory NotificationsStateEmpty() = _$NotificationsStateEmptyImpl; +} + +/// @nodoc +abstract class _$$NotificationsStateLoadedImplCopyWith<$Res> { + factory _$$NotificationsStateLoadedImplCopyWith( + _$NotificationsStateLoadedImpl value, + $Res Function(_$NotificationsStateLoadedImpl) then) = + __$$NotificationsStateLoadedImplCopyWithImpl<$Res>; + @useResult + $Res call({int notificationsCount}); +} + +/// @nodoc +class __$$NotificationsStateLoadedImplCopyWithImpl<$Res> + extends _$NotificationsStateCopyWithImpl<$Res, + _$NotificationsStateLoadedImpl> + implements _$$NotificationsStateLoadedImplCopyWith<$Res> { + __$$NotificationsStateLoadedImplCopyWithImpl( + _$NotificationsStateLoadedImpl _value, + $Res Function(_$NotificationsStateLoadedImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? notificationsCount = null, + }) { + return _then(_$NotificationsStateLoadedImpl( + notificationsCount: null == notificationsCount + ? _value.notificationsCount + : notificationsCount // ignore: cast_nullable_to_non_nullable + as int, + )); + } +} + +/// @nodoc + +class _$NotificationsStateLoadedImpl implements NotificationsStateLoaded { + const _$NotificationsStateLoadedImpl({required this.notificationsCount}); + + @override + final int notificationsCount; + + @override + String toString() { + return 'NotificationsState.loaded(notificationsCount: $notificationsCount)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$NotificationsStateLoadedImpl && + (identical(other.notificationsCount, notificationsCount) || + other.notificationsCount == notificationsCount)); + } + + @override + int get hashCode => Object.hash(runtimeType, notificationsCount); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$NotificationsStateLoadedImplCopyWith<_$NotificationsStateLoadedImpl> + get copyWith => __$$NotificationsStateLoadedImplCopyWithImpl< + _$NotificationsStateLoadedImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when( + TResult Function() $default, { + required TResult Function() empty, + required TResult Function(int notificationsCount) loaded, + required TResult Function(String errorMessage) error, + }) { + return loaded(notificationsCount); + } + + @override + @optionalTypeArgs + TResult? whenOrNull( + TResult? Function()? $default, { + TResult? Function()? empty, + TResult? Function(int notificationsCount)? loaded, + TResult? Function(String errorMessage)? error, + }) { + return loaded?.call(notificationsCount); + } + + @override + @optionalTypeArgs + TResult maybeWhen( + TResult Function()? $default, { + TResult Function()? empty, + TResult Function(int notificationsCount)? loaded, + TResult Function(String errorMessage)? error, + required TResult orElse(), + }) { + if (loaded != null) { + return loaded(notificationsCount); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map( + TResult Function(_NotificationsState value) $default, { + required TResult Function(NotificationsStateEmpty value) empty, + required TResult Function(NotificationsStateLoaded value) loaded, + required TResult Function(NotificationsStateError value) error, + }) { + return loaded(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull( + TResult? Function(_NotificationsState value)? $default, { + TResult? Function(NotificationsStateEmpty value)? empty, + TResult? Function(NotificationsStateLoaded value)? loaded, + TResult? Function(NotificationsStateError value)? error, + }) { + return loaded?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap( + TResult Function(_NotificationsState value)? $default, { + TResult Function(NotificationsStateEmpty value)? empty, + TResult Function(NotificationsStateLoaded value)? loaded, + TResult Function(NotificationsStateError value)? error, + required TResult orElse(), + }) { + if (loaded != null) { + return loaded(this); + } + return orElse(); + } +} + +abstract class NotificationsStateLoaded implements NotificationsState { + const factory NotificationsStateLoaded( + {required final int notificationsCount}) = _$NotificationsStateLoadedImpl; + + int get notificationsCount; + @JsonKey(ignore: true) + _$$NotificationsStateLoadedImplCopyWith<_$NotificationsStateLoadedImpl> + get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$NotificationsStateErrorImplCopyWith<$Res> { + factory _$$NotificationsStateErrorImplCopyWith( + _$NotificationsStateErrorImpl value, + $Res Function(_$NotificationsStateErrorImpl) then) = + __$$NotificationsStateErrorImplCopyWithImpl<$Res>; + @useResult + $Res call({String errorMessage}); +} + +/// @nodoc +class __$$NotificationsStateErrorImplCopyWithImpl<$Res> + extends _$NotificationsStateCopyWithImpl<$Res, + _$NotificationsStateErrorImpl> + implements _$$NotificationsStateErrorImplCopyWith<$Res> { + __$$NotificationsStateErrorImplCopyWithImpl( + _$NotificationsStateErrorImpl _value, + $Res Function(_$NotificationsStateErrorImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? errorMessage = null, + }) { + return _then(_$NotificationsStateErrorImpl( + errorMessage: null == errorMessage + ? _value.errorMessage + : errorMessage // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc + +class _$NotificationsStateErrorImpl implements NotificationsStateError { + const _$NotificationsStateErrorImpl({required this.errorMessage}); + + @override + final String errorMessage; + + @override + String toString() { + return 'NotificationsState.error(errorMessage: $errorMessage)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$NotificationsStateErrorImpl && + (identical(other.errorMessage, errorMessage) || + other.errorMessage == errorMessage)); + } + + @override + int get hashCode => Object.hash(runtimeType, errorMessage); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$NotificationsStateErrorImplCopyWith<_$NotificationsStateErrorImpl> + get copyWith => __$$NotificationsStateErrorImplCopyWithImpl< + _$NotificationsStateErrorImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when( + TResult Function() $default, { + required TResult Function() empty, + required TResult Function(int notificationsCount) loaded, + required TResult Function(String errorMessage) error, + }) { + return error(errorMessage); + } + + @override + @optionalTypeArgs + TResult? whenOrNull( + TResult? Function()? $default, { + TResult? Function()? empty, + TResult? Function(int notificationsCount)? loaded, + TResult? Function(String errorMessage)? error, + }) { + return error?.call(errorMessage); + } + + @override + @optionalTypeArgs + TResult maybeWhen( + TResult Function()? $default, { + TResult Function()? empty, + TResult Function(int notificationsCount)? loaded, + TResult Function(String errorMessage)? error, + required TResult orElse(), + }) { + if (error != null) { + return error(errorMessage); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map( + TResult Function(_NotificationsState value) $default, { + required TResult Function(NotificationsStateEmpty value) empty, + required TResult Function(NotificationsStateLoaded value) loaded, + required TResult Function(NotificationsStateError value) error, + }) { + return error(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull( + TResult? Function(_NotificationsState value)? $default, { + TResult? Function(NotificationsStateEmpty value)? empty, + TResult? Function(NotificationsStateLoaded value)? loaded, + TResult? Function(NotificationsStateError value)? error, + }) { + return error?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap( + TResult Function(_NotificationsState value)? $default, { + TResult Function(NotificationsStateEmpty value)? empty, + TResult Function(NotificationsStateLoaded value)? loaded, + TResult Function(NotificationsStateError value)? error, + required TResult orElse(), + }) { + if (error != null) { + return error(this); + } + return orElse(); + } +} + +abstract class NotificationsStateError implements NotificationsState { + const factory NotificationsStateError({required final String errorMessage}) = + _$NotificationsStateErrorImpl; + + String get errorMessage; + @JsonKey(ignore: true) + _$$NotificationsStateErrorImplCopyWith<_$NotificationsStateErrorImpl> + get copyWith => throw _privateConstructorUsedError; +} diff --git a/lib/widgets/bottom_navigation_bar_widget.dart b/lib/features/bottom_navigation_bar/presentation/widgets/bottom_navigation_bar_widget.dart similarity index 60% rename from lib/widgets/bottom_navigation_bar_widget.dart rename to lib/features/bottom_navigation_bar/presentation/widgets/bottom_navigation_bar_widget.dart index 51fd3e9d..9865221a 100644 --- a/lib/widgets/bottom_navigation_bar_widget.dart +++ b/lib/features/bottom_navigation_bar/presentation/widgets/bottom_navigation_bar_widget.dart @@ -1,8 +1,13 @@ import 'package:eatvio_app/core/app_export.dart'; +import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/new_messages_cubit/new_messages_cubit.dart'; +import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/new_messages_cubit/new_messages_state.dart'; +import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/notifications_cubit/notifications_cubit.dart'; +import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/notifications_cubit/notifications_state.dart'; import 'package:flutter/material.dart'; import 'package:eatvio_app/widgets/custom_navigation_menu.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:go_router/go_router.dart'; +import 'package:badges/badges.dart' as badges; int currentBottomNavBarIndex = 0; @@ -113,36 +118,56 @@ class _BottomNavigationBarWidgetState extends State { ), BottomNavigationBarItem( label: "chat".tr, - icon: SvgPicture.asset( - ImageConstant.imgChat, - width: 24, - height: 24, - ), - activeIcon: SvgPicture.asset( - ImageConstant.imgChat, - width: 24, - height: 24, - colorFilter: ColorFilter.mode( - appTheme.blueA400, - BlendMode.srcIn, - ), + icon: BlocBuilder( + builder: (BuildContext context, NewMessagesState state) { + return badges.Badge( + position: badges.BadgePosition.topEnd(), + badgeContent: Text( + (state is NewMessagesStateLoaded) ? state.messagesCount.toString() : '0', + style: TextStyle( + color: Colors.white, + ), + ), + child: SvgPicture.asset( + ImageConstant.imgChat, + width: 24, + height: 24, + colorFilter: currentBottomNavBarIndex == 3 + ? ColorFilter.mode( + appTheme.blueA400, + BlendMode.srcIn, + ) + : null, + ), + ); + }, ), ), BottomNavigationBarItem( label: '', - icon: SvgPicture.asset( - ImageConstant.imgBell, - width: 24, - height: 24, - ), - activeIcon: SvgPicture.asset( - ImageConstant.imgBell, - width: 24, - height: 24, - colorFilter: ColorFilter.mode( - appTheme.blueA400, - BlendMode.srcIn, - ), + icon: BlocBuilder( + builder: (BuildContext context, NotificationsState state) { + return badges.Badge( + position: badges.BadgePosition.topEnd(), + badgeContent: Text( + (state is NotificationsStateLoaded) ? state.notificationsCount.toString() : '0', + style: TextStyle( + color: Colors.white, + ), + ), + child: SvgPicture.asset( + ImageConstant.imgBell, + colorFilter: currentBottomNavBarIndex == 4 + ? ColorFilter.mode( + appTheme.blueA400, + BlendMode.srcIn, + ) + : null, + width: 24, + height: 24, + ), + ); + }, ), ), BottomNavigationBarItem( diff --git a/lib/features/chat/presentation/pages/chat_screen.dart b/lib/features/chat/presentation/pages/chat_screen.dart index 258b7317..d3ecef8b 100644 --- a/lib/features/chat/presentation/pages/chat_screen.dart +++ b/lib/features/chat/presentation/pages/chat_screen.dart @@ -155,7 +155,9 @@ class _ChatScreenState extends State { children: [ SizedBox(width: 8), InkWell( - onTap: () {}, + onTap: () { + + }, borderRadius: BorderRadiusStyle.circleBorder17, child: Ink( height: 32, diff --git a/lib/locator_service.dart b/lib/locator_service.dart index 5a924d20..dfd61987 100644 --- a/lib/locator_service.dart +++ b/lib/locator_service.dart @@ -3,6 +3,8 @@ import 'package:eatvio_app/core/initialization_cubit/initialization_cubit.dart'; import 'package:eatvio_app/features/blog_post/presentation/bloc/blog_post_cubit.dart'; import 'package:eatvio_app/features/blog_post/repositories/blog_post_repository.dart'; import 'package:eatvio_app/features/blog_post/services/blog_post_service.dart'; +import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/new_messages_cubit/new_messages_cubit.dart'; +import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/notifications_cubit/notifications_cubit.dart'; import 'package:eatvio_app/features/chat/presentation/bloc/chat_room_cubit/chat_room_cubit.dart'; import 'package:eatvio_app/features/chat/repositories/chat_repository.dart'; import 'package:eatvio_app/features/chat/services/chat_service.dart'; @@ -17,6 +19,8 @@ final sl = GetIt.instance; Future init() async { // BLoC / Cubit sl.registerLazySingleton(() => InitializationCubit(chatRepository: sl())); + sl.registerLazySingleton(() => NewMessagesCubit()); + sl.registerLazySingleton(() => NotificationsCubit()); sl.registerFactory(() => ChatRoomCubit(chatRepository: sl(), initializationCubit: sl())); sl.registerFactory(() => FeedCubit(feedRepository: sl())); diff --git a/lib/main.dart b/lib/main.dart index 3f285adc..e73bd429 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,6 +1,8 @@ import 'package:eatvio_app/app_router.dart'; import 'package:eatvio_app/core/initialization_cubit/initialization_cubit.dart'; import 'package:eatvio_app/core/initialization_cubit/initialization_state.dart'; +import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/new_messages_cubit/new_messages_cubit.dart'; +import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/notifications_cubit/notifications_cubit.dart'; import 'package:eatvio_app/features/chat/presentation/pages/chat_select_screen/bloc/chat_select_bloc.dart'; import 'package:eatvio_app/features/chat/presentation/pages/chat_select_screen/models/chat_select_model.dart'; import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart'; @@ -43,6 +45,9 @@ class MyApp extends StatelessWidget { return MultiBlocProvider( providers: [ BlocProvider(create: (context) => sl()), + BlocProvider(create: (context) => sl()), + BlocProvider(create: (context) => sl()), + BlocProvider(create: (context) => ThemeBloc(ThemeState(themeType: PrefUtils().getThemeData()))), BlocProvider(create: (context) => sl()..loadFeedBlogPostList()), BlocProvider(create: (context) => MyProfileBloc(MyProfileState(myProfileModelObj: MyProfileModel()))..add(MyProfileInitialEvent())),