From 4e08f57c48e7b29cf400454289f46f430fb04102 Mon Sep 17 00:00:00 2001 From: liyi Date: Wed, 2 Jul 2025 17:11:39 +0800 Subject: [PATCH] =?UTF-8?q?fix:=E8=B0=83=E6=95=B4=E4=B8=BAcallkit=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ios/Runner/AppDelegate.m | 84 +++++++++++++++++++++++++++++++++- ios/Runner/Info.plist | 2 + ios/Runner/LCKBridge.swift | 35 ++++++++++++-- ios/Runner/info_sky.plist | 2 + ios/Runner/info_xhj.plist | 2 + lib/main.dart | 2 + lib/tools/callkit_handler.dart | 56 +++++++++++++++++++++++ 7 files changed, 179 insertions(+), 4 deletions(-) create mode 100644 lib/tools/callkit_handler.dart diff --git a/ios/Runner/AppDelegate.m b/ios/Runner/AppDelegate.m index 107a37cc..555cd8c2 100755 --- a/ios/Runner/AppDelegate.m +++ b/ios/Runner/AppDelegate.m @@ -7,11 +7,15 @@ #import #import +#import // #import -@interface AppDelegate() +@interface AppDelegate() @property (nonatomic, strong) FlutterMethodChannel *methodChannel; +@property (nonatomic, strong) CXProvider *callKitProvider; +@property (nonatomic, copy) NSString *pendingCallKitEvent; // 缓存未处理的CallKit事件 +@property (nonatomic, strong) NSUUID *lastCallUUID; @end @@ -45,6 +49,45 @@ self.window.rootViewController = VC; [self.window makeKeyAndVisible]; + // 初始化FlutterMethodChannel + FlutterViewController *controller = (FlutterViewController *)self.window.rootViewController; + self.methodChannel = [FlutterMethodChannel methodChannelWithName:@"com.starlock/callkit" binaryMessenger:controller.binaryMessenger]; + // 注册拉取pending事件方法 + __weak typeof(self) weakSelf = self; + [self.methodChannel setMethodCallHandler:^(FlutterMethodCall *call, FlutterResult result) { + if ([call.method isEqualToString:@"get_pending_event"]) { + if (weakSelf.pendingCallKitEvent) { + result(weakSelf.pendingCallKitEvent); + weakSelf.pendingCallKitEvent = nil; + } else { + result(nil); + } + } else if ([call.method isEqualToString:@"end_call"]) { + NSLog(@"[CallKit] 收到Flutter端结束通话请求"); + // 结束CallKit通话 + // 这里只能结束最近一次来电,需保存UUID + if (weakSelf.lastCallUUID) { + CXCallEndedReason reason = CXCallEndedReasonRemoteEnded; + [weakSelf.callKitProvider reportCallWithUUID:weakSelf.lastCallUUID endedAtDate:[NSDate date] reason:reason]; + weakSelf.lastCallUUID = nil; + result(@"ok"); + } else { + NSLog(@"[CallKit] 无有效UUID,无法结束通话"); + result(@"no_call"); + } + } else { + result(FlutterMethodNotImplemented); + } + }]; + // 初始化CallKit Provider(仅国外包) +#if USE_CALLKIT + CXProviderConfiguration *providerConfiguration = [[CXProviderConfiguration alloc] initWithLocalizedName:@"来电"]; + providerConfiguration.supportsVideo = NO; + providerConfiguration.maximumCallsPerCallGroup = 1; + self.callKitProvider = [[CXProvider alloc] initWithConfiguration:providerConfiguration]; + [self.callKitProvider setDelegate:self queue:nil]; +#endif + return YES; } @@ -190,13 +233,52 @@ - (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(PKPushType)type withCompletionHandler:(void (^)(void))completion { if ([type isEqualToString:PKPushTypeVoIP]) { NSLog(@"[VoIP] didReceiveIncomingPushWithPayload: %@", payload.dictionaryPayload); +#if USE_CALLKIT + // 国外环境,直接用CallKit弹窗 + NSString *callerName = @"来电"; // 可根据payload内容自定义 + // 复用已初始化的provider + CXCallUpdate *update = [[CXCallUpdate alloc] init]; + update.remoteHandle = [[CXHandle alloc] initWithType:CXHandleTypeGeneric value:callerName]; + update.hasVideo = NO; + NSUUID *callUUID = [NSUUID UUID]; + self.lastCallUUID = callUUID; + [self.callKitProvider reportNewIncomingCallWithUUID:callUUID update:update completion:^(NSError * _Nullable error) { + if (error) { + NSLog(@"CallKit error: %@", error); + } + }]; +#else + // 国内环境,使用LiveCommunicationKit弹窗 UIViewController *rootVC = [UIApplication sharedApplication].delegate.window.rootViewController; NSString *callerName = @"来电"; // 可根据payload内容自定义 [LCKBridge presentCallInterfaceFromRootVC:rootVC callerName:callerName]; +#endif } if (completion) { completion(); } } +#pragma mark - CXProviderDelegate +// 用户点击"接听" +- (void)provider:(CXProvider *)provider performAnswerCallAction:(CXAnswerCallAction *)action { + NSLog(@"[CallKit] 用户接听来电"); + if (self.methodChannel) { + [self.methodChannel invokeMethod:@"callkit_answered" arguments:nil]; + } else { + self.pendingCallKitEvent = @"callkit_answered"; + } + [action fulfill]; +} +// 用户点击"拒绝"或挂断 +- (void)provider:(CXProvider *)provider performEndCallAction:(CXEndCallAction *)action { + NSLog(@"[CallKit] 用户拒绝/挂断来电"); + if (self.methodChannel) { + [self.methodChannel invokeMethod:@"callkit_declined" arguments:nil]; + } else { + self.pendingCallKitEvent = @"callkit_declined"; + } + [action fulfill]; +} + @end diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 9260e206..3d7b8cd8 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -109,6 +109,8 @@ remote-notification voip + processing + fetch UIFileSharingEnabled diff --git a/ios/Runner/LCKBridge.swift b/ios/Runner/LCKBridge.swift index c53f488e..24d28218 100644 --- a/ios/Runner/LCKBridge.swift +++ b/ios/Runner/LCKBridge.swift @@ -1,9 +1,37 @@ import Foundation import UIKit +#if USE_CALLKIT +import CallKit +#endif import LiveCommunicationKit -@objc class LCKBridge: NSObject { - @objc static func presentCallInterfaceFromRootVC(_ rootVC: UIViewController, callerName: String) { +@objc(LCKBridge) +class LCKBridge: NSObject { + // CallKit来电弹窗 + @objc(presentCallInterfaceWithCallKit:) + class func presentCallInterfaceWithCallKit(_ callerName: NSString) { + #if USE_CALLKIT + let providerConfiguration = CXProviderConfiguration(localizedName: callerName as String) + providerConfiguration.supportsVideo = false + providerConfiguration.maximumCallsPerCallGroup = 1 + let provider = CXProvider(configuration: providerConfiguration) + let update = CXCallUpdate() + update.remoteHandle = CXHandle(type: .generic, value: callerName as String) + update.hasVideo = false + provider.setDelegate(nil, queue: nil) + provider.reportNewIncomingCall(with: UUID(), update: update) { error in + if let error = error { + print("CallKit来电弹窗失败: \(error.localizedDescription)") + } else { + print("CallKit来电弹窗成功") + } + } + #endif + } + // LiveCommunicationKit来电弹窗 + @objc(presentCallInterfaceFromRootVC:callerName:) + class func presentCallInterfaceFromRootVC(_ rootVC: UIViewController, callerName: NSString) { + #if !USE_CALLKIT if #available(iOS 17.4, *) { // 配置ConversationManager let config = ConversationManager.Configuration( @@ -16,7 +44,7 @@ import LiveCommunicationKit supportedHandleTypes: [.generic, .phoneNumber, .emailAddress] ) let manager = ConversationManager(configuration: config) - let local = Handle(type: .generic, value: callerName, displayName: callerName) + let local = Handle(type: .generic, value: callerName as String, displayName: callerName as String) let update = Conversation.Update(localMember: local, members: [local], activeRemoteMembers: [local]) Task { do { @@ -27,5 +55,6 @@ import LiveCommunicationKit } } } + #endif } } \ No newline at end of file diff --git a/ios/Runner/info_sky.plist b/ios/Runner/info_sky.plist index b4c7aed1..d1054912 100755 --- a/ios/Runner/info_sky.plist +++ b/ios/Runner/info_sky.plist @@ -109,6 +109,8 @@ remote-notification voip + processing + fetch UIFileSharingEnabled diff --git a/ios/Runner/info_xhj.plist b/ios/Runner/info_xhj.plist index 35272474..b37a80a4 100755 --- a/ios/Runner/info_xhj.plist +++ b/ios/Runner/info_xhj.plist @@ -109,6 +109,8 @@ remote-notification voip + processing + fetch UIFileSharingEnabled diff --git a/lib/main.dart b/lib/main.dart index c74c51d8..ca70354c 100755 --- a/lib/main.dart +++ b/lib/main.dart @@ -19,6 +19,7 @@ import 'package:star_lock/talk/starChart/handle/impl/debug_Info_model.dart'; import 'package:star_lock/talk/starChart/status/appLifecycle_observer.dart'; import 'package:star_lock/tools/baseGetXController.dart'; import 'package:star_lock/tools/bugly/bugly_tool.dart'; +import 'package:star_lock/tools/callkit_handler.dart'; import 'package:star_lock/tools/device_info_service.dart'; import 'package:star_lock/tools/eventBusEventManage.dart'; import 'package:star_lock/tools/jverify_one_click_login.dart'; @@ -65,6 +66,7 @@ FutureOr main() async { // runApp(MultiProvider(providers: [ // ChangeNotifierProvider(create: (_) => DebugInfoModel()), // ], child: MyApp(isLogin: isLogin))); + CallKitHandler.setupListener(); runApp(MyApp(isLogin: isLogin)); }, onException: (FlutterErrorDetails details) async { debugPrint('FlutterErrorDetails ${details.exceptionAsString()}'); diff --git a/lib/tools/callkit_handler.dart b/lib/tools/callkit_handler.dart new file mode 100644 index 00000000..06d360f5 --- /dev/null +++ b/lib/tools/callkit_handler.dart @@ -0,0 +1,56 @@ +import 'package:flutter/services.dart'; +import 'package:get/get.dart'; +import 'package:star_lock/appRouters.dart'; +import 'package:star_lock/app_settings/app_settings.dart'; + +class CallKitHandler { + static const MethodChannel _channel = MethodChannel('com.starlock/callkit'); + static bool _isInitialized = false; + + /// 初始化监听,无需context + static void setupListener() async { + if (_isInitialized) { + print('CallKitHandler.setupListener 已初始化,跳过'); + return; + } + _isInitialized = true; + print('CallKitHandler.setupListener 初始化,准备拉取pending事件'); + // 启动时主动拉取pending事件 + final pendingEvent = + await _channel.invokeMethod('get_pending_event'); + print('CallKitHandler 拉取到pendingEvent: $pendingEvent'); + if (pendingEvent == 'callkit_answered') { + print('启动时拉取到callkit_answered,跳转到对讲页面'); + // 如需跳转页面可在此处实现 + } else if (pendingEvent == 'callkit_declined') { + print('启动时拉取到callkit_declined,返回主页面'); + } else { + print('启动时无pending事件'); + } + // 注册实时监听 + _channel.setMethodCallHandler((call) async { + print('CallKitHandler 收到原生事件: ${call.method}'); + if (call.method == 'callkit_answered') { + print('跳转到对讲页面'); + String currentRoute = Get.currentRoute; + print('当前路由: $currentRoute'); + if (currentRoute != Routers.h264View) { + Get.toNamed( + Routers.h264View, + ); + } + } else if (call.method == 'callkit_declined') { + print('返回主页面'); + } else { + print('收到未知事件: ${call.method}'); + } + }); + print('CallKitHandler.setupListener 监听已注册'); + } + + /// 通知原生端结束CallKit通话 + static Future endCall() async { + print('CallKitHandler.endCall 通知原生端结束通话'); + await _channel.invokeMethod('end_call'); + } +}