feat(video_calls): fixed ios and HMS pushes for calls

This commit is contained in:
misha
2026-06-16 15:01:46 +03:00
parent a07565fca9
commit 03f037518e
5 changed files with 183 additions and 28 deletions

View File

@@ -14,7 +14,6 @@ import 'package:flutter_callkit_incoming/entities/call_event.dart';
import 'package:flutter_callkit_incoming/entities/call_kit_params.dart';
import 'package:flutter_callkit_incoming/flutter_callkit_incoming.dart';
class VideoCallCallKitCoordinator {
VideoCallCallKitCoordinator({
required FirebaseApi firebaseApi,
@@ -29,6 +28,7 @@ class VideoCallCallKitCoordinator {
final VideoCallSessionCubit? _videoCallSessionCubit;
StreamSubscription<CallEvent?>? _eventSubscription;
AppLifecycleListener? _lifecycleListener;
final Map<String, CallKitParams> _callKitParamsById = {};
final Set<String> _acceptHandledCallKitIds = {};
final Set<String> _suppressedEndCallKitIds = {};
@@ -43,13 +43,22 @@ class VideoCallCallKitCoordinator {
if (Platform.isIOS) {
await _firebaseApi.registerPushToken();
_lifecycleListener = AppLifecycleListener(
onResume: () => unawaited(_openActiveCallKitCallIfNeeded()),
);
WidgetsBinding.instance.addPostFrameCallback((_) {
unawaited(_openActiveCallKitCallIfNeeded());
});
return;
}
if (Platform.isAndroid) {
VideoCallFcmHandler.registerForegroundListener();
FlutterCallkitIncoming.acceptCallHandle(_onNativeAcceptCallHandle);
await FlutterCallkitIncoming.requestNotificationPermission(<String, dynamic>{
await FlutterCallkitIncoming.requestNotificationPermission(<
String,
dynamic
>{
'title': 'Уведомления',
'rationaleMessagePermission':
'Разрешите уведомления, чтобы получать входящие видеозвонки.',
@@ -61,13 +70,14 @@ class VideoCallCallKitCoordinator {
void dispose() {
unawaited(_eventSubscription?.cancel());
_lifecycleListener?.dispose();
_eventSubscription = null;
_lifecycleListener = null;
_callKitParamsById.clear();
_initialized = false;
}
VideoCallManager get _manager =>
_videoCallManager ?? sl<VideoCallManager>();
VideoCallManager get _manager => _videoCallManager ?? sl<VideoCallManager>();
VideoCallSessionCubit get _cubit =>
_videoCallSessionCubit ?? sl<VideoCallSessionCubit>();
@@ -102,6 +112,49 @@ class VideoCallCallKitCoordinator {
CallKitParams? _paramsForCallId(String id) => _callKitParamsById[id];
Future<void> _openActiveCallKitCallIfNeeded() async {
if (!Platform.isIOS) return;
if (_cubit.state.connectPhase != VideoCallConnectPhase.idle) return;
final activeCalls = await FlutterCallkitIncoming.activeCalls();
if (activeCalls.isEmpty) return;
for (final params in activeCalls) {
_cacheCallKitParams(params);
final extra = params.extra ?? <String, dynamic>{};
final conversationUuid = _parseConversationUuid(
extra,
handle: params.handle,
);
if (conversationUuid.isEmpty) continue;
await _deliverIncomingAccept(
callKitId: params.id,
conversationUuid: conversationUuid,
counterpartName:
_parseString(extra, const [
'counterpartName',
'caller_name',
'callerName',
]) ??
params.nameCaller,
counterpartAvatarUrl:
_parseString(extra, const [
'counterpartAvatarUrl',
'avatar',
'avatarUrl',
'caller_avatar',
]) ??
params.avatar,
localParticipantName: _parseString(extra, const [
'localParticipantName',
'local_participant_name',
]),
);
return;
}
}
Future<void> _onNativeAcceptCallHandle(Map<dynamic, dynamic> body) async {
final extra = Map<String, dynamic>.from(body);
final conversationUuid = _parseConversationUuid(extra);
@@ -156,17 +209,21 @@ class VideoCallCallKitCoordinator {
await _deliverIncomingAccept(
callKitId: callKitId,
conversationUuid: conversationUuid,
counterpartName: _parseString(extra, const [
'counterpartName',
'caller_name',
'callerName',
]) ?? params?.nameCaller,
counterpartAvatarUrl: _parseString(extra, const [
'counterpartAvatarUrl',
'avatar',
'avatarUrl',
'caller_avatar',
]) ?? params?.avatar,
counterpartName:
_parseString(extra, const [
'counterpartName',
'caller_name',
'callerName',
]) ??
params?.nameCaller,
counterpartAvatarUrl:
_parseString(extra, const [
'counterpartAvatarUrl',
'avatar',
'avatarUrl',
'caller_avatar',
]) ??
params?.avatar,
localParticipantName: _parseString(extra, const [
'localParticipantName',
'local_participant_name',
@@ -191,12 +248,14 @@ class VideoCallCallKitCoordinator {
final binding = WidgetsBinding.instance;
final completer = Completer<void>();
binding.addPostFrameCallback((_) {
unawaited(_openConnectingScreenAfterAccept(
conversationUuid: conversationUuid,
counterpartName: counterpartName,
counterpartAvatarUrl: counterpartAvatarUrl,
localParticipantName: localParticipantName,
).whenComplete(completer.complete));
unawaited(
_openConnectingScreenAfterAccept(
conversationUuid: conversationUuid,
counterpartName: counterpartName,
counterpartAvatarUrl: counterpartAvatarUrl,
localParticipantName: localParticipantName,
).whenComplete(completer.complete),
);
});
await completer.future;
} finally {
@@ -284,10 +343,7 @@ class VideoCallCallKitCoordinator {
return '';
}
static String? _parseString(
Map<String, dynamic> map,
List<String> keys,
) {
static String? _parseString(Map<String, dynamic> map, List<String> keys) {
for (final key in keys) {
final value = map[key];
if (value is String && value.trim().isNotEmpty) {

View File

@@ -47,7 +47,7 @@ class VideoCallNotificationNavigator {
String? counterpartAvatarUrl,
String? localParticipantName,
}) async {
for (var attempt = 0; attempt < 40; attempt++) {
for (var attempt = 0; attempt < 100; attempt++) {
final ctx = AppRouter.navigatorKey.currentContext;
if (ctx != null && ctx.mounted) {
final router = GoRouter.of(ctx);
@@ -68,7 +68,7 @@ class VideoCallNotificationNavigator {
}
return;
}
await Future<void>.delayed(const Duration(milliseconds: 50));
await Future<void>.delayed(const Duration(milliseconds: 100));
}
}
}