Flutter来电计时管理插件flutter_callkit_incoming_timer的使用
Flutter来电计时管理插件flutter_callkit_incoming的使用
感谢Hien Nguyen和magbdev。此包从版本1.0.3+3的flutter_callkit_incoming分叉而来,并在Pull Request #246中添加了新的功能开始计时。
一个用于在Flutter应用中显示来电的插件(自定义Android界面/Callkit for iOS)。
⭐ 特性
- 显示来电
- 开始拨出电话
- 自定义Android界面/Callkit for iOS
- 使用Pushkit/VoIP for 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: '接受',
textDecline: '拒绝',
textMissedCall: '未接来电',
textCallback: '回拨',
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: "来电",
missedCallNotificationChannelName: "未接来电"),
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: '未接来电',
textCallback: '回拨',
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>
// 示例
d6a77ca80c5f09f87f353cdd328ec8d7d34e92eb108d046c91906f27f54949cd
确保在AppDelegate.swift
中使用SwiftFlutterCallkitIncomingPlugin.sharedInstance?.setDevicePushTokenVoIP(deviceToken)
:
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("")
}
监听事件
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(...)
// 或者
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>)
属性
Prop | Description | Default |
---|---|---|
id |
每个通话的UUID标识符。UUID应为每个通话唯一,并且当通话结束时,相同的UUID用于该通话。建议使用uuid。仅接受UUID。 | Required |
nameCaller |
来电者的名称。 | None |
appName |
应用名称。用于在Callkit(iOS)中显示。 | App Name, Deprecated for iOS > 14, default using App name |
avatar |
用于Android显示的头像URL。 | None |
handle |
电话号码/邮箱/其他。 | None |
type |
0 - 音频通话, 1 - 视频通话 | 0 |
duration |
来电/拨出通话显示时间(秒)。如果时间超时,通话将被错过。 | 30000 |
textAccept |
Android中使用的“接受”文本。 | Accept |
textDecline |
Android中使用的“拒绝”文本。 | Decline |
textMissedCall |
Android中使用的“未接来电”文本(显示在未接来电通知中)。 | Missed Call |
textCallback |
Android中使用的“回拨”文本(显示在未接来电通知中)。 | Call back |
extra |
添加到事件中的任何数据。 | {} |
headers |
用于自定义头像/背景图像的任何数据。 | {} |
android |
需要自定义UI的Android数据。 | Below |
ios |
需要的iOS数据。 | Below |
Android
Prop | Description | Default |
---|---|---|
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”。 | None |
actionColor |
通知中使用的按钮/文本颜色。 | #4CAF50 |
incomingCallNotificationChannelName |
来电的通知通道名称。 | Incoming call |
missedCallNotificationChannelName |
未接来电的通知通道名称。 | Missed call |
iOS
Prop | Description | Default |
---|---|---|
iconName |
应用图标。用于在Callkit(iOS)中显示。 | CallKitLogo 使用从 Images.xcassets/CallKitLogo |
handleType |
呼叫类型处理 <code>generic</code> , <code>number</code> , <code>email</code> |
generic |
supportsVideo |
支持视频 | true |
maximumCallGroups |
最大通话组数 | 2 |
maximumCallsPerCallGroup |
每个通话组的最大通话数 | 1 |
audioSessionMode |
音频会话模式 <em>None</em> , <code>gameChat</code> , <code>measurement</code> , <code>moviePlayback</code> , <code>spokenAudio</code> , <code>videoChat</code> , <code>videoRecording</code> , <code>voiceChat</code> , <code>voicePrompt</code> |
|
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 使用手机默认铃声 |
源代码
请查看GitHub仓库
- https://github.com/hiennguyen92/flutter_callkit_incoming
- https://github.com/hiennguyen92/flutter_callkit_incoming/blob/master/example/lib/main.dart
Pushkit - 收到VoIP并从终止状态唤醒应用(仅限iOS)
请检查PUSHKIT.md设置iOS的Pushkit。
待办事项
- 运行后台
- 简化安装过程
💡 示例
1. 示例说明图
表格
iOS(锁屏) | iOS(全屏) | iOS(警报) |
---|---|---|
![]() |
![]() |
![]() |
iOS(锁屏)-音频 | iOS(警报)-音频 | iOS(锁屏)-视频 |
![]() |
![]() |
![]() |
iOS(警报)-视频 | isCustomNotification: false | |
![]() |
![]() |
示例代码
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: '接受',
textDecline: '拒绝',
textMissedCall: '未接来电',
textCallback: '回拨',
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_timer的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter来电计时管理插件flutter_callkit_incoming_timer的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
flutter_callkit_incoming_timer
是一个用于在 Flutter 应用中实现来电计时功能的插件。它通常与 flutter_callkit_incoming
插件结合使用,以在接收到来电时显示一个计时器,记录通话的持续时间。
安装插件
首先,你需要在 pubspec.yaml
文件中添加 flutter_callkit_incoming_timer
插件的依赖:
dependencies:
flutter:
sdk: flutter
flutter_callkit_incoming_timer: ^1.0.0 # 请确保使用最新版本
然后运行 flutter pub get
来安装插件。
基本用法
-
初始化插件
在你的 Flutter 应用中初始化flutter_callkit_incoming_timer
插件。import 'package:flutter_callkit_incoming_timer/flutter_callkit_incoming_timer.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { [@override](/user/override) Widget build(BuildContext context) { return MaterialApp( home: HomeScreen(), ); } } class HomeScreen extends StatefulWidget { [@override](/user/override) _HomeScreenState createState() => _HomeScreenState(); } class _HomeScreenState extends State<HomeScreen> { [@override](/user/override) void initState() { super.initState(); FlutterCallkitIncomingTimer.init(); } [@override](/user/override) Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Call Timer Example'), ), body: Center( child: Text('Call Timer Example'), ), ); } }
-
启动计时器
当接收到来电时,启动计时器。你可以使用flutter_callkit_incoming
插件来接收来电事件。FlutterCallkitIncoming.onEvent.listen((CallEvent event) async { if (event == CallEvent.ACTION_CALL_START) { // 开始计时 FlutterCallkitIncomingTimer.startTimer(); } else if (event == CallEvent.ACTION_CALL_END) { // 停止计时 FlutterCallkitIncomingTimer.stopTimer(); } });
-
获取计时器值
你可以通过监听计时器的值来获取通话的持续时间。FlutterCallkitIncomingTimer.onTimerChanged.listen((Duration duration) { print('Call duration: $duration'); });
-
显示计时器
你可以在 UI 中显示计时器的值。例如,你可以在一个Text
组件中显示通话的持续时间。class _HomeScreenState extends State<HomeScreen> { Duration _callDuration = Duration.zero; [@override](/user/override) void initState() { super.initState(); FlutterCallkitIncomingTimer.init(); FlutterCallkitIncomingTimer.onTimerChanged.listen((Duration duration) { setState(() { _callDuration = duration; }); }); } [@override](/user/override) Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Call Timer Example'), ), body: Center( child: Text('Call Duration: $_callDuration'), ), ); } }
注意事项
flutter_callkit_incoming_timer
插件通常与flutter_callkit_incoming
插件一起使用,以确保在接收到来电时能够正确启动和停止计时器。- 确保在适当的时机启动和停止计时器,以避免计时器在不必要的情况下继续运行。
- 你可能需要根据你的应用需求自定义计时器的显示方式和逻辑。
示例代码
以下是一个完整的示例代码,展示了如何使用 flutter_callkit_incoming_timer
插件来显示通话的持续时间:
import 'package:flutter/material.dart';
import 'package:flutter_callkit_incoming/flutter_callkit_incoming.dart';
import 'package:flutter_callkit_incoming_timer/flutter_callkit_incoming_timer.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
[@override](/user/override)
Widget build(BuildContext context) {
return MaterialApp(
home: HomeScreen(),
);
}
}
class HomeScreen extends StatefulWidget {
[@override](/user/override)
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
Duration _callDuration = Duration.zero;
[@override](/user/override)
void initState() {
super.initState();
FlutterCallkitIncomingTimer.init();
FlutterCallkitIncomingTimer.onTimerChanged.listen((Duration duration) {
setState(() {
_callDuration = duration;
});
});
FlutterCallkitIncoming.onEvent.listen((CallEvent event) async {
if (event == CallEvent.ACTION_CALL_START) {
FlutterCallkitIncomingTimer.startTimer();
} else if (event == CallEvent.ACTION_CALL_END) {
FlutterCallkitIncomingTimer.stopTimer();
}
});
}
[@override](/user/override)
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Call Timer Example'),
),
body: Center(
child: Text('Call Duration: $_callDuration'),
),
);
}
}