Flutter来电管理插件billtech_incoming_call的使用

Flutter 来电管理插件 billtech_incoming_call 的使用

概述

这是一个用于在 Flutter 应用中显示来电的插件(适用于 Android 和 Callkit for iOS)。

pub package

Build Status

特性

  • 显示来电
  • 开始外呼
  • 自定义 Android UI / Callkit for iOS
  • 使用 Pushkit/VoIP 作为 iOS 示例

iOS 注意事项

仅在真实设备上运行,模拟器不支持(Callkit 框架在模拟器上无法工作)

安装

  1. 安装包

    flutter pub add billtech_incoming_call
    
  2. 添加到 pubspec.yaml

    dependencies:
      billtech_incoming_call: any
    
  3. 配置项目

    • AndroidAndroidManifest.xml 中添加以下权限:

      <uses-permission android:name="android.permission.INTERNET"/>
      
    • iOSInfo.plist 中添加以下配置:

      <key>UIBackgroundModes</key>
      <array>
        <string>processing</string>
        <string>remote-notification</string>
        <string>voip</string>
      </array>
      

使用方法

  1. 导入插件

    import 'package:billtech_incoming_call/billtech_incoming_call.dart';
    
  2. 接收来电

    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);
    
  3. 显示未接来电通知

    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);
    
  4. 开始外呼

    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);
    
  5. 结束通话

    var params = <String, dynamic>{'id': this._currentUuid};
    await BilltechIncomingCall.endCall(params);
    
  6. 结束所有通话

    await BilltechIncomingCall.endAllCalls();
    
  7. 获取活跃通话

    await BilltechIncomingCall.activeCalls();
    

    输出:

    [{"id": "8BAA2B26-47AD-42C1-9197-1D75F662DF78", ...}]
    
  8. 获取设备 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("")
    }
    
  9. 监听事件

    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;
      }
    });
    
  10. 从原生代码调用(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

1 回复

更多关于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 插件:

  1. 配置 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)可能需要在运行时请求用户授权。

  1. 初始化插件并处理来电

在你的 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来确定正确的数据结构。
  • showIncomingCallUIhideIncomingCallUI 方法只是占位符,你需要根据实际需求来实现来电界面的显示和隐藏。
  • initStateHookinitState 的部分是为了在应用启动时初始化来电处理器。在 Flutter 中,通常你会在 StatefulWidgetinitState 方法中执行这样的初始化操作。但在这个示例中,为了简化,我直接在 main.dart 中调用了它。在实际应用中,你应该将其放在合适的位置。

最后,请务必查阅 billtech_incoming_call 插件的官方文档,以获取最新的API信息和最佳实践指南。

回到顶部