Flutter来电界面定制修复插件flutter_callkit_incoming_fix_custom的使用
Flutter来电界面定制修复插件flutter_callkit_incoming_fix_custom的使用
Flutter来电界面定制修复插件flutter_callkit_incoming_fix_custom的使用
本插件用于在您的Flutter应用中展示来电界面(针对Android自定义UI,针对iOS使用Callkit)。
⭐ 特性
- 显示来电
- 开始通话
- 自定义Android/Callkit iOS界面
- 示例:使用Pushkit/VoIP进行iOS通话
iOS:仅在真机上工作,模拟器不支持(Callkit框架在模拟器上无法运行)
🚀 安装
1. 安装包
flutter pub add flutter_callkit_incoming
在pubspec.yaml
文件中添加依赖:
dependencies:
flutter_callkit_incoming: any
2. 配置项目
Android
编辑AndroidManifest.xml
文件:
<manifest...>
...
<!-- 使用网络加载图像 -->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>
iOS
编辑Info.plist
文件:
<key>UIBackgroundModes</key>
<array>
<string>processing</string>
<string>remote-notification</string>
<string>voip</string>
</array>
3. 使用
导入
import 'package:flutter_callkit_incoming/flutter_callkit_incoming.dart';
接收来电
this._currentUuid = _uuid.v4();
CallKitParams callKitParams = CallKitParams(
id: _currentUuid,
nameCaller: 'Hien Nguyen',
appName: 'Callkit',
avatar: 'https://i.pravatar.cc/100',
handle: '0123456789',
type: 0,
textAccept: 'Accept',
textDecline: 'Decline',
textMissedCall: 'Missed call',
textCallback: 'Call back',
duration: 30000,
extra: <String, dynamic>{'userId': '1a2b3c4d'},
headers: <String, dynamic>{'apiKey': 'Abc@123!', 'platform': 'flutter'},
android: const AndroidParams(
isCustomNotification: true,
isShowLogo: false,
isShowCallback: false,
isShowMissedCallNotification: true,
ringtonePath: 'system_ringtone_default',
backgroundColor: '#0955fa',
backgroundUrl: 'https://i.pravatar.cc/500',
actionColor: '#4CAF50',
incomingCallNotificationChannelName: "Incoming Call",
missedCallNotificationChannelName: "Missed Call"),
ios: IOSParams(
iconName: 'CallKitLogo',
handleType: 'generic',
supportsVideo: true,
maximumCallGroups: 2,
maximumCallsPerCallGroup: 1,
audioSessionMode: 'default',
audioSessionActive: true,
audioSessionPreferredSampleRate: 44100.0,
audioSessionPreferredIOBufferDuration: 0.005,
supportsDTMF: true,
supportsHolding: true,
supportsGrouping: false,
supportsUngrouping: false,
ringtonePath: 'system_ringtone_default',
),
);
await FlutterCallkitIncoming.showCallkitIncoming(callKitParams);
显示未接来电通知
this._currentUuid = _uuid.v4();
CallKitParams params = CallKitParams(
id: _currentUuid,
nameCaller: 'Hien Nguyen',
handle: '0123456789',
type: 1,
textMissedCall: 'Missed call',
textCallback: 'Call back',
extra: <String, dynamic>{'userId': '1a2b3c4d'},
);
await FlutterCallkitIncoming.showMissCallNotification(params);
开始通话
this._currentUuid = _uuid.v4();
CallKitParams params = CallKitParams(
id: this._currentUuid,
nameCaller: 'Hien Nguyen',
handle: '0123456789',
type: 1,
extra: <String, dynamic>{'userId': '1a2b3c4d'},
ios: IOSParams(handleType: 'generic')
);
await FlutterCallkitIncoming.startCall(params);
结束通话
await FlutterCallkitIncoming.endCall(this._currentUuid);
结束所有通话
await FlutterCallkitIncoming.endAllCalls();
获取活跃通话
await FlutterCallkitIncoming.activeCalls();
输出:
[{"id": "8BAA2B26-47AD-42C1-9197-1D75F662DF78", ...}]
获取设备VoIP推送令牌
await FlutterCallkitIncoming.getDevicePushTokenVoIP();
输出:
<d6a77ca80c5f09f87f353cdd328ec8d7d34e92eb108d046c91906f27f54949cd>
// Example
d6a77ca80c5f09f87f353cdd328ec8d7d34e92eb108d046c91906f27f54949cd
确保在AppDelegate.swift中使用:
func pushRegistry(_ registry: PKPushRegistry, didUpdate credentials: PKPushCredentials, for type: PKPushType) {
print(credentials.token)
let deviceToken = credentials.token.map { String(format: "%02x", $0) }.joined()
// Save deviceToken to your server
SwiftFlutterCallkitIncomingPlugin.sharedInstance?.setDevicePushTokenVoIP(deviceToken)
}
func pushRegistry(_ registry: PKPushRegistry, didInvalidatePushTokenFor type: PKPushType) {
print("didInvalidatePushTokenFor")
SwiftFlutterCallkitIncomingPlugin.sharedInstance?.setDevicePushTokenVoIP("")
}
监听事件
FlutterCallkitIncoming.onEvent.listen((CallEvent event) {
switch (event!.event) {
case Event.ACTION_CALL_INCOMING:
// TODO: 收到来电
break;
case Event.ACTION_CALL_START:
// TODO: 开始通话
// TODO: 在Flutter中显示通话屏幕
break;
case Event.ACTION_CALL_ACCEPT:
// TODO: 接受来电
// TODO: 在Flutter中显示通话屏幕
break;
case Event.ACTION_CALL_DECLINE:
// TODO: 拒绝来电
break;
case Event.ACTION_CALL_ENDED:
// TODO: 结束通话
break;
case Event.ACTION_CALL_TIMEOUT:
// TODO: 来电超时
break;
case Event.ACTION_CALL_CALLBACK:
// TODO: 仅Android - 点击未接来电通知中的“回拨”
break;
case Event.ACTION_CALL_TOGGLE_HOLD:
// TODO: 仅iOS
break;
case Event.ACTION_CALL_TOGGLE_MUTE:
// TODO: 仅iOS
break;
case Event.ACTION_CALL_TOGGLE_DMTF:
// TODO: 仅iOS
break;
case Event.ACTION_CALL_TOGGLE_GROUP:
// TODO: 仅iOS
break;
case Event.ACTION_CALL_TOGGLE_AUDIO_SESSION:
// TODO: 仅iOS
break;
case Event.ACTION_DID_UPDATE_DEVICE_PUSH_TOKEN_VOIP:
// TODO: 仅iOS
break;
}
});
从原生代码调用(iOS/Android)
// Swift iOS
var info = [String: Any?]()
info["id"] = "44d915e1-5ff4-4bed-bf13-c423048ec97a"
info["nameCaller"] = "Hien Nguyen"
info["handle"] = "0123456789"
info["type"] = 1
// 设置更多数据
SwiftFlutterCallkitIncomingPlugin.sharedInstance?.showCallkitIncoming(flutter_callkit_incoming.Data(args: info), fromPushKit: true)
// Kotlin/Java Android
FlutterCallkitIncomingPlugin.getInstance().showIncomingNotification(...)
// OR
let data = flutter_callkit_incoming.Data(id: "44d915e1-5ff4-4bed-bf13-c423048ec97a", nameCaller: "Hien Nguyen", handle: "0123456789", type: 0)
data.nameCaller = "Johnny"
data.extra = ["user": "abc@123", "platform": "ios"]
// 设置更多数据
SwiftFlutterCallkitIncomingPlugin.sharedInstance?.showCallkitIncoming(data, fromPushKit: true)
// 发送自定义事件
SwiftFlutterCallkitIncomingPlugin.sharedInstance?.sendEventCustom("customEvent", body: ["customKey": "customValue"])
// Kotlin/Java Android
FlutterCallkitIncomingPlugin.getInstance().sendEventCustom(event: String, body: Map<String, Any>)
属性
属性 | 描述 | 默认值 |
---|---|---|
id | 每个通话的UUID标识符。UUID应为每次通话唯一,并且当通话结束时,应使用相同的UUID。建议使用 uuid 包。只接受UUID。 | 必填 |
nameCaller | 来电者的名字。 | 无 |
appName | 应用名。在iOS中用于显示Callkit。 | 应用名,已弃用用于iOS > 14,默认使用应用名。 |
avatar | 用于Android的头像URL。 | 无 |
handle | 电话号码/电子邮件/其他。 | 无 |
type | 0 - 语音通话,1 - 视频通话。 | 0 |
duration | 来电/去电显示时间(秒)。如果超过此时间,通话将被标记为未接。 | 30000 |
textAccept | 用于Android的“接受”文本。 | Accept |
textDecline | 用于Android的“拒绝”文本。 | Decline |
textMissedCall | 用于Android的“未接来电”文本(显示在未接来电通知中)。 | Missed Call |
textCallback | 用于Android的“回拨”文本(显示在未接来电通知中)。 | Call back |
extra | 当收到事件时添加到事件的任何数据。 | {} |
headers | 用于自定义头像/背景图像的任何数据。 | {} |
android | Android自定义UI所需的数据。 | 下方 |
ios | iOS所需的数据。 | 下方 |
Android
属性 | 描述 | 默认值 |
---|---|---|
isCustomNotification | 是否使用自定义通知。 | false |
isCustomSmallExNotification | 是否使用自定义小通知(在某些设备上剪辑出Android)。 | false |
isShowLogo | 是否显示全屏内的应用图标。 | false |
isShowMissedCallNotification | 是否显示未接来电通知。 | true |
isShowCallback | 是否显示未接来电通知中的回调操作。 | true |
ringtonePath | 文件名铃声。将文件放入 /android/app/src/main/res/raw/ringtone_default.pm3 。 |
system_ringtone_default 使用手机默认铃声 |
backgroundColor | 来电屏幕背景色。 | #0955fa |
backgroundUrl | 用于来电屏幕的背景图像。例如:http://… 或 https://… 或 “assets/abc.png”。 | 无 |
actionColor | 通知按钮/文字颜色。 | #4CAF50 |
incomingCallNotificationChannelName | 来电通知频道名称。 | Incoming call |
missedCallNotificationChannelName | 未接来电通知频道名称。 | Missed call |
iOS
属性 | 描述 | 默认值 |
---|---|---|
iconName | 应用图标。在iOS中用于显示Callkit。 | CallKitLogo 使用 Images.xcassets/CallKitLogo |
handleType | 呼叫类型:generic , number , email 。 |
generic |
supportsVideo | 是否支持视频通话。 | true |
maximumCallGroups | 最大通话组数。 | 2 |
maximumCallsPerCallGroup | 每个通话组的最大通话数。 | 1 |
audioSessionMode | 音频会话模式。 | 无,gameChat , measurement , moviePlayback , spokenAudio , videoChat , videoRecording , voiceChat , voicePrompt |
audioSessionActive | 音频会话是否激活。 | true |
audioSessionPreferredSampleRate | 音频会话首选采样率。 | 44100.0 |
audioSessionPreferredIOBufferDuration | 音频会话首选I/O缓冲区持续时间。 | 0.005 |
supportsDTMF | 是否支持DTMF。 | true |
supportsHolding | 是否支持保持。 | true |
supportsGrouping | 是否支持分组。 | true |
supportsUngrouping | 是否支持取消分组。 | true |
ringtonePath | 添加文件到Xcode根项目 <ios/Runner/Ringtone.caf> 并复制资源(构建阶段)。 |
Ringtone.caf system_ringtone_default 使用手机默认铃声 |
示例代码
import 'dart:async';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';
import 'package:flutter_callkit_incoming/entities/android_params.dart';
import 'package:flutter_callkit_incoming/entities/call_kit_params.dart';
import 'package:flutter_callkit_incoming/entities/ios_params.dart';
import 'package:flutter_callkit_incoming/flutter_callkit_incoming.dart';
import 'package:flutter_callkit_incoming_example/app_router.dart';
import 'package:flutter_callkit_incoming_example/navigation_service.dart';
import 'package:uuid/uuid.dart';
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
print("Handling a background message: ${message.messageId}");
showCallkitIncoming(Uuid().v4());
}
Future<void> showCallkitIncoming(String uuid) async {
final params = CallKitParams(
id: uuid,
nameCaller: 'Hien Nguyen',
appName: 'Callkit',
avatar: 'https://i.pravatar.cc/100',
handle: '0123456789',
type: 0,
duration: 30000,
textAccept: 'Accept',
textDecline: 'Decline',
textMissedCall: 'Missed call',
textCallback: 'Call back',
extra: <String, dynamic>{'userId': '1a2b3c4d'},
headers: <String, dynamic>{'apiKey': 'Abc@123!', 'platform': 'flutter'},
android: AndroidParams(
isCustomNotification: true,
isShowLogo: false,
isShowCallback: true,
isShowMissedCallNotification: true,
ringtonePath: 'system_ringtone_default',
backgroundColor: '#0955fa',
backgroundUrl: 'assets/test.png',
actionColor: '#4CAF50',
),
ios: IOSParams(
iconName: 'CallKitLogo',
handleType: '',
supportsVideo: true,
maximumCallGroups: 2,
maximumCallsPerCallGroup: 1,
audioSessionMode: 'default',
audioSessionActive: true,
audioSessionPreferredSampleRate: 44100.0,
audioSessionPreferredIOBufferDuration: 0.005,
supportsDTMF: true,
supportsHolding: true,
supportsGrouping: false,
supportsUngrouping: false,
ringtonePath: 'system_ringtone_default',
),
);
await FlutterCallkitIncoming.showCallkitIncoming(params);
}
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(MyApp());
}
class MyApp extends StatefulWidget {
[@override](/user/override)
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
late final Uuid _uuid;
String? _currentUuid;
late final FirebaseMessaging _firebaseMessaging;
[@override](/user/override)
void initState() {
super.initState();
_uuid = Uuid();
initFirebase();
WidgetsBinding.instance.addObserver(this);
// 检查打开应用后是否有通话
checkAndNavigationCallingPage();
}
getCurrentCall() async {
// 检查可能来自Pushkit的当前通话
var calls = await FlutterCallkitIncoming.activeCalls();
if (calls is List) {
if (calls.isNotEmpty) {
print('DATA: $calls');
_currentUuid = calls[0]['id'];
return calls[0];
} else {
_currentUuid = "";
return null;
}
}
}
checkAndNavigationCallingPage() async {
var currentCall = await getCurrentCall();
if (currentCall != null) {
NavigationService.instance
.pushNamedIfNotCurrent(AppRoute.callingPage, args: currentCall);
}
}
[@override](/user/override)
Future<void> didChangeAppLifecycleState(AppLifecycleState state) async {
print(state);
if (state == AppLifecycleState.resumed) {
// 检查从后台打开应用后是否有通话
checkAndNavigationCallingPage();
}
}
[@override](/user/override)
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
initFirebase() async {
await Firebase.initializeApp();
_firebaseMessaging = FirebaseMessaging.instance;
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
FirebaseMessaging.onMessage.listen((RemoteMessage message) async {
print(
'Message title: ${message.notification?.title}, body: ${message.notification?.body}, data: ${message.data}');
_currentUuid = _uuid.v4();
showCallkitIncoming(_currentUuid!);
});
_firebaseMessaging.getToken().then((token) {
print('Device Token FCM: $token');
});
}
[@override](/user/override)
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.light(),
onGenerateRoute: AppRoute.generateRoute,
initialRoute: AppRoute.homePage,
navigatorKey: NavigationService.instance.navigationKey,
navigatorObservers: <NavigatorObserver>[
NavigationService.instance.routeObserver
],
);
}
Future<void> getDevicePushTokenVoIP() async {
var devicePushTokenVoIP =
await FlutterCallkitIncoming.getDevicePushTokenVoIP();
print(devicePushTokenVoIP);
}
}
更多关于Flutter来电界面定制修复插件flutter_callkit_incoming_fix_custom的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter来电界面定制修复插件flutter_callkit_incoming_fix_custom的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,以下是如何在Flutter项目中使用flutter_callkit_incoming_fix_custom
插件来定制和修复来电界面的一个简要指南和代码示例。这个插件主要用于iOS平台,因为它涉及到系统级的来电界面定制。
步骤 1: 添加依赖
首先,你需要在pubspec.yaml
文件中添加flutter_callkit_incoming_fix_custom
依赖:
dependencies:
flutter:
sdk: flutter
flutter_callkit_incoming_fix_custom: ^最新版本号 # 请替换为最新的版本号
然后运行flutter pub get
来安装依赖。
步骤 2: 配置iOS项目
由于这个插件主要针对iOS,你需要在Xcode中进行一些配置。
-
打开
ios/Runner/Info.plist
文件,添加必要的权限请求,例如麦克风和相机权限(如果你的应用需要这些权限)。 -
在Xcode中,确保你的
Runner
target的Signing & Capabilities
部分配置了正确的App ID和需要的权限。
步骤 3: 使用插件
下面是一个简单的示例,展示如何在Flutter中使用flutter_callkit_incoming_fix_custom
插件来配置和显示来电界面。
import 'package:flutter/material.dart';
import 'package:flutter_callkit_incoming_fix_custom/flutter_callkit_incoming_fix_custom.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('CallKit Custom Incoming Call Example'),
),
body: Center(
child: ElevatedButton(
onPressed: _startIncomingCall,
child: Text('Start Incoming Call'),
),
),
),
);
}
void _startIncomingCall() async {
// 配置CallKit的参数
var callUUID = UUID().v4(); // 生成一个唯一的UUID
var handle = 'caller_handle'; // 来电显示的号码或名称
var hasVideo = false; // 是否包含视频通话
// 启动来电界面
await FlutterCallkitIncomingFixCustom.reportNewIncomingCall(
uuid: callUUID,
handle: handle,
hasVideo: hasVideo,
localizedCallerName: 'Caller Name', // 来电显示的名称
);
// 这里你可以处理接听或拒绝的逻辑
// 例如,监听用户的操作(接听或拒绝)
// FlutterCallkitIncomingFixCustom.provider.listenForCallActions().listen((event) {
// if (event.action == CallAction.answer) {
// // 用户接听电话
// } else if (event.action == CallAction.end) {
// // 用户拒绝或结束电话
// }
// });
// 注意:实际应用中,你需要在适当的时候调用
// FlutterCallkitIncomingFixCustom.provider.endCall(uuid: callUUID);
// 来结束通话
}
}
// UUID生成工具类(你可以使用任何UUID生成库)
class UUID {
static String v4() => 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'
.replaceAll('x', _randomHex())
.replaceAll('y', (_randomHex()..codeUnitAt(0) & 0x3f | 0x80).toRadixString(16));
static String _randomHex() =>
'${_randomInt(16)}${_randomInt(16)}'.padLeft(2, '0');
static int _randomInt(int max) => Random().nextInt(max);
}
注意事项
- 权限处理:确保你的应用已经请求并获得了必要的权限,如麦克风和相机权限。
- 事件监听:在实际应用中,你需要监听用户的接听或拒绝操作,并相应地处理通话逻辑。
- 结束通话:在通话结束后,确保调用
FlutterCallkitIncomingFixCustom.provider.endCall
方法来清理CallKit状态。
这个示例提供了一个基本的框架,你可以根据自己的需求进一步定制和扩展来电界面的功能和样式。