Flutter来电管理插件billtech_incoming_call的使用
Flutter 来电管理插件 billtech_incoming_call 的使用
概述
这是一个用于在 Flutter 应用中显示来电的插件(适用于 Android 和 Callkit for iOS)。
特性
- 显示来电
- 开始外呼
- 自定义 Android UI / Callkit for iOS
- 使用 Pushkit/VoIP 作为 iOS 示例
iOS 注意事项
仅在真实设备上运行,模拟器不支持(Callkit 框架在模拟器上无法工作)
安装
-
安装包
flutter pub add billtech_incoming_call
-
添加到 pubspec.yaml
dependencies: billtech_incoming_call: any
-
配置项目
-
Android 在
AndroidManifest.xml
中添加以下权限:<uses-permission android:name="android.permission.INTERNET"/>
-
iOS 在
Info.plist
中添加以下配置:<key>UIBackgroundModes</key> <array> <string>processing</string> <string>remote-notification</string> <string>voip</string> </array>
-
使用方法
-
导入插件
import 'package:billtech_incoming_call/billtech_incoming_call.dart';
-
接收来电
this._currentUuid = _uuid.v4(); var params = <String, dynamic>{ 'id': _currentUuid, 'nameCaller': 'Hien Nguyen', 'appName': 'Callkit', 'avatar': 'https://i.pravatar.cc/100', 'handle': '0123456789', 'type': 0, 'textAccept': '接受', 'textDecline': '拒绝', 'textMissedCall': '未接来电', 'textCallback': '回拨', 'duration': 30000, 'extra': <String, dynamic>{'userId': '1a2b3c4d'}, 'headers': <String, dynamic>{'apiKey': 'Abc@123!', 'platform': 'flutter'}, 'android': <String, dynamic>{ 'isCustomNotification': true, 'isShowLogo': false, 'isShowCallback': false, 'isShowMissedCallNotification': true, 'ringtonePath': 'system_ringtone_default', 'backgroundColor': '#0955fa', 'backgroundUrl': 'https://i.pravatar.cc/500', 'actionColor': '#4CAF50' }, 'ios': <String, dynamic>{ '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 BilltechIncomingCall.showCallkitIncoming(params);
-
显示未接来电通知
this._currentUuid = _uuid.v4(); var params = <String, dynamic>{ 'id': this._currentUuid, 'nameCaller': 'Hien Nguyen', 'handle': '0123456789', 'type': 1, 'textMissedCall': '未接来电', 'textCallback': '回拨', 'extra': <String, dynamic>{'userId': '1a2b3c4d'}, }; await BilltechIncomingCall.showMissCallNotification(params);
-
开始外呼
this._currentUuid = _uuid.v4(); var params = <String, dynamic>{ 'id': this._currentUuid, 'nameCaller': 'Hien Nguyen', 'handle': '0123456789', 'type': 1, 'extra': <String, dynamic>{'userId': '1a2b3c4d'}, 'ios': <String, dynamic>{'handleType': 'generic'} }; await BilltechIncomingCall.startCall(params);
-
结束通话
var params = <String, dynamic>{'id': this._currentUuid}; await BilltechIncomingCall.endCall(params);
-
结束所有通话
await BilltechIncomingCall.endAllCalls();
-
获取活跃通话
await BilltechIncomingCall.activeCalls();
输出:
[{"id": "8BAA2B26-47AD-42C1-9197-1D75F662DF78", ...}]
-
获取设备 VoIP 推送令牌
await BilltechIncomingCall.getDevicePushTokenVoIP();
输出:
<device token> // 示例 d6a77ca80c5f09f87f353cdd328ec8d7d34e92eb108d046c91906f27f54949cd
确保使用以下代码保存设备令牌:
func pushRegistry(_ registry: PKPushRegistry, didUpdate credentials: PKPushCredentials, for type: PKPushType) { print(credentials.token) let deviceToken = credentials.token.map { String(format: "%02x", $0) }.joined() // 保存设备令牌到服务器 SwiftFlutterCallkitIncomingPlugin.sharedInstance?.setDevicePushTokenVoIP(deviceToken) } func pushRegistry(_ registry: PKPushRegistry, didInvalidatePushTokenFor type: PKPushType) { print("didInvalidatePushTokenFor") SwiftFlutterCallkitIncomingPlugin.sharedInstance?.setDevicePushTokenVoIP("") }
-
监听事件
BilltechIncomingCall.onEvent.listen((event) { switch (event!.name) { case CallEvent.ACTION_CALL_INCOMING: // TODO: 收到来电 break; case CallEvent.ACTION_CALL_START: // TODO: 开始外呼 // TODO: 显示呼叫屏幕 break; case CallEvent.ACTION_CALL_ACCEPT: // TODO: 接听来电 // TODO: 显示呼叫屏幕 break; case CallEvent.ACTION_CALL_DECLINE: // TODO: 拒接来电 break; case CallEvent.ACTION_CALL_ENDED: // TODO: 结束通话 break; case CallEvent.ACTION_CALL_TIMEOUT: // TODO: 未接来电 break; case CallEvent.ACTION_CALL_CALLBACK: // TODO: 仅 Android - 点击未接来电通知中的“回拨” break; case CallEvent.ACTION_CALL_TOGGLE_HOLD: // TODO: 仅 iOS break; case CallEvent.ACTION_CALL_TOGGLE_MUTE: // TODO: 仅 iOS break; case CallEvent.ACTION_CALL_TOGGLE_DMTF: // TODO: 仅 iOS break; case CallEvent.ACTION_CALL_TOGGLE_GROUP: // TODO: 仅 iOS break; case CallEvent.ACTION_CALL_TOGGLE_AUDIO_SESSION: // TODO: 仅 iOS break; case CallEvent.ACTION_DID_UPDATE_DEVICE_PUSH_TOKEN_VOIP: // TODO: 仅 iOS break; } });
-
从原生代码调用(iOS PushKit)
// Swift 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)
// 或者 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)
// Objective-C #if __has_include(<flutter_callkit_incoming/flutter_callkit_incoming-Swift.h>) #import <flutter_callkit_incoming/flutter_callkit_incoming-Swift.h> #else #import "flutter_callkit_incoming-Swift.h" #endif Data * data = [[Data alloc]initWithId:@"44d915e1-5ff4-4bed-bf13-c423048ec97a" nameCaller:@"Hien Nguyen" handle:@"0123456789" type:1]; [data setNameCaller:@"Johnny"]; [data setExtra:@{ @"userId" : @"HelloXXXX", @"key2" : @"value2"}]; //... 设置更多数据 [SwiftFlutterCallkitIncomingPlugin.sharedInstance showCallkitIncoming:data fromPushKit:YES];
// 发送自定义事件 SwiftFlutterCallkitIncomingPlugin.sharedInstance?.sendEventCustom("customEvent", body: ["customKey": "customValue"])
属性
Android 数据
Prop | Description | Default |
---|---|---|
id |
每个通话的 UUID 标识符。建议使用 uuid 包生成唯一 UUID |
必须 |
nameCaller |
呼叫者的名称 | 无 |
appName |
应用名。iOS 上使用 | 应用名,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 数据属性
Prop | Description | Default |
---|---|---|
isCustomNotification |
使用自定义通知 | 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 |
iOS 数据属性
Prop | Description | Default |
---|---|---|
iconName |
应用图标。iOS 上使用 | 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 使用手机默认铃声 |
示例代码
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:flutter_callkit_incoming_example/app_router.dart';
import 'package:flutter_callkit_incoming_example/navigation_service.dart';
import 'dart:async';
import 'dart:convert';
import 'package:uuid/uuid.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:billtech_incoming_call/billtech_incoming_call.dart';
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
print("Handling a background message: ${message.messageId}");
showCallkitIncoming(Uuid().v4());
}
Future<void> showCallkitIncoming(String uuid) async {
var params = <String, dynamic>{
'id': uuid,
'nameCaller': 'Hien Nguyen',
'appName': 'Callkit',
'avatar': 'https://i.pravatar.cc/100',
'handle': '0123456789',
'type': 0,
'duration': 30000,
'textAccept': '接受',
'textDecline': '拒绝',
'textMissedCall': '未接来电',
'textCallback': '回拨',
'extra': <String, dynamic>{'userId': '1a2b3c4d'},
'headers': <String, dynamic>{'apiKey': 'Abc@123!', 'platform': 'flutter'},
'android': <String, dynamic>{
'isCustomNotification': true,
'isShowLogo': false,
'isShowCallback': false,
'ringtonePath': 'system_ringtone_default',
'backgroundColor': '#0955fa',
'backgroundUrl': 'https://i.pravatar.cc/500',
'actionColor': '#4CAF50'
},
'ios': <String, dynamic>{
'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 BilltechIncomingCall.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 {
var _uuid;
var _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 BilltechIncomingCall.activeCalls();
if (calls is List) {
if (calls.isNotEmpty) {
print('DATA: $calls');
this._currentUuid = calls[0]['id'];
return calls[0];
} else {
this._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}');
this._currentUuid = _uuid.v4();
showCallkitIncoming(this._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 BilltechIncomingCall.getDevicePushTokenVoIP();
print(devicePushTokenVoIP);
}
}
更多关于Flutter来电管理插件billtech_incoming_call的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter来电管理插件billtech_incoming_call的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,以下是一个关于如何使用 billtech_incoming_call
插件的 Flutter 代码示例。这个插件通常用于在 Flutter 应用中管理来电显示和处理。请注意,这个插件可能需要一些特定的权限配置,并且在实际设备上进行测试时,确保你遵循了相关的隐私和安全准则。
首先,你需要在 pubspec.yaml
文件中添加这个插件的依赖:
dependencies:
flutter:
sdk: flutter
billtech_incoming_call: ^最新版本号 # 请替换为实际可用的最新版本号
然后,运行 flutter pub get
命令来获取依赖。
接下来,在你的 Flutter 应用中,你可以按照以下步骤来使用 billtech_incoming_call
插件:
- 配置 Android 权限:
在 android/app/src/main/AndroidManifest.xml
文件中添加必要的权限,例如:
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
注意:这些权限可能需要根据实际需求进行调整,并且某些权限(如 SYSTEM_ALERT_WINDOW
)可能需要在运行时请求用户授权。
- 初始化插件并处理来电:
在你的 Flutter 应用中,初始化插件并设置来电处理逻辑。例如,在 main.dart
文件中:
import 'package:flutter/material.dart';
import 'package:billtech_incoming_call/billtech_incoming_call.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Incoming Call Management'),
),
body: Center(
child: Text('Waiting for an incoming call...'),
),
),
);
}
}
class _IncomingCallHandler {
static final BilltechIncomingCall _incomingCall = BilltechIncomingCall();
_IncomingCallHandler() {
// 监听来电事件
_incomingCall.setIncomingCallListener((callData) {
print('Incoming call from: ${callData.phoneNumber}');
showIncomingCallUI(callData);
});
// 监听挂断事件
_incomingCall.setEndCallListener((callData) {
print('Call ended with: ${callData.phoneNumber}');
hideIncomingCallUI();
});
// 初始化插件(如果需要)
_incomingCall.initialize();
}
void showIncomingCallUI(CallData callData) {
// 显示自定义的来电界面
// 这里可以使用 Overlay 或其他 Flutter 组件来显示来电界面
// 注意:这只是一个示例,实际实现可能需要更多的UI和逻辑处理
print('Displaying incoming call UI for ${callData.phoneNumber}');
}
void hideIncomingCallUI() {
// 隐藏来电界面
print('Hiding incoming call UI');
}
}
// 在应用启动时初始化来电处理器
void initStateHook() {
_IncomingCallHandler();
}
// 在 Flutter 应用的入口点调用 initStateHook 函数
@override
void initState() {
super.initState();
initStateHook();
}
注意:上面的代码示例中有几个关键点需要注意:
CallData
是一个假设的类,用于存储来电信息(如电话号码)。实际使用时,你需要根据插件提供的API来确定正确的数据结构。showIncomingCallUI
和hideIncomingCallUI
方法只是占位符,你需要根据实际需求来实现来电界面的显示和隐藏。initStateHook
和initState
的部分是为了在应用启动时初始化来电处理器。在 Flutter 中,通常你会在StatefulWidget
的initState
方法中执行这样的初始化操作。但在这个示例中,为了简化,我直接在main.dart
中调用了它。在实际应用中,你应该将其放在合适的位置。
最后,请务必查阅 billtech_incoming_call
插件的官方文档,以获取最新的API信息和最佳实践指南。