forked from eatvio/eatvio_app
feat(video_calls): fixed online indicator and in app notifications for video call
This commit is contained in:
@@ -16,6 +16,7 @@ import 'package:eatvio_app/features/chat/models/plan_complain_model.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/bloc/chat_room_cubit/chat_room_state.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/bloc/chats_cubit/chats_cubit.dart';
|
||||
import 'package:eatvio_app/features/chat/repositories/chat_repository.dart';
|
||||
import 'package:eatvio_app/features/chat/utils/chat_counterpart_utils.dart';
|
||||
import 'package:eatvio_app/features/my_finance/models/card_model.dart';
|
||||
import 'package:eatvio_app/features/my_finance/repositories/cards_repository.dart';
|
||||
import 'package:eatvio_app/features/plans/models/chosen_plan_model.dart';
|
||||
@@ -34,6 +35,8 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
|
||||
late int lastPage;
|
||||
bool isLoading = false;
|
||||
Timer? _timer;
|
||||
String? _activeChatUuid;
|
||||
void Function(bool isOnline)? _presenceOnlineListener;
|
||||
var uuid = const Uuid();
|
||||
|
||||
ChatRoomCubit({required this.chatRepository, required this.wsManager, required this.cardsRepository}) : super(const ChatRoomState.loading());
|
||||
@@ -170,8 +173,10 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
|
||||
Future<void> _subscribeToChatChannel({required String uuid, required String chatId}) async {
|
||||
final client = wsManager.client;
|
||||
if (client == null) return;
|
||||
_activeChatUuid = uuid;
|
||||
await wsManager.initPrivateDirectChatChannel(chatId);
|
||||
wsManager.subscribeDirectChatChannel();
|
||||
await _subscribeToPresenceChannel(chatId: chatId);
|
||||
messagesSubscription = wsManager.directChatStream?.listen((event) {
|
||||
AudioPlayer().play((AssetSource('audio/recive_message.wav')));
|
||||
final message = ChatMessageModel.fromJson(jsonDecode(event.data));
|
||||
@@ -197,6 +202,118 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _subscribeToPresenceChannel({required String chatId}) async {
|
||||
final currentState = state;
|
||||
if (currentState is! ChatRoomStateLoaded) return;
|
||||
|
||||
final counterpart = resolveChatCounterpart(
|
||||
users: currentState.chatRoom.users,
|
||||
currentUserId: wsManager.userId,
|
||||
);
|
||||
|
||||
await wsManager.initPresenceDirectChatChannel(chatId);
|
||||
_presenceOnlineListener = (isOnline) {
|
||||
unawaited(_handleCounterpartOnlineChanged(
|
||||
counterpartField: counterpart.id == currentState.chatRoom.users.receiver.id
|
||||
? _ChatCounterpartField.receiver
|
||||
: _ChatCounterpartField.sender,
|
||||
isOnline: isOnline,
|
||||
));
|
||||
};
|
||||
wsManager.addPresenceDirectChatListener(
|
||||
counterpartUserId: counterpart.id,
|
||||
onCounterpartOnlineChanged: _presenceOnlineListener!,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _handleCounterpartOnlineChanged({
|
||||
required _ChatCounterpartField counterpartField,
|
||||
required bool isOnline,
|
||||
}) async {
|
||||
final currentState = state;
|
||||
if (currentState is! ChatRoomStateLoaded) return;
|
||||
|
||||
final counterpart = counterpartField == _ChatCounterpartField.receiver
|
||||
? currentState.chatRoom.users.receiver
|
||||
: currentState.chatRoom.users.sender;
|
||||
|
||||
if (counterpart.isOnline == isOnline && (isOnline || counterpart.lastSeenText.isNotEmpty)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isOnline) {
|
||||
await _refreshCounterpartOnlineStatus(counterpartField: counterpartField);
|
||||
return;
|
||||
}
|
||||
|
||||
_emitCounterpartOnlineStatus(
|
||||
counterpartField: counterpartField,
|
||||
isOnline: true,
|
||||
lastSeenText: '',
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _refreshCounterpartOnlineStatus({
|
||||
required _ChatCounterpartField counterpartField,
|
||||
}) async {
|
||||
final chatUuid = _activeChatUuid;
|
||||
if (chatUuid == null) return;
|
||||
|
||||
try {
|
||||
final chatRoom = await chatRepository.getChatRoom(chatUuid);
|
||||
final currentState = state;
|
||||
if (currentState is! ChatRoomStateLoaded) return;
|
||||
|
||||
final counterpart = resolveChatCounterpart(
|
||||
users: chatRoom.users,
|
||||
currentUserId: wsManager.userId,
|
||||
);
|
||||
final field = counterpart.id == currentState.chatRoom.users.receiver.id
|
||||
? _ChatCounterpartField.receiver
|
||||
: _ChatCounterpartField.sender;
|
||||
|
||||
if (field != counterpartField) return;
|
||||
|
||||
_emitCounterpartOnlineStatus(
|
||||
counterpartField: counterpartField,
|
||||
isOnline: counterpart.isOnline,
|
||||
lastSeenText: counterpart.lastSeenText,
|
||||
);
|
||||
} catch (e, st) {
|
||||
await reportFrom(e, st, this);
|
||||
}
|
||||
}
|
||||
|
||||
void _emitCounterpartOnlineStatus({
|
||||
required _ChatCounterpartField counterpartField,
|
||||
required bool isOnline,
|
||||
required String lastSeenText,
|
||||
}) {
|
||||
final currentState = state;
|
||||
if (currentState is! ChatRoomStateLoaded) return;
|
||||
|
||||
final users = currentState.chatRoom.users;
|
||||
final updatedUsers = counterpartField == _ChatCounterpartField.receiver
|
||||
? users.copyWith(
|
||||
receiver: users.receiver.copyWith(
|
||||
isOnline: isOnline,
|
||||
lastSeenText: lastSeenText,
|
||||
),
|
||||
)
|
||||
: users.copyWith(
|
||||
sender: users.sender.copyWith(
|
||||
isOnline: isOnline,
|
||||
lastSeenText: lastSeenText,
|
||||
),
|
||||
);
|
||||
|
||||
emit(
|
||||
currentState.copyWith(
|
||||
chatRoom: currentState.chatRoom.copyWith(users: updatedUsers),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<bool> sendMessage({String? data, List<File>? files, required String userSlug}) async {
|
||||
try {
|
||||
final messageUuid = uuid.v4();
|
||||
@@ -337,6 +454,10 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
if (_presenceOnlineListener != null) {
|
||||
wsManager.removePresenceDirectChatListener(_presenceOnlineListener!);
|
||||
_presenceOnlineListener = null;
|
||||
}
|
||||
wsManager.unsubscribeDirectChatChannel();
|
||||
_timer?.cancel();
|
||||
messagesSubscription?.cancel();
|
||||
@@ -435,3 +556,5 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum _ChatCounterpartField { sender, receiver }
|
||||
|
||||
@@ -15,11 +15,14 @@ import 'package:eatvio_app/features/chat/presentation/widgets/about_order_button
|
||||
import 'package:eatvio_app/features/chat/presentation/widgets/chat_bubble_widget.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/widgets/plan_order_message_widget.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/websockets/ws_manager.dart';
|
||||
import 'package:eatvio_app/features/menu/presentation/blocked_user_cubit/blocked_users_cubit.dart';
|
||||
import 'package:eatvio_app/features/menu/presentation/blocked_user_cubit/blocked_users_state.dart';
|
||||
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/widgets/chat_user_online_status_line.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/widgets/notification_banner.dart';
|
||||
import 'package:eatvio_app/features/chat/utils/chat_counterpart_utils.dart';
|
||||
import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart';
|
||||
import 'package:eatvio_app/features/order_info/presentation/info_by_order_screen.dart';
|
||||
import 'package:eatvio_app/features/plans/enums/chosen_plan_state_enum.dart';
|
||||
@@ -141,6 +144,10 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
}
|
||||
|
||||
Scaffold _buildChat(BuildContext context, ChatRoomStateLoaded state) {
|
||||
final chatCounterpart = resolveChatCounterpart(
|
||||
users: state.chatRoom.users,
|
||||
currentUserId: sl<WsManager>().userId,
|
||||
);
|
||||
final chosenPlan = state.chatRoom.chosenPlan;
|
||||
final amIConsultant = chosenPlan?.consultantId == state.chatRoom.users.sender.id;
|
||||
final showUserDetailsButton = [
|
||||
@@ -236,7 +243,12 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
),
|
||||
firstCurve: Curves.easeIn,
|
||||
secondCurve: Curves.easeOut,
|
||||
secondChild: const SizedBox.shrink(),
|
||||
secondChild: ChatUserOnlineStatusLine(
|
||||
isOnline: chatCounterpart.isOnline,
|
||||
lastSeenText: chatCounterpart.lastSeenText,
|
||||
style: CustomTextStyles.bodySmallMulishPrimary,
|
||||
showOnlineDot: false,
|
||||
),
|
||||
crossFadeState: state.isTyping ? CrossFadeState.showFirst : CrossFadeState.showSecond,
|
||||
duration: const Duration(milliseconds: 150),
|
||||
),
|
||||
@@ -255,8 +267,11 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
VideoCallScreen.name,
|
||||
extra: <String, dynamic>{
|
||||
'conversationUuid': widget.uuid,
|
||||
'counterpartName': state.chatRoom.users.receiver.name,
|
||||
'counterpartAvatarUrl': state.chatRoom.users.receiver.profilePhotoUrl,
|
||||
'counterpartName': chatCounterpart.name,
|
||||
'counterpartAvatarUrl': chatCounterpart.profilePhotoUrl,
|
||||
'counterpartUserId': chatCounterpart.id,
|
||||
'counterpartIsOnline': chatCounterpart.isOnline,
|
||||
'counterpartLastSeenText': chatCounterpart.lastSeenText,
|
||||
'localParticipantName': state.chatRoom.users.sender.name,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ChatUserOnlineStatusLine extends StatelessWidget {
|
||||
const ChatUserOnlineStatusLine({
|
||||
super.key,
|
||||
required this.isOnline,
|
||||
required this.lastSeenText,
|
||||
this.style,
|
||||
this.showOnlineDot = true,
|
||||
});
|
||||
|
||||
final bool isOnline;
|
||||
final String lastSeenText;
|
||||
final TextStyle? style;
|
||||
final bool showOnlineDot;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final textStyle = style ??
|
||||
CustomTextStyles.bodySmallMulishBlack900.copyWith(height: 14 / 12);
|
||||
|
||||
if (isOnline) {
|
||||
if (!showOnlineDot) {
|
||||
return Text(
|
||||
S.of(context).video_call_status_online,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: textStyle,
|
||||
);
|
||||
}
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
width: 8,
|
||||
height: 8,
|
||||
decoration: BoxDecoration(
|
||||
color: appTheme.greenA700,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Flexible(
|
||||
child: Text(
|
||||
S.of(context).video_call_status_online,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: textStyle,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
final trimmedLastSeen = lastSeenText.trim();
|
||||
if (trimmedLastSeen.isEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return Text(
|
||||
trimmedLastSeen,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: textStyle,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user