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

1 回复

更多关于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中进行一些配置。

  1. 打开ios/Runner/Info.plist文件,添加必要的权限请求,例如麦克风和相机权限(如果你的应用需要这些权限)。

  2. 在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);
}

注意事项

  1. 权限处理:确保你的应用已经请求并获得了必要的权限,如麦克风和相机权限。
  2. 事件监听:在实际应用中,你需要监听用户的接听或拒绝操作,并相应地处理通话逻辑。
  3. 结束通话:在通话结束后,确保调用FlutterCallkitIncomingFixCustom.provider.endCall方法来清理CallKit状态。

这个示例提供了一个基本的框架,你可以根据自己的需求进一步定制和扩展来电界面的功能和样式。

回到顶部