forked from eatvio/eatvio_app
feat(video_calls): incommig call handling fix for ios and anroid
This commit is contained in:
@@ -5,9 +5,14 @@ import 'dart:developer';
|
||||
import 'package:eatvio_app/core/enums/base_state.dart';
|
||||
import 'package:eatvio_app/core/websockets/ws_manager.dart';
|
||||
import 'package:eatvio_app/features/chat/models/chat_message_model.dart';
|
||||
import 'package:eatvio_app/features/chat/models/chat_room_pagination_model.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/bloc/chats_cubit/chats_state.dart';
|
||||
import 'package:eatvio_app/features/chat/repositories/chat_repository.dart';
|
||||
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
|
||||
import 'package:eatvio_app/features/video_calls/services/video_call_chat_signal.dart';
|
||||
import 'package:eatvio_app/features/video_calls/services/video_call_ended_push_handler.dart';
|
||||
import 'package:eatvio_app/features/video_calls/services/video_call_incoming_push_presenter.dart';
|
||||
import 'package:eatvio_app/features/video_calls/services/video_call_push_payload.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:eatvio_app/core/error_reporting/report_from.dart';
|
||||
|
||||
@@ -18,13 +23,22 @@ class ChatsCubit extends Cubit<ChatsState> {
|
||||
StreamSubscription? newIncomingMessagesSubscription;
|
||||
StreamSubscription? newLastMessageSubscription;
|
||||
StreamSubscription<void>? reconnectedSubscription;
|
||||
final Set<int> _presentedVideoCallMessageIds = {};
|
||||
|
||||
/// Test seam. Production uses CallKit.
|
||||
Future<void> Function(VideoCallPushPayload payload)? presentIncomingVideoCall;
|
||||
Future<void> Function()? dismissIncomingVideoCall;
|
||||
|
||||
ChatsCubit({
|
||||
required this.chatRepository,
|
||||
required this.wsManager,
|
||||
required this.profileRepository,
|
||||
this.presentIncomingVideoCall,
|
||||
this.dismissIncomingVideoCall,
|
||||
}) : super(ChatsState()) {
|
||||
newLastMessageSubscription = chatRepository.newUserMessagesStream.listen((message) {
|
||||
newLastMessageSubscription = chatRepository.newUserMessagesStream.listen((
|
||||
message,
|
||||
) {
|
||||
_setNewLastMessage(message);
|
||||
});
|
||||
}
|
||||
@@ -35,7 +49,9 @@ class ChatsCubit extends Cubit<ChatsState> {
|
||||
Future<void> connectToWebSocket() async {
|
||||
try {
|
||||
await newIncomingMessagesSubscription?.cancel();
|
||||
newIncomingMessagesSubscription = wsManager.newMessagesStream?.listen((event) {
|
||||
newIncomingMessagesSubscription = wsManager.newMessagesStream?.listen((
|
||||
event,
|
||||
) {
|
||||
_handleNewMessageViaSocket(event.data);
|
||||
});
|
||||
|
||||
@@ -53,7 +69,9 @@ class ChatsCubit extends Cubit<ChatsState> {
|
||||
_query = query;
|
||||
emit(state.copyWith(state: BaseState.loading));
|
||||
try {
|
||||
final chatRoomsPaginationModel = await chatRepository.getChatRooms(query: query);
|
||||
final chatRoomsPaginationModel = await chatRepository.getChatRooms(
|
||||
query: query,
|
||||
);
|
||||
final unreadChats = chatRoomsPaginationModel.unreadChatsCount;
|
||||
lastPage = chatRoomsPaginationModel.meta.lastPage + 1;
|
||||
emit(
|
||||
@@ -66,9 +84,7 @@ class ChatsCubit extends Cubit<ChatsState> {
|
||||
page++;
|
||||
} catch (e, st) {
|
||||
await reportFrom(e, st, this);
|
||||
emit(
|
||||
state.copyWith(errorMessage: e.toString(), state: BaseState.error),
|
||||
);
|
||||
emit(state.copyWith(errorMessage: e.toString(), state: BaseState.error));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +92,9 @@ class ChatsCubit extends Cubit<ChatsState> {
|
||||
page = 1;
|
||||
_query = query;
|
||||
try {
|
||||
final chatRoomsPaginationModel = await chatRepository.getChatRooms(query: query);
|
||||
final chatRoomsPaginationModel = await chatRepository.getChatRooms(
|
||||
query: query,
|
||||
);
|
||||
final unreadChats = chatRoomsPaginationModel.unreadChatsCount;
|
||||
lastPage = chatRoomsPaginationModel.meta.lastPage + 1;
|
||||
emit(
|
||||
@@ -90,9 +108,7 @@ class ChatsCubit extends Cubit<ChatsState> {
|
||||
} catch (e, st) {
|
||||
await reportFrom(e, st, this);
|
||||
log(e.toString(), stackTrace: st);
|
||||
emit(
|
||||
state.copyWith(errorMessage: e.toString(), state: BaseState.error),
|
||||
);
|
||||
emit(state.copyWith(errorMessage: e.toString(), state: BaseState.error));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,7 +116,10 @@ class ChatsCubit extends Cubit<ChatsState> {
|
||||
try {
|
||||
if (state.isLoadingMoreChats || lastPage == page) return;
|
||||
emit(state.copyWith(isLoadingMoreChats: true));
|
||||
final chatRoomsPaginationModel = await chatRepository.getChatRooms(query: _query, page: page);
|
||||
final chatRoomsPaginationModel = await chatRepository.getChatRooms(
|
||||
query: _query,
|
||||
page: page,
|
||||
);
|
||||
final unreadChats = chatRoomsPaginationModel.unreadChatsCount;
|
||||
emit(
|
||||
state.copyWith(
|
||||
@@ -140,7 +159,9 @@ class ChatsCubit extends Cubit<ChatsState> {
|
||||
emit(
|
||||
state.copyWith(
|
||||
chatRooms: updatedChatRooms,
|
||||
chatsWithUnreadMessagesCount: isChatRead ? state.chatsWithUnreadMessagesCount - 1 : state.chatsWithUnreadMessagesCount,
|
||||
chatsWithUnreadMessagesCount: isChatRead
|
||||
? state.chatsWithUnreadMessagesCount - 1
|
||||
: state.chatsWithUnreadMessagesCount,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -162,7 +183,8 @@ class ChatsCubit extends Cubit<ChatsState> {
|
||||
wasChanged = true;
|
||||
// Идемпотентность: повторная доставка того же message.id (реконнект,
|
||||
// доставка по нескольким каналам) не начисляет непрочитанное дважды.
|
||||
final isDuplicate = message.id != 0 && chatRoom.lastMessage?.id == message.id;
|
||||
final isDuplicate =
|
||||
message.id != 0 && chatRoom.lastMessage?.id == message.id;
|
||||
// Собственное сообщение (второе устройство автора) обновляет
|
||||
// lastMessage, но не создаёт непрочитанное (S3).
|
||||
if (!isOwn && !isDuplicate && chatRoom.unreadMessagesCount == 0) {
|
||||
@@ -170,7 +192,9 @@ class ChatsCubit extends Cubit<ChatsState> {
|
||||
}
|
||||
return chatRoom.copyWith(
|
||||
lastMessage: message,
|
||||
unreadMessagesCount: isOwn || isDuplicate ? chatRoom.unreadMessagesCount : chatRoom.unreadMessagesCount + 1,
|
||||
unreadMessagesCount: isOwn || isDuplicate
|
||||
? chatRoom.unreadMessagesCount
|
||||
: chatRoom.unreadMessagesCount + 1,
|
||||
);
|
||||
}
|
||||
return chatRoom;
|
||||
@@ -182,15 +206,83 @@ class ChatsCubit extends Cubit<ChatsState> {
|
||||
emit(
|
||||
state.copyWith(
|
||||
chatRooms: updatedChatRooms,
|
||||
chatsWithUnreadMessagesCount: willIncrementUnreadChats ? state.chatsWithUnreadMessagesCount + 1 : state.chatsWithUnreadMessagesCount,
|
||||
chatsWithUnreadMessagesCount: willIncrementUnreadChats
|
||||
? state.chatsWithUnreadMessagesCount + 1
|
||||
: state.chatsWithUnreadMessagesCount,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
_presentVideoCallFromMessage(
|
||||
message,
|
||||
isOwn: isOwn,
|
||||
chatRooms: currentChatRooms,
|
||||
);
|
||||
} catch (e, st) {
|
||||
await reportFrom(e, st, this);
|
||||
}
|
||||
}
|
||||
|
||||
void _presentVideoCallFromMessage(
|
||||
ChatMessageModel message, {
|
||||
required bool isOwn,
|
||||
required List<ChatRoom> chatRooms,
|
||||
}) {
|
||||
final kind = VideoCallChatSignal.kindOf(message);
|
||||
if (kind == VideoCallChatSignalKind.ended) {
|
||||
unawaited(_dismissIncomingVideoCallUi());
|
||||
return;
|
||||
}
|
||||
if (isOwn || kind != VideoCallChatSignalKind.started) return;
|
||||
if (message.id != 0 && !_presentedVideoCallMessageIds.add(message.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final caller = _callerFromRooms(message, chatRooms);
|
||||
final payload = VideoCallChatSignal.incomingPayload(
|
||||
message: message,
|
||||
callerName: caller?.name,
|
||||
callerAvatarUrl: caller?.profilePhotoUrl,
|
||||
);
|
||||
if (payload == null) return;
|
||||
unawaited(_showIncomingVideoCall(payload));
|
||||
}
|
||||
|
||||
Future<void> _showIncomingVideoCall(VideoCallPushPayload payload) async {
|
||||
try {
|
||||
await (presentIncomingVideoCall ??
|
||||
VideoCallIncomingPushPresenter.showIncomingCall)(payload);
|
||||
} catch (e, st) {
|
||||
log('ChatsCubit: failed to show incoming video call: $e', stackTrace: st);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _dismissIncomingVideoCallUi() async {
|
||||
try {
|
||||
await (dismissIncomingVideoCall ??
|
||||
VideoCallEndedPushHandler.dismissIncomingCallUi)();
|
||||
} catch (e, st) {
|
||||
log(
|
||||
'ChatsCubit: failed to dismiss incoming video call: $e',
|
||||
stackTrace: st,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ChatUser? _callerFromRooms(
|
||||
ChatMessageModel message,
|
||||
List<ChatRoom> chatRooms,
|
||||
) {
|
||||
for (final room in chatRooms) {
|
||||
if (room.id != message.roomId) continue;
|
||||
final from = room.users.fromChatUser;
|
||||
final to = room.users.toChatUser;
|
||||
if (from.id == message.authorId) return from;
|
||||
if (to.id == message.authorId) return to;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void _setNewLastMessage(ChatMessageModel message) {
|
||||
final chats = state.chatRooms.map((chat) {
|
||||
if (chat.id == message.roomId) {
|
||||
|
||||
Reference in New Issue
Block a user