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)。

pub package

Build Status

Buy Me A Coffee

⭐ 特性

  • 显示来电
  • 开始拨出电话
  • 自定义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仓库

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

1 回复

更多关于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 来安装插件。

基本用法

  1. 初始化插件
    在你的 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'),
          ),
        );
      }
    }
    
  2. 启动计时器
    当接收到来电时,启动计时器。你可以使用 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();
      }
    });
    
  3. 获取计时器值
    你可以通过监听计时器的值来获取通话的持续时间。

    FlutterCallkitIncomingTimer.onTimerChanged.listen((Duration duration) {
      print('Call duration: $duration');
    });
    
  4. 显示计时器
    你可以在 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'),
      ),
    );
  }
}
回到顶部