feat(video_calls): ios call declining notification for other devices fix

This commit is contained in:
misha
2026-10-22 17:44:46 +03:00
parent d8d7a211bd
commit 2779f79464
3 changed files with 80 additions and 2 deletions

View File

@@ -322,6 +322,21 @@ import flutter_foreground_task
return nil
}
private func extraMarksEndedPush(_ extra: [String: Any]) -> Bool {
let value = extra["endedPush"]
if let bool = value as? Bool {
return bool
}
if let number = value as? NSNumber {
return number.intValue != 0
}
if let text = value as? String {
let normalized = text.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
return normalized == "true" || normalized == "1"
}
return false
}
private func voipBool(_ value: Any?) -> Bool? {
if let bool = value as? Bool {
return bool
@@ -1106,6 +1121,22 @@ import flutter_foreground_task
resumeAudioAfterCallKitEnds = false
audioRestartGeneration += 1
callKitOwnsAudioSession = false
// Accept reports the CallKit payload on this channel, and that is what
// stops ringing on the other devices. The plugin decline event does not.
let extra = call.data.extra as? [String: Any] ?? [:]
if !extraMarksEndedPush(extra) {
recordVoipDiagnostic(
"callkit_decline_native",
data: [
"callKitId": call.data.uuid,
"conversationUuid": conversationUuid(fromCall: call) ?? "",
]
)
voipDiagnosticsChannel?.invokeMethod(
"declinedVoipCall",
arguments: call.data.toJSON()
)
}
action.fulfill()
}

View File

@@ -79,6 +79,8 @@ class VideoCallCallKitCoordinator {
_scheduleNativeVoipDiagnosticsDrain();
case 'acceptedVoipCall':
await _handleNativeAcceptedVoipCall(call.arguments);
case 'declinedVoipCall':
await _handleNativeDeclinedVoipCall(call.arguments);
case 'tombstoneCallId':
_handleNativeTombstoneCallId(call.arguments);
}
@@ -390,7 +392,11 @@ class VideoCallCallKitCoordinator {
);
return;
}
await _handleDecline(id, eventExtra: _callKitEventParams(event)?.extra);
await _handleDecline(
id,
eventExtra: _callKitEventParams(event)?.extra,
skipBackendNotify: Platform.isIOS && event is CallEventActionCallDecline,
);
}
Future<void> _handleEndedEvent(CallEvent event) async {
@@ -615,6 +621,39 @@ class VideoCallCallKitCoordinator {
);
}
/// Same payload accept uses. The plugin decline event is not what stops
/// ringing on the other devices.
Future<void> _handleNativeDeclinedVoipCall(Object? arguments) async {
if (!Platform.isIOS) return;
final data = _stringKeyedMap(arguments);
final extra = _stringKeyedMap(data['extra']);
if (callKitExtraMarksEndedPush(Map<String, dynamic>.from(extra))) return;
final callKitId = _parseString(data, const ['id', 'uuid']);
if (callKitId != null &&
(_suppressedEndCallKitIds.contains(callKitId) ||
VideoCallRemoteEndRegistry.contains(callKitId))) {
return;
}
final conversationUuid = _parseConversationUuid(
extra,
handle: _parseString(data, const ['handle']),
);
final callId = backendVideoCallId(
callId:
_parseCallId(extra) ??
(callKitId != null && callKitId.contains('-') ? callKitId : null),
conversationUuid: conversationUuid,
);
log(
'VideoCallCallKitCoordinator: native declinedVoipCall conversation=$conversationUuid callId=$callId',
name: 'VideoCallCallKit',
);
await _manager.declineIncomingCall(conversationUuid, callId: callId);
}
Future<void> _handleAccept(String callKitId) async {
if (_acceptNavigationCompletedIds.contains(callKitId)) return;
@@ -976,6 +1015,7 @@ class VideoCallCallKitCoordinator {
Future<void> _handleDecline(
String callKitId, {
Map<String, dynamic>? eventExtra,
bool skipBackendNotify = false,
}) async {
if (_suppressedEndCallKitIds.contains(callKitId)) {
log(
@@ -1015,6 +1055,13 @@ class VideoCallCallKitCoordinator {
return;
}
if (skipBackendNotify) {
// iOS decline is reported from AppDelegate with the CallKit payload,
// the same way accept is. This plugin event must not send /ended instead.
_forgetCallKitId(callKitId);
return;
}
_suppressedEndCallKitIds.add(callKitId);
try {
await FlutterCallkitIncoming.endCall(callKitId);

View File

@@ -1,6 +1,6 @@
name: eatvio_app
description: A new Flutter project.
version: 1.3.65+154
version: 1.3.66+155
publish_to: 'none'
environment: