From 7493870a3d259fd80ccc4faac8cbc810444ff73e Mon Sep 17 00:00:00 2001 From: misha Date: Thu, 18 Jun 2026 15:01:21 +0300 Subject: [PATCH] feat(video_calls): removed system ui, fixed wrong app initializing --- ios/Runner/AppDelegate.swift | 44 +++++++++++++++++-- .../video_call_callkit_coordinator.dart | 42 ++++++++++++++++-- 2 files changed, 80 insertions(+), 6 deletions(-) diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index ed5fa3dd..e1b521eb 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -13,6 +13,7 @@ import flutter_foreground_task private var voipRegistry: PKPushRegistry? private var voipDiagnosticsChannel: FlutterMethodChannel? private let voipDiagnosticsStorageKey = "EatvioVoipDiagnostics" + private let pendingAcceptedVoipCallKey = "EatvioPendingAcceptedVoipCall" private let voipDiagnosticsChannelName = "com.eatvioapp/voip_diagnostics" private let appUrlScheme = "eatvio" @@ -43,11 +44,18 @@ import flutter_foreground_task ) voipDiagnosticsChannel = channel channel.setMethodCallHandler { [weak self] call, result in - guard call.method == "drainVoipDiagnostics" else { - result(FlutterMethodNotImplemented) + guard let self else { + result(nil) return } - result(self?.drainVoipDiagnostics() ?? []) + switch call.method { + case "drainVoipDiagnostics": + result(self.drainVoipDiagnostics()) + case "drainPendingAcceptedVoipCall": + result(self.drainPendingAcceptedVoipCall()) + default: + result(FlutterMethodNotImplemented) + } } } @@ -159,6 +167,32 @@ import flutter_foreground_task return nil } + private func storePendingAcceptedCall(_ call: Call) { + let payload = sanitizedDiagnosticValue(call.data.toJSON()) as? [String: Any] ?? [:] + UserDefaults.standard.set(payload, forKey: pendingAcceptedVoipCallKey) + } + + private func drainPendingAcceptedVoipCall() -> [String: Any]? { + let defaults = UserDefaults.standard + guard let payload = defaults.dictionary(forKey: pendingAcceptedVoipCallKey) else { + return nil + } + defaults.removeObject(forKey: pendingAcceptedVoipCallKey) + return payload + } + + private func bringAppToForeground() { + DispatchQueue.main.async { + for scene in UIApplication.shared.connectedScenes { + guard let windowScene = scene as? UIWindowScene else { continue } + for window in windowScene.windows { + window.makeKeyAndVisible() + } + } + self.window?.makeKeyAndVisible() + } + } + private func openAppForAcceptedCall(_ call: Call) { guard let conversationUuid = conversationUuid(fromCall: call), !conversationUuid.isEmpty else { recordVoipDiagnostic( @@ -197,6 +231,8 @@ import flutter_foreground_task ] ) + bringAppToForeground() + UIApplication.shared.open(url, options: [:]) { [weak self] success in self?.recordVoipDiagnostic( success ? "accepted_call_open_app_succeeded" : "accepted_call_open_app_failed", @@ -446,6 +482,7 @@ import flutter_foreground_task func onAccept(_ call: Call, _ action: CXAnswerCallAction) { + storePendingAcceptedCall(call) recordVoipDiagnostic( "callkit_accept_native", data: [ @@ -456,6 +493,7 @@ import flutter_foreground_task ) voipDiagnosticsChannel?.invokeMethod("acceptedVoipCall", arguments: call.data.toJSON()) DispatchQueue.main.async { [weak self] in + self?.bringAppToForeground() self?.openAppForAcceptedCall(call) } action.fulfill() 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 55f5821e..b25e0468 100644 --- a/lib/features/video_calls/services/video_call_callkit_coordinator.dart +++ b/lib/features/video_calls/services/video_call_callkit_coordinator.dart @@ -14,6 +14,7 @@ import 'package:flutter/widgets.dart'; 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'; +import 'package:flutter_native_splash/flutter_native_splash.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; class VideoCallCallKitCoordinator { @@ -57,8 +58,10 @@ class VideoCallCallKitCoordinator { } }); await _firebaseApi.registerPushToken(); + await _drainPendingNativeAcceptedVoipCall(); WidgetsBinding.instance.addPostFrameCallback((_) { _scheduleNativeVoipDiagnosticsDrain(); + unawaited(_drainPendingNativeAcceptedVoipCall()); }); return; } @@ -137,6 +140,29 @@ class VideoCallCallKitCoordinator { } } + Future _drainPendingNativeAcceptedVoipCall() async { + if (!Platform.isIOS) return; + try { + final pending = await _voipDiagnosticsChannel.invokeMethod( + 'drainPendingAcceptedVoipCall', + ); + if (pending == null) return; + await _handleNativeAcceptedVoipCall(pending); + } on PlatformException catch (e, st) { + log( + 'VideoCallCallKitCoordinator: failed to drain pending native accept: $e', + name: 'VideoCallCallKit', + stackTrace: st, + ); + } catch (e, st) { + log( + 'VideoCallCallKitCoordinator: failed to process pending native accept: $e', + name: 'VideoCallCallKit', + stackTrace: st, + ); + } + } + Future _onCallKitEvent(CallEvent? event) async { if (event == null) return; unawaited(_captureCallKitEventToSentry(event)); @@ -424,7 +450,9 @@ class VideoCallCallKitCoordinator { _suppressedEndCallKitIds.add(callKitId); try { - await FlutterCallkitIncoming.endCall(callKitId); + if (!Platform.isIOS) { + await FlutterCallkitIncoming.endCall(callKitId); + } final binding = WidgetsBinding.instance; final completer = Completer(); @@ -439,6 +467,10 @@ class VideoCallCallKitCoordinator { ); }); await completer.future; + + if (Platform.isIOS) { + await FlutterCallkitIncoming.endCall(callKitId); + } } finally { unawaited( Future.delayed(const Duration(seconds: 2), () { @@ -454,8 +486,12 @@ class VideoCallCallKitCoordinator { String? counterpartName, String? counterpartAvatarUrl, String? localParticipantName, - }) { - return VideoCallNotificationNavigator.openIncomingCall( + }) async { + if (Platform.isIOS) { + FlutterNativeSplash.remove(); + } + + await VideoCallNotificationNavigator.openIncomingCall( conversationUuid: conversationUuid, counterpartName: counterpartName, counterpartAvatarUrl: counterpartAvatarUrl,