From 5a8f8aee3e40dc95725a6679809ee546a51d9e9e Mon Sep 17 00:00:00 2001 From: misha Date: Fri, 16 Oct 2026 15:41:13 +0300 Subject: [PATCH] feat(video_calls): ios repeated call fix --- ios/Runner/AppDelegate.swift | 6 ++++++ .../video_call_callkit_coordinator.dart | 14 +++++++++---- .../services/video_call_peer_departure.dart | 11 ++++++++++ pubspec.yaml | 2 +- .../video_call_peer_departure_test.dart | 21 +++++++++++++++++++ 5 files changed, 49 insertions(+), 5 deletions(-) diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 72fbc95b..8d3392a2 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -686,6 +686,12 @@ import flutter_foreground_task } else if !nameCaller.isEmpty { extra["counterpartName"] = nameCaller } + // Closing this report looks like a user decline. Dart must not tell the + // backend, or the server sends another ended VoIP and the other phone + // shows it as a new incoming call. + if isEndedPush { + extra["endedPush"] = true + } let data = flutter_callkit_incoming.Data( id: id, diff --git a/lib/features/video_calls/services/video_call_callkit_coordinator.dart b/lib/features/video_calls/services/video_call_callkit_coordinator.dart index d936f139..523f41fa 100644 --- a/lib/features/video_calls/services/video_call_callkit_coordinator.dart +++ b/lib/features/video_calls/services/video_call_callkit_coordinator.dart @@ -363,7 +363,7 @@ class VideoCallCallKitCoordinator { ); return; } - await _handleDecline(id); + await _handleDecline(id, eventExtra: _callKitEventParams(event)?.extra); } Future _handleEndedEvent(CallEvent event) async { @@ -855,7 +855,10 @@ class VideoCallCallKitCoordinator { ); } - Future _handleDecline(String callKitId) async { + Future _handleDecline( + String callKitId, { + Map? eventExtra, + }) async { if (_suppressedEndCallKitIds.contains(callKitId)) { log( 'VideoCallCallKitCoordinator: ignore spurious end/decline during accept, callKitId=$callKitId', @@ -865,12 +868,15 @@ class VideoCallCallKitCoordinator { } final params = _paramsForCallId(callKitId); - final extra = params?.extra ?? {}; + final extra = params?.extra ?? eventExtra ?? {}; final conversationUuid = _parseConversationUuid( extra, handle: params?.handle ?? callKitId, ); - final endedPushDismissal = callKitExtraMarksEndedPush(extra); + final endedPushDismissal = callKitDeclineIsEndedPush( + cachedExtra: params?.extra, + eventExtra: eventExtra, + ); _suppressedEndCallKitIds.add(callKitId); try { diff --git a/lib/features/video_calls/services/video_call_peer_departure.dart b/lib/features/video_calls/services/video_call_peer_departure.dart index d0709ef5..7db7ba16 100644 --- a/lib/features/video_calls/services/video_call_peer_departure.dart +++ b/lib/features/video_calls/services/video_call_peer_departure.dart @@ -71,6 +71,17 @@ bool callKitExtraMarksEndedPush(Map? extra) { return false; } +/// Either the cached incoming payload or the decline event itself can carry +/// the marker. The decline often arrives with the push payload, while the +/// cache still holds the original ringing call. +bool callKitDeclineIsEndedPush({ + Map? cachedExtra, + Map? eventExtra, +}) { + return callKitExtraMarksEndedPush(cachedExtra) || + callKitExtraMarksEndedPush(eventExtra); +} + bool shouldForceEndCallAfterRoomDisconnect({ required bool callStageEntered, required bool sessionConnected, diff --git a/pubspec.yaml b/pubspec.yaml index 0b2adf7d..088eb5eb 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: eatvio_app description: A new Flutter project. -version: 1.3.54+143 +version: 1.3.55+144 publish_to: 'none' environment: diff --git a/test/features/video_calls/services/video_call_peer_departure_test.dart b/test/features/video_calls/services/video_call_peer_departure_test.dart index a8d3c4af..30a9c38b 100644 --- a/test/features/video_calls/services/video_call_peer_departure_test.dart +++ b/test/features/video_calls/services/video_call_peer_departure_test.dart @@ -158,6 +158,27 @@ void main() { isTrue, ); expect(callKitExtraMarksEndedPush({}), isFalse); + expect( + callKitDeclineIsEndedPush( + cachedExtra: {}, + eventExtra: {'endedPush': true}, + ), + isTrue, + ); + expect( + callKitDeclineIsEndedPush( + cachedExtra: {'endedPush': true}, + eventExtra: {}, + ), + isTrue, + ); + expect( + callKitDeclineIsEndedPush( + cachedExtra: {}, + eventExtra: {}, + ), + isFalse, + ); }, );