feat(video_calls): ios voip push, mix and crushes fix

This commit is contained in:
misha
2026-10-16 13:49:48 +03:00
parent ff17b813ac
commit 6d2935400a
8 changed files with 715 additions and 89 deletions

View File

@@ -479,6 +479,39 @@ void main() {
},
);
test(
'_handleNewMessageViaSocket - завершение видеозвонка закрывает только этот чат',
() async {
const chatId = 'chat-id';
final dismissed = <String?>[];
chatsCubit.dismissIncomingVideoCall = (uuid) async {
dismissed.add(uuid);
};
chatsCubit.emit(
ChatsState(
state: BaseState.success,
chatRooms: [buildChatRoom(chatId)],
),
);
when(mockWsManager.userId).thenReturn(42);
final controller = await connectToSocketStream();
controller.add(
socketEvent(
id: 502,
authorId: 7,
roomId: chatId,
data: 'Анна завершил(а) видеозвонок.',
),
);
await Future.delayed(const Duration(milliseconds: 100));
expect(dismissed, [chatId]);
await controller.close();
},
);
test('close - закрывает cubit и отписывается от подписок', () async {
final mockSubscription1 = MockStreamSubscription();

View File

@@ -0,0 +1,46 @@
import 'package:eatvio_app/features/video_calls/services/video_call_ended_push_handler.dart';
import 'package:flutter_callkit_incoming/entities/call_kit_params.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
const conversation = '11111111-1111-4111-8111-111111111111';
group('VideoCallEndedPushHandler.callMatchesConversation', () {
test('matches the conversation stored as the call handle', () {
const call = CallKitParams(id: 'derived-id', handle: conversation);
expect(
VideoCallEndedPushHandler.callMatchesConversation(call, conversation),
isTrue,
);
});
test('matches the conversation stored in the call payload', () {
const call = CallKitParams(
id: 'derived-id',
handle: 'Итвио',
extra: <String, dynamic>{'conversationUuid': conversation},
);
expect(
VideoCallEndedPushHandler.callMatchesConversation(
call,
conversation.toUpperCase(),
),
isTrue,
);
});
test('leaves a call from another chat alone', () {
const call = CallKitParams(
id: 'other-id',
handle: '22222222-2222-4222-8222-222222222222',
);
expect(
VideoCallEndedPushHandler.callMatchesConversation(call, conversation),
isFalse,
);
});
});
}

View File

@@ -0,0 +1,150 @@
import 'package:eatvio_app/features/video_calls/services/video_call_incoming_push_presenter.dart';
import 'package:eatvio_app/features/video_calls/services/video_call_kit_id.dart';
import 'package:eatvio_app/features/video_calls/services/video_call_push_payload.dart';
import 'package:flutter_callkit_incoming/entities/entities.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
const payload = VideoCallPushPayload(
conversationUuid: '11111111-1111-4111-8111-111111111111',
counterpartName: ' Anna ',
messageId: 26,
);
final callId = VideoCallKitId.iosUuid(
conversationUuid: payload.conversationUuid,
messageId: payload.messageId,
);
group('VideoCallIncomingPushPresenter', () {
test(
'iOS keeps the push-reported call and does not show it again',
() async {
final shown = <CallKitParams>[];
final delays = <Duration>[];
await VideoCallIncomingPushPresenter.showIncomingCall(
payload,
isIos: true,
isIncomingCallClaimed: (_) async => true,
reserveIncomingCall: (_) async => true,
showCallkitIncoming: (params) async {
shown.add(params);
},
delay: (duration) async {
delays.add(duration);
},
);
expect(shown, isEmpty);
expect(delays, isEmpty);
expect(callId, isNotNull);
},
);
test('iOS shows the system call once the push does not claim it', () async {
final shown = <CallKitParams>[];
var polls = 0;
await VideoCallIncomingPushPresenter.showIncomingCall(
payload,
isIos: true,
claimPollDelays: const <Duration>[
Duration.zero,
Duration(milliseconds: 300),
],
isIncomingCallClaimed: (_) async {
polls += 1;
return false;
},
reserveIncomingCall: (id) async {
expect(id, callId);
return true;
},
showCallkitIncoming: (params) async {
shown.add(params);
},
delay: (_) async {},
);
expect(polls, 2);
expect(shown, hasLength(1));
expect(shown.single.id, callId);
expect(shown.single.nameCaller, 'Anna');
expect(shown.single.ios?.configureAudioSession, isFalse);
expect(shown.single.ios?.audioSessionActive, isFalse);
expect(shown.single.ios?.audioSessionMode, 'videoChat');
expect(shown.single.ios?.ringtonePath, 'system_ringtone_default');
});
test('iOS stops polling when the push claims the call', () async {
final shown = <CallKitParams>[];
var polls = 0;
await VideoCallIncomingPushPresenter.showIncomingCall(
payload,
isIos: true,
claimPollDelays: const <Duration>[
Duration.zero,
Duration(milliseconds: 300),
Duration(milliseconds: 800),
],
isIncomingCallClaimed: (_) async {
polls += 1;
return polls == 2;
},
reserveIncomingCall: (_) async => true,
showCallkitIncoming: (params) async {
shown.add(params);
},
delay: (_) async {},
);
expect(polls, 2);
expect(shown, isEmpty);
});
test('iOS does not show when the call was reserved by the push', () async {
final shown = <CallKitParams>[];
await VideoCallIncomingPushPresenter.showIncomingCall(
payload,
isIos: true,
claimPollDelays: const <Duration>[Duration.zero],
isIncomingCallClaimed: (_) async => false,
reserveIncomingCall: (_) async => false,
showCallkitIncoming: (params) async {
shown.add(params);
},
delay: (_) async {},
);
expect(shown, isEmpty);
});
test(
'Android shows the incoming call without waiting for a VoIP claim',
() async {
final shown = <CallKitParams>[];
var claimed = false;
await VideoCallIncomingPushPresenter.showIncomingCall(
payload,
isIos: false,
isIncomingCallClaimed: (_) async {
claimed = true;
return true;
},
showCallkitIncoming: (params) async {
shown.add(params);
},
);
expect(claimed, isFalse);
expect(shown, hasLength(1));
expect(shown.single.id, payload.conversationUuid);
expect(shown.single.android?.ringtonePath, 'system_ringtone_default');
},
);
});
}