22 changed files with 1683 additions and 1345 deletions
@ -0,0 +1,9 @@ |
|||
import 'package:get/get.dart'; |
|||
import '../controllers/ble_controller.dart'; |
|||
|
|||
class BleBinding extends Bindings { |
|||
@override |
|||
void dependencies() { |
|||
Get.put<BleController>(BleController()); |
|||
} |
|||
} |
|||
@ -0,0 +1,281 @@ |
|||
import 'package:flutter/material.dart'; |
|||
import 'package:get/get.dart'; |
|||
import 'package:flutter_blue_plus/flutter_blue_plus.dart' as fbp; |
|||
import 'package:permission_handler/permission_handler.dart'; |
|||
import 'package:app_settings/app_settings.dart'; |
|||
|
|||
class BleController extends GetxController { |
|||
// 扫描状态 |
|||
final RxBool isScanning = false.obs; |
|||
final RxBool isBluetoothOn = false.obs; |
|||
final RxBool hasPermissions = false.obs; |
|||
|
|||
// 发现的设备列表 |
|||
final RxList<fbp.ScanResult> scanResults = <fbp.ScanResult>[].obs; |
|||
|
|||
// 连接的设备 |
|||
final Rx<fbp.BluetoothDevice?> connectedDevice = |
|||
Rx<fbp.BluetoothDevice?>(null); |
|||
|
|||
// 错误信息 |
|||
final RxString errorMessage = ''.obs; |
|||
|
|||
@override |
|||
void onInit() { |
|||
super.onInit(); |
|||
_initBluetooth(); |
|||
} |
|||
|
|||
@override |
|||
void onClose() { |
|||
stopScan(); |
|||
super.onClose(); |
|||
} |
|||
|
|||
// 初始化蓝牙 |
|||
Future<void> _initBluetooth() async { |
|||
try { |
|||
// 先检查权限 |
|||
await _checkPermissions(); |
|||
|
|||
// 检查蓝牙适配器状态 |
|||
fbp.FlutterBluePlus.adapterState.listen((state) { |
|||
isBluetoothOn.value = state == fbp.BluetoothAdapterState.on; |
|||
if (state == fbp.BluetoothAdapterState.on) { |
|||
errorMessage.value = ''; |
|||
} else { |
|||
errorMessage.value = '请开启蓝牙'; |
|||
stopScan(); |
|||
} |
|||
}); |
|||
|
|||
// 监听扫描结果 |
|||
fbp.FlutterBluePlus.scanResults.listen((results) { |
|||
scanResults.value = results; |
|||
}); |
|||
|
|||
// 监听扫描状态 |
|||
fbp.FlutterBluePlus.isScanning.listen((scanning) { |
|||
isScanning.value = scanning; |
|||
}); |
|||
} catch (e) { |
|||
errorMessage.value = '蓝牙初始化失败: $e'; |
|||
} |
|||
} |
|||
|
|||
// 检查权限(不请求) |
|||
Future<void> _checkPermissions() async { |
|||
try { |
|||
Map<Permission, PermissionStatus> statuses = await [ |
|||
Permission.bluetoothScan, |
|||
Permission.bluetoothConnect, |
|||
Permission.location, |
|||
].request(); |
|||
|
|||
hasPermissions.value = |
|||
statuses.values.every((status) => status == PermissionStatus.granted); |
|||
|
|||
if (!hasPermissions.value) { |
|||
errorMessage.value = '需要蓝牙和位置权限才能扫描设备'; |
|||
} |
|||
} catch (e) { |
|||
print('检查权限失败: $e'); |
|||
hasPermissions.value = false; |
|||
} |
|||
} |
|||
|
|||
// 请求权限 |
|||
Future<bool> _requestPermissions() async { |
|||
try { |
|||
Map<Permission, PermissionStatus> statuses = await [ |
|||
Permission.bluetoothScan, |
|||
Permission.bluetoothConnect, |
|||
Permission.location, |
|||
].request(); |
|||
|
|||
bool allGranted = |
|||
statuses.values.every((status) => status == PermissionStatus.granted); |
|||
hasPermissions.value = allGranted; |
|||
|
|||
if (!allGranted) { |
|||
// 检查是否有权限被永久拒绝 |
|||
bool hasPermanentlyDenied = statuses.values |
|||
.any((status) => status == PermissionStatus.permanentlyDenied); |
|||
|
|||
if (hasPermanentlyDenied) { |
|||
_showPermissionDialog(); |
|||
} else { |
|||
errorMessage.value = '需要蓝牙和位置权限才能扫描设备'; |
|||
} |
|||
} |
|||
|
|||
return allGranted; |
|||
} catch (e) { |
|||
print('请求权限失败: $e'); |
|||
errorMessage.value = '权限请求失败: $e'; |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
// 显示权限对话框 |
|||
void _showPermissionDialog() { |
|||
Get.dialog( |
|||
AlertDialog( |
|||
title: Text('需要权限'), |
|||
content: Text('应用需要蓝牙和位置权限才能扫描设备。请在设置中手动开启权限。'), |
|||
actions: [ |
|||
TextButton( |
|||
onPressed: () => Get.back(), |
|||
child: Text('取消'), |
|||
), |
|||
TextButton( |
|||
onPressed: () { |
|||
Get.back(); |
|||
AppSettings.openAppSettings(); |
|||
}, |
|||
child: Text('去设置'), |
|||
), |
|||
], |
|||
), |
|||
); |
|||
} |
|||
|
|||
// 显示蓝牙开启对话框 |
|||
void _showBluetoothDialog() { |
|||
Get.dialog( |
|||
AlertDialog( |
|||
title: Text('需要开启蓝牙'), |
|||
content: Text('请开启蓝牙以扫描和连接设备。'), |
|||
actions: [ |
|||
TextButton( |
|||
onPressed: () => Get.back(), |
|||
child: Text('取消'), |
|||
), |
|||
TextButton( |
|||
onPressed: () { |
|||
Get.back(); |
|||
// 尝试开启蓝牙 |
|||
fbp.FlutterBluePlus.turnOn(); |
|||
}, |
|||
child: Text('开启蓝牙'), |
|||
), |
|||
], |
|||
), |
|||
); |
|||
} |
|||
|
|||
// 开始扫描 |
|||
Future<void> startScan() async { |
|||
try { |
|||
errorMessage.value = ''; |
|||
|
|||
// 检查权限 |
|||
if (!hasPermissions.value) { |
|||
if (!await _requestPermissions()) { |
|||
return; |
|||
} |
|||
} |
|||
|
|||
// 检查蓝牙状态 |
|||
if (!isBluetoothOn.value) { |
|||
_showBluetoothDialog(); |
|||
return; |
|||
} |
|||
|
|||
// 清空之前的结果 |
|||
scanResults.clear(); |
|||
|
|||
// 开始扫描 |
|||
await fbp.FlutterBluePlus.startScan( |
|||
timeout: const Duration(seconds: 30), |
|||
androidUsesFineLocation: true, |
|||
); |
|||
} catch (e) { |
|||
errorMessage.value = '扫描失败: $e'; |
|||
} |
|||
} |
|||
|
|||
// 停止扫描 |
|||
Future<void> stopScan() async { |
|||
try { |
|||
await fbp.FlutterBluePlus.stopScan(); |
|||
} catch (e) { |
|||
print('停止扫描失败: $e'); |
|||
} |
|||
} |
|||
|
|||
// 连接设备 |
|||
Future<void> connectToDevice(fbp.BluetoothDevice device) async { |
|||
try { |
|||
errorMessage.value = ''; |
|||
|
|||
// 停止扫描 |
|||
await stopScan(); |
|||
|
|||
// 连接设备 |
|||
await device.connect(timeout: const Duration(seconds: 15)); |
|||
connectedDevice.value = device; |
|||
|
|||
Get.snackbar( |
|||
'连接成功', |
|||
'已连接到 ${device.platformName.isNotEmpty ? device.platformName : '未知设备'}', |
|||
backgroundColor: Colors.green, |
|||
colorText: Colors.white, |
|||
); |
|||
} catch (e) { |
|||
errorMessage.value = '连接失败: $e'; |
|||
Get.snackbar( |
|||
'连接失败', |
|||
e.toString(), |
|||
backgroundColor: Colors.red, |
|||
colorText: Colors.white, |
|||
); |
|||
} |
|||
} |
|||
|
|||
// 断开连接 |
|||
Future<void> disconnectDevice() async { |
|||
try { |
|||
if (connectedDevice.value != null) { |
|||
await connectedDevice.value!.disconnect(); |
|||
connectedDevice.value = null; |
|||
|
|||
Get.snackbar( |
|||
'已断开连接', |
|||
'设备已断开连接', |
|||
backgroundColor: Colors.orange, |
|||
colorText: Colors.white, |
|||
); |
|||
} |
|||
} catch (e) { |
|||
errorMessage.value = '断开连接失败: $e'; |
|||
} |
|||
} |
|||
|
|||
// 获取设备名称 |
|||
String getDeviceName(fbp.ScanResult result) { |
|||
if (result.device.platformName.isNotEmpty) { |
|||
return result.device.platformName; |
|||
} |
|||
if (result.advertisementData.localName.isNotEmpty) { |
|||
return result.advertisementData.localName; |
|||
} |
|||
return '未知设备'; |
|||
} |
|||
|
|||
// 获取信号强度描述 |
|||
String getSignalStrengthText(int rssi) { |
|||
if (rssi >= -50) return '信号强'; |
|||
if (rssi >= -70) return '信号中等'; |
|||
if (rssi >= -90) return '信号弱'; |
|||
return '信号很弱'; |
|||
} |
|||
|
|||
// 获取信号强度图标 |
|||
IconData getSignalStrengthIcon(int rssi) { |
|||
if (rssi >= -50) return Icons.signal_wifi_4_bar; |
|||
if (rssi >= -70) return Icons.signal_wifi_4_bar; |
|||
if (rssi >= -90) return Icons.signal_wifi_4_bar; |
|||
return Icons.signal_wifi_4_bar; |
|||
} |
|||
} |
|||
@ -0,0 +1,355 @@ |
|||
import 'package:flutter/material.dart'; |
|||
import 'package:get/get.dart'; |
|||
import 'package:flutter_screenutil/flutter_screenutil.dart'; |
|||
import 'package:flutter_blue_plus/flutter_blue_plus.dart' as fbp; |
|||
import '../controllers/ble_controller.dart'; |
|||
|
|||
class BleView extends GetView<BleController> { |
|||
const BleView({Key? key}) : super(key: key); |
|||
|
|||
@override |
|||
Widget build(BuildContext context) { |
|||
final bool isDarkMode = Theme.of(context).brightness == Brightness.dark; |
|||
|
|||
return Scaffold( |
|||
backgroundColor: isDarkMode ? Colors.black : Colors.white, |
|||
appBar: AppBar( |
|||
title: Text( |
|||
'BLE设备扫描', |
|||
style: TextStyle( |
|||
color: isDarkMode ? Colors.white : Colors.black, |
|||
fontSize: 18.sp, |
|||
fontWeight: FontWeight.bold, |
|||
), |
|||
), |
|||
backgroundColor: isDarkMode ? Colors.grey[900] : Colors.white, |
|||
elevation: 0, |
|||
iconTheme: IconThemeData( |
|||
color: isDarkMode ? Colors.white : Colors.black, |
|||
), |
|||
actions: [ |
|||
Obx(() => IconButton( |
|||
onPressed: controller.isScanning.value |
|||
? controller.stopScan |
|||
: controller.startScan, |
|||
icon: Icon( |
|||
controller.isScanning.value ? Icons.stop : Icons.refresh, |
|||
color: controller.isScanning.value |
|||
? Colors.red |
|||
: (isDarkMode ? Colors.white : Colors.black), |
|||
), |
|||
)), |
|||
], |
|||
), |
|||
body: SafeArea( |
|||
child: Column( |
|||
children: [ |
|||
// 状态栏 |
|||
_buildStatusBar(isDarkMode), |
|||
|
|||
// 连接的设备信息 |
|||
_buildConnectedDevice(isDarkMode), |
|||
|
|||
// 设备列表 |
|||
Expanded( |
|||
child: _buildDeviceList(isDarkMode), |
|||
), |
|||
], |
|||
), |
|||
), |
|||
floatingActionButton: Obx(() => FloatingActionButton( |
|||
onPressed: controller.isScanning.value |
|||
? controller.stopScan |
|||
: controller.startScan, |
|||
backgroundColor: |
|||
controller.isScanning.value ? Colors.red : Colors.blue, |
|||
child: Icon( |
|||
controller.isScanning.value |
|||
? Icons.stop |
|||
: Icons.bluetooth_searching, |
|||
color: Colors.white, |
|||
), |
|||
)), |
|||
); |
|||
} |
|||
|
|||
// 状态栏 |
|||
Widget _buildStatusBar(bool isDarkMode) { |
|||
return Obx(() => Container( |
|||
width: double.infinity, |
|||
padding: EdgeInsets.all(16.w), |
|||
margin: EdgeInsets.all(16.w), |
|||
decoration: BoxDecoration( |
|||
color: isDarkMode ? Colors.grey[800] : Colors.grey[100], |
|||
borderRadius: BorderRadius.circular(12.r), |
|||
), |
|||
child: Column( |
|||
children: [ |
|||
Row( |
|||
children: [ |
|||
Icon( |
|||
controller.isBluetoothOn.value |
|||
? Icons.bluetooth |
|||
: Icons.bluetooth_disabled, |
|||
color: controller.isBluetoothOn.value |
|||
? Colors.blue |
|||
: Colors.red, |
|||
size: 24.w, |
|||
), |
|||
SizedBox(width: 8.w), |
|||
Text( |
|||
controller.isBluetoothOn.value ? '蓝牙已开启' : '蓝牙已关闭', |
|||
style: TextStyle( |
|||
color: isDarkMode ? Colors.white : Colors.black, |
|||
fontSize: 16.sp, |
|||
fontWeight: FontWeight.w500, |
|||
), |
|||
), |
|||
Spacer(), |
|||
if (controller.isScanning.value) |
|||
SizedBox( |
|||
width: 20.w, |
|||
height: 20.w, |
|||
child: CircularProgressIndicator( |
|||
strokeWidth: 2, |
|||
valueColor: AlwaysStoppedAnimation<Color>(Colors.blue), |
|||
), |
|||
), |
|||
], |
|||
), |
|||
if (controller.errorMessage.value.isNotEmpty) ...[ |
|||
SizedBox(height: 8.h), |
|||
Text( |
|||
controller.errorMessage.value, |
|||
style: TextStyle( |
|||
color: Colors.red, |
|||
fontSize: 14.sp, |
|||
), |
|||
), |
|||
], |
|||
if (controller.isScanning.value) ...[ |
|||
SizedBox(height: 8.h), |
|||
Text( |
|||
'正在扫描设备...', |
|||
style: TextStyle( |
|||
color: Colors.blue, |
|||
fontSize: 14.sp, |
|||
), |
|||
), |
|||
], |
|||
], |
|||
), |
|||
)); |
|||
} |
|||
|
|||
// 已连接设备信息 |
|||
Widget _buildConnectedDevice(bool isDarkMode) { |
|||
return Obx(() { |
|||
if (controller.connectedDevice.value == null) { |
|||
return SizedBox.shrink(); |
|||
} |
|||
|
|||
final device = controller.connectedDevice.value!; |
|||
return Container( |
|||
width: double.infinity, |
|||
padding: EdgeInsets.all(16.w), |
|||
margin: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.h), |
|||
decoration: BoxDecoration( |
|||
color: Colors.green.withOpacity(0.1), |
|||
border: Border.all(color: Colors.green, width: 1), |
|||
borderRadius: BorderRadius.circular(12.r), |
|||
), |
|||
child: Row( |
|||
children: [ |
|||
Icon( |
|||
Icons.bluetooth_connected, |
|||
color: Colors.green, |
|||
size: 24.w, |
|||
), |
|||
SizedBox(width: 12.w), |
|||
Expanded( |
|||
child: Column( |
|||
crossAxisAlignment: CrossAxisAlignment.start, |
|||
children: [ |
|||
Text( |
|||
'已连接设备', |
|||
style: TextStyle( |
|||
color: Colors.green, |
|||
fontSize: 12.sp, |
|||
fontWeight: FontWeight.w500, |
|||
), |
|||
), |
|||
Text( |
|||
device.platformName.isNotEmpty |
|||
? device.platformName |
|||
: '未知设备', |
|||
style: TextStyle( |
|||
color: isDarkMode ? Colors.white : Colors.black, |
|||
fontSize: 16.sp, |
|||
fontWeight: FontWeight.bold, |
|||
), |
|||
), |
|||
Text( |
|||
device.remoteId.toString(), |
|||
style: TextStyle( |
|||
color: isDarkMode ? Colors.white70 : Colors.black54, |
|||
fontSize: 12.sp, |
|||
), |
|||
), |
|||
], |
|||
), |
|||
), |
|||
IconButton( |
|||
onPressed: controller.disconnectDevice, |
|||
icon: Icon( |
|||
Icons.close, |
|||
color: Colors.red, |
|||
size: 20.w, |
|||
), |
|||
), |
|||
], |
|||
), |
|||
); |
|||
}); |
|||
} |
|||
|
|||
// 设备列表 |
|||
Widget _buildDeviceList(bool isDarkMode) { |
|||
return Obx(() { |
|||
if (controller.scanResults.isEmpty) { |
|||
return Center( |
|||
child: Column( |
|||
mainAxisAlignment: MainAxisAlignment.center, |
|||
children: [ |
|||
Icon( |
|||
Icons.bluetooth_searching, |
|||
size: 64.w, |
|||
color: isDarkMode ? Colors.white54 : Colors.black54, |
|||
), |
|||
SizedBox(height: 16.h), |
|||
Text( |
|||
controller.isScanning.value ? '正在搜索设备...' : '点击扫描按钮开始搜索BLE设备', |
|||
style: TextStyle( |
|||
color: isDarkMode ? Colors.white54 : Colors.black54, |
|||
fontSize: 16.sp, |
|||
), |
|||
textAlign: TextAlign.center, |
|||
), |
|||
], |
|||
), |
|||
); |
|||
} |
|||
|
|||
return ListView.builder( |
|||
padding: EdgeInsets.symmetric(horizontal: 16.w), |
|||
itemCount: controller.scanResults.length, |
|||
itemBuilder: (context, index) { |
|||
final result = controller.scanResults[index]; |
|||
return _buildDeviceItem(result, isDarkMode); |
|||
}, |
|||
); |
|||
}); |
|||
} |
|||
|
|||
// 设备项 |
|||
Widget _buildDeviceItem(fbp.ScanResult result, bool isDarkMode) { |
|||
final deviceName = controller.getDeviceName(result); |
|||
final isConnected = |
|||
controller.connectedDevice.value?.remoteId == result.device.remoteId; |
|||
|
|||
return Container( |
|||
margin: EdgeInsets.only(bottom: 12.h), |
|||
decoration: BoxDecoration( |
|||
color: isDarkMode ? Colors.grey[800] : Colors.white, |
|||
borderRadius: BorderRadius.circular(12.r), |
|||
border: Border.all( |
|||
color: isConnected |
|||
? Colors.green |
|||
: (isDarkMode ? Colors.grey[700]! : Colors.grey[300]!), |
|||
width: isConnected ? 2 : 1, |
|||
), |
|||
boxShadow: [ |
|||
BoxShadow( |
|||
color: Colors.black.withOpacity(0.05), |
|||
blurRadius: 8, |
|||
offset: Offset(0, 2), |
|||
), |
|||
], |
|||
), |
|||
child: ListTile( |
|||
contentPadding: EdgeInsets.all(16.w), |
|||
leading: Container( |
|||
width: 48.w, |
|||
height: 48.w, |
|||
decoration: BoxDecoration( |
|||
color: isConnected |
|||
? Colors.green.withOpacity(0.1) |
|||
: Colors.blue.withOpacity(0.1), |
|||
borderRadius: BorderRadius.circular(24.r), |
|||
), |
|||
child: Icon( |
|||
isConnected ? Icons.bluetooth_connected : Icons.bluetooth, |
|||
color: isConnected ? Colors.green : Colors.blue, |
|||
size: 24.w, |
|||
), |
|||
), |
|||
title: Text( |
|||
deviceName, |
|||
style: TextStyle( |
|||
color: isDarkMode ? Colors.white : Colors.black, |
|||
fontSize: 16.sp, |
|||
fontWeight: FontWeight.bold, |
|||
), |
|||
), |
|||
subtitle: Column( |
|||
crossAxisAlignment: CrossAxisAlignment.start, |
|||
children: [ |
|||
SizedBox(height: 4.h), |
|||
Text( |
|||
result.device.remoteId.toString(), |
|||
style: TextStyle( |
|||
color: isDarkMode ? Colors.white70 : Colors.black54, |
|||
fontSize: 12.sp, |
|||
), |
|||
), |
|||
SizedBox(height: 4.h), |
|||
Row( |
|||
children: [ |
|||
Icon( |
|||
controller.getSignalStrengthIcon(result.rssi), |
|||
size: 16.w, |
|||
color: isDarkMode ? Colors.white70 : Colors.black54, |
|||
), |
|||
SizedBox(width: 4.w), |
|||
Text( |
|||
'${result.rssi} dBm (${controller.getSignalStrengthText(result.rssi)})', |
|||
style: TextStyle( |
|||
color: isDarkMode ? Colors.white70 : Colors.black54, |
|||
fontSize: 12.sp, |
|||
), |
|||
), |
|||
], |
|||
), |
|||
], |
|||
), |
|||
trailing: isConnected |
|||
? Icon( |
|||
Icons.check_circle, |
|||
color: Colors.green, |
|||
size: 24.w, |
|||
) |
|||
: IconButton( |
|||
onPressed: () => controller.connectToDevice(result.device), |
|||
icon: Icon( |
|||
Icons.connect_without_contact, |
|||
color: Colors.blue, |
|||
size: 24.w, |
|||
), |
|||
), |
|||
onTap: isConnected |
|||
? null |
|||
: () => controller.connectToDevice(result.device), |
|||
), |
|||
); |
|||
} |
|||
} |
|||
@ -1,306 +1,306 @@ |
|||
import Foundation |
|||
import CoreBluetooth |
|||
import os.log |
|||
import AVFoundation |
|||
import AccessorySetupKit |
|||
import UIKit |
|||
// import Foundation |
|||
// import CoreBluetooth |
|||
// import os.log |
|||
// import AVFoundation |
|||
// import AccessorySetupKit |
|||
// import UIKit |
|||
|
|||
@available(iOS 18.0, *) |
|||
class BleAccessorySetup: NSObject { |
|||
// MARK: - 属性 |
|||
static let shared = BleAccessorySetup() |
|||
// @available(iOS 18.0, *) |
|||
// class BleAccessorySetup: NSObject { |
|||
// // MARK: - 属性 |
|||
// static let shared = BleAccessorySetup() |
|||
|
|||
private var bleService: BleService? |
|||
private var session: ASAccessorySession? |
|||
private var isSessionActivated = false |
|||
private var isInitializing = false |
|||
private let logger = OSLog(subsystem: "com.yunqiinnovation.ble_service", category: "BleAccessorySetup") |
|||
// private var bleService: BleService? |
|||
// private var session: ASAccessorySession? |
|||
// private var isSessionActivated = false |
|||
// private var isInitializing = false |
|||
// private let logger = OSLog(subsystem: "com.yunqiinnovation.ble_service", category: "BleAccessorySetup") |
|||
|
|||
// 杰理蓝牙设备的公司标识符 |
|||
private let deviceCompanyIdentifier: UInt16 = 0xEFAB |
|||
private let deviceDisplayName = "语音助理耳机" |
|||
// // 杰理蓝牙设备的公司标识符 |
|||
// private let deviceCompanyIdentifier: UInt16 = 0xEFAB |
|||
// private let deviceDisplayName = "语音助理耳机" |
|||
|
|||
// 用于存储配对设备UUID的UserDefaults键 |
|||
private let pairedDeviceUUIDKey = "com.yunqiinnovation.ble_service.paired_device_uuid" |
|||
// // 用于存储配对设备UUID的UserDefaults键 |
|||
// private let pairedDeviceUUIDKey = "com.yunqiinnovation.ble_service.paired_device_uuid" |
|||
|
|||
typealias DeviceDiscoveredCallback = (UUID) -> Void |
|||
private var onDeviceDiscovered: DeviceDiscoveredCallback? |
|||
// typealias DeviceDiscoveredCallback = (UUID) -> Void |
|||
// private var onDeviceDiscovered: DeviceDiscoveredCallback? |
|||
|
|||
// MARK: - 初始化 |
|||
private override init() { super.init() } |
|||
// // MARK: - 初始化 |
|||
// private override init() { super.init() } |
|||
|
|||
// MARK: - 公共方法 |
|||
func setBleService(_ service: Any) { |
|||
self.bleService = service as? BleService |
|||
} |
|||
// // MARK: - 公共方法 |
|||
// func setBleService(_ service: Any) { |
|||
// self.bleService = service as? BleService |
|||
// } |
|||
|
|||
func setDeviceDiscoveredCallback(_ callback: @escaping (UUID) -> Void) { |
|||
self.onDeviceDiscovered = callback |
|||
} |
|||
// func setDeviceDiscoveredCallback(_ callback: @escaping (UUID) -> Void) { |
|||
// self.onDeviceDiscovered = callback |
|||
// } |
|||
|
|||
// 处理发现的设备并连接 |
|||
func connectToAccessory(uuid: UUID) { |
|||
guard let service = bleService else { |
|||
return |
|||
} |
|||
// // 处理发现的设备并连接 |
|||
// func connectToAccessory(uuid: UUID) { |
|||
// guard let service = bleService else { |
|||
// return |
|||
// } |
|||
|
|||
service.connectToAccessory(uuid: uuid) |
|||
} |
|||
// service.connectToAccessory(uuid: uuid) |
|||
// } |
|||
|
|||
func addToReconnectQueue(uuid: UUID) { |
|||
guard let service = bleService else { |
|||
return |
|||
} |
|||
// func addToReconnectQueue(uuid: UUID) { |
|||
// guard let service = bleService else { |
|||
// return |
|||
// } |
|||
|
|||
service.addToReconnectQueue(uuid: uuid) |
|||
} |
|||
// service.addToReconnectQueue(uuid: uuid) |
|||
// } |
|||
|
|||
// 从视图控制器显示设备选择器 - 主要入口点 |
|||
func showDevicePickerFromViewController(_ viewController: UIViewController) { |
|||
initializeSession() |
|||
} |
|||
// // 从视图控制器显示设备选择器 - 主要入口点 |
|||
// func showDevicePickerFromViewController(_ viewController: UIViewController) { |
|||
// initializeSession() |
|||
// } |
|||
|
|||
// 清除蓝牙状态(仅用于测试或重置,不会清除系统授权) |
|||
func resetBluetoothState() { |
|||
if let service = bleService { |
|||
service.disconnect() |
|||
} |
|||
} |
|||
// // 清除蓝牙状态(仅用于测试或重置,不会清除系统授权) |
|||
// func resetBluetoothState() { |
|||
// if let service = bleService { |
|||
// service.disconnect() |
|||
// } |
|||
// } |
|||
|
|||
// MARK: - 会话管理 |
|||
func initializeSession() { |
|||
if isInitializing { |
|||
os_log("会话正在初始化中,跳过", log: logger, type: .info) |
|||
return |
|||
} |
|||
// // MARK: - 会话管理 |
|||
// func initializeSession() { |
|||
// if isInitializing { |
|||
// os_log("会话正在初始化中,跳过", log: logger, type: .info) |
|||
// return |
|||
// } |
|||
|
|||
// 如果会话已激活,检查是否有配对设备 |
|||
if session != nil && isSessionActivated { |
|||
os_log("会话已激活,检查配对设备", log: logger, type: .info) |
|||
// 会话已激活,检查是否有配对设备 |
|||
if let accessories = session?.accessories, !accessories.isEmpty { |
|||
os_log("发现 %d 个已配对设备,尝试连接", log: logger, type: .info, accessories.count) |
|||
// 有配对设备,尝试连接 |
|||
if let service = bleService { |
|||
let _ = service.getCentralManager() |
|||
// // 如果会话已激活,检查是否有配对设备 |
|||
// if session != nil && isSessionActivated { |
|||
// os_log("会话已激活,检查配对设备", log: logger, type: .info) |
|||
// // 会话已激活,检查是否有配对设备 |
|||
// if let accessories = session?.accessories, !accessories.isEmpty { |
|||
// os_log("发现 %d 个已配对设备,尝试连接", log: logger, type: .info, accessories.count) |
|||
// // 有配对设备,尝试连接 |
|||
// if let service = bleService { |
|||
// let _ = service.getCentralManager() |
|||
|
|||
for accessory in accessories { |
|||
if let uuid = accessory.bluetoothIdentifier { |
|||
onDeviceDiscovered?(uuid) |
|||
connectToAccessory(uuid: uuid) |
|||
} |
|||
} |
|||
} |
|||
} else { |
|||
os_log("没有已配对设备,显示配对框", log: logger, type: .info) |
|||
// 没有配对设备,直接显示配对框 |
|||
showDevicePicker() |
|||
} |
|||
return |
|||
} |
|||
// for accessory in accessories { |
|||
// if let uuid = accessory.bluetoothIdentifier { |
|||
// onDeviceDiscovered?(uuid) |
|||
// connectToAccessory(uuid: uuid) |
|||
// } |
|||
// } |
|||
// } |
|||
// } else { |
|||
// os_log("没有已配对设备,显示配对框", log: logger, type: .info) |
|||
// // 没有配对设备,直接显示配对框 |
|||
// showDevicePicker() |
|||
// } |
|||
// return |
|||
// } |
|||
|
|||
os_log("开始初始化新会话", log: logger, type: .info) |
|||
isInitializing = true |
|||
// os_log("开始初始化新会话", log: logger, type: .info) |
|||
// isInitializing = true |
|||
|
|||
do { |
|||
session = try ASAccessorySession() |
|||
session?.activate(on: .main) { [weak self] event in |
|||
guard let self = self else { return } |
|||
self.handleSessionEvent(event) |
|||
} |
|||
} catch { |
|||
os_log("会话初始化失败: %{public}@", log: logger, type: .error, error.localizedDescription) |
|||
handleSessionInitError(error) |
|||
} |
|||
} |
|||
// do { |
|||
// session = try ASAccessorySession() |
|||
// session?.activate(on: .main) { [weak self] event in |
|||
// guard let self = self else { return } |
|||
// self.handleSessionEvent(event) |
|||
// } |
|||
// } catch { |
|||
// os_log("会话初始化失败: %{public}@", log: logger, type: .error, error.localizedDescription) |
|||
// handleSessionInitError(error) |
|||
// } |
|||
// } |
|||
|
|||
func resetSession() { |
|||
session?.invalidate() |
|||
session = nil |
|||
isSessionActivated = false |
|||
isInitializing = false |
|||
} |
|||
// func resetSession() { |
|||
// session?.invalidate() |
|||
// session = nil |
|||
// isSessionActivated = false |
|||
// isInitializing = false |
|||
// } |
|||
|
|||
// MARK: - 设备选择器显示 |
|||
private func showDevicePicker() { |
|||
guard let session = session, isSessionActivated else { |
|||
os_log("无法显示配对框:会话未激活", log: logger, type: .error) |
|||
return |
|||
} |
|||
// // MARK: - 设备选择器显示 |
|||
// private func showDevicePicker() { |
|||
// guard let session = session, isSessionActivated else { |
|||
// os_log("无法显示配对框:会话未激活", log: logger, type: .error) |
|||
// return |
|||
// } |
|||
|
|||
os_log("准备显示设备配对框", log: logger, type: .info) |
|||
// os_log("准备显示设备配对框", log: logger, type: .info) |
|||
|
|||
let descriptor = ASDiscoveryDescriptor() |
|||
descriptor.bluetoothCompanyIdentifier = ASBluetoothCompanyIdentifier(deviceCompanyIdentifier) |
|||
// let descriptor = ASDiscoveryDescriptor() |
|||
// descriptor.bluetoothCompanyIdentifier = ASBluetoothCompanyIdentifier(deviceCompanyIdentifier) |
|||
|
|||
let deviceImage = UIImage(systemName: "headphones") ?? UIImage() |
|||
let displayItem = ASPickerDisplayItem( |
|||
name: deviceDisplayName, |
|||
productImage: deviceImage, |
|||
descriptor: descriptor |
|||
) |
|||
// let deviceImage = UIImage(systemName: "headphones") ?? UIImage() |
|||
// let displayItem = ASPickerDisplayItem( |
|||
// name: deviceDisplayName, |
|||
// productImage: deviceImage, |
|||
// descriptor: descriptor |
|||
// ) |
|||
|
|||
DispatchQueue.main.async { |
|||
os_log("正在显示设备配对框", log: self.logger, type: .info) |
|||
session.showPicker(for: [displayItem]) { [weak self] error in |
|||
guard let self = self else { return } |
|||
// DispatchQueue.main.async { |
|||
// os_log("正在显示设备配对框", log: self.logger, type: .info) |
|||
// session.showPicker(for: [displayItem]) { [weak self] error in |
|||
// guard let self = self else { return } |
|||
|
|||
if let error = error { |
|||
let nsError = error as NSError |
|||
os_log("设备选择器错误: %{public}@", log: self.logger, type: .error, error.localizedDescription) |
|||
} else { |
|||
os_log("设备选择器完成", log: self.logger, type: .info) |
|||
} |
|||
} |
|||
} |
|||
} |
|||
// if let error = error { |
|||
// let nsError = error as NSError |
|||
// os_log("设备选择器错误: %{public}@", log: self.logger, type: .error, error.localizedDescription) |
|||
// } else { |
|||
// os_log("设备选择器完成", log: self.logger, type: .info) |
|||
// } |
|||
// } |
|||
// } |
|||
// } |
|||
|
|||
// MARK: - 私有辅助方法 |
|||
private func handleSessionEvent(_ event: ASAccessoryEvent) { |
|||
switch event.eventType { |
|||
case .activated: |
|||
os_log("会话已激活", log: logger, type: .info) |
|||
handleSessionActivated() |
|||
// // MARK: - 私有辅助方法 |
|||
// private func handleSessionEvent(_ event: ASAccessoryEvent) { |
|||
// switch event.eventType { |
|||
// case .activated: |
|||
// os_log("会话已激活", log: logger, type: .info) |
|||
// handleSessionActivated() |
|||
|
|||
case .accessoryAdded: |
|||
os_log("发现新设备: %{public}@", log: logger, type: .info, event.accessory?.bluetoothIdentifier?.uuidString ?? "Unknown") |
|||
handleAccessoryAdded(event.accessory) |
|||
// case .accessoryAdded: |
|||
// os_log("发现新设备: %{public}@", log: logger, type: .info, event.accessory?.bluetoothIdentifier?.uuidString ?? "Unknown") |
|||
// handleAccessoryAdded(event.accessory) |
|||
|
|||
case .accessoryRemoved: |
|||
os_log("设备已移除: %{public}@", log: logger, type: .info, event.accessory?.bluetoothIdentifier?.uuidString ?? "Unknown") |
|||
handleAccessoryRemoved(event.accessory) |
|||
// case .accessoryRemoved: |
|||
// os_log("设备已移除: %{public}@", log: logger, type: .info, event.accessory?.bluetoothIdentifier?.uuidString ?? "Unknown") |
|||
// handleAccessoryRemoved(event.accessory) |
|||
|
|||
case .accessoryChanged: |
|||
os_log("设备状态已改变", log: logger, type: .info) |
|||
break |
|||
// case .accessoryChanged: |
|||
// os_log("设备状态已改变", log: logger, type: .info) |
|||
// break |
|||
|
|||
case .pickerDidPresent: |
|||
os_log("配对框已显示", log: logger, type: .info) |
|||
break |
|||
// case .pickerDidPresent: |
|||
// os_log("配对框已显示", log: logger, type: .info) |
|||
// break |
|||
|
|||
case .pickerDidDismiss: |
|||
os_log("配对框已关闭", log: logger, type: .info) |
|||
break |
|||
// case .pickerDidDismiss: |
|||
// os_log("配对框已关闭", log: logger, type: .info) |
|||
// break |
|||
|
|||
case .pickerSetupFailed: |
|||
os_log("选择器设置失败: %{public}@", log: logger, type: .error, |
|||
event.error?.localizedDescription ?? "未知错误") |
|||
// case .pickerSetupFailed: |
|||
// os_log("选择器设置失败: %{public}@", log: logger, type: .error, |
|||
// event.error?.localizedDescription ?? "未知错误") |
|||
|
|||
default: |
|||
os_log("未处理的会话事件: %d", log: logger, type: .info, event.eventType.rawValue) |
|||
break |
|||
} |
|||
} |
|||
// default: |
|||
// os_log("未处理的会话事件: %d", log: logger, type: .info, event.eventType.rawValue) |
|||
// break |
|||
// } |
|||
// } |
|||
|
|||
private func handleSessionActivated() { |
|||
isSessionActivated = true |
|||
isInitializing = false |
|||
// private func handleSessionActivated() { |
|||
// isSessionActivated = true |
|||
// isInitializing = false |
|||
|
|||
if let accessories = session?.accessories, !accessories.isEmpty { |
|||
if let service = bleService { |
|||
let _ = service.getCentralManager() |
|||
// if let accessories = session?.accessories, !accessories.isEmpty { |
|||
// if let service = bleService { |
|||
// let _ = service.getCentralManager() |
|||
|
|||
for accessory in accessories { |
|||
if let uuid = accessory.bluetoothIdentifier { |
|||
onDeviceDiscovered?(uuid) |
|||
connectToAccessory(uuid: uuid) |
|||
} |
|||
} |
|||
} |
|||
} else { |
|||
showDevicePicker() |
|||
} |
|||
} |
|||
// for accessory in accessories { |
|||
// if let uuid = accessory.bluetoothIdentifier { |
|||
// onDeviceDiscovered?(uuid) |
|||
// connectToAccessory(uuid: uuid) |
|||
// } |
|||
// } |
|||
// } |
|||
// } else { |
|||
// showDevicePicker() |
|||
// } |
|||
// } |
|||
|
|||
private func handleAccessoryAdded(_ accessory: ASAccessory?) { |
|||
guard let accessory = accessory, let uuid = accessory.bluetoothIdentifier else { |
|||
return |
|||
} |
|||
// private func handleAccessoryAdded(_ accessory: ASAccessory?) { |
|||
// guard let accessory = accessory, let uuid = accessory.bluetoothIdentifier else { |
|||
// return |
|||
// } |
|||
|
|||
// 保存配对设备的UUID |
|||
savePairedMacAddress(uuid) |
|||
// // 保存配对设备的UUID |
|||
// savePairedMacAddress(uuid) |
|||
|
|||
onDeviceDiscovered?(uuid) |
|||
connectToAccessory(uuid: uuid) |
|||
} |
|||
// onDeviceDiscovered?(uuid) |
|||
// connectToAccessory(uuid: uuid) |
|||
// } |
|||
|
|||
private func handleAccessoryRemoved(_ accessory: ASAccessory?) { |
|||
guard let accessory = accessory, let uuid = accessory.bluetoothIdentifier else { |
|||
return |
|||
} |
|||
// private func handleAccessoryRemoved(_ accessory: ASAccessory?) { |
|||
// guard let accessory = accessory, let uuid = accessory.bluetoothIdentifier else { |
|||
// return |
|||
// } |
|||
|
|||
if let service = bleService { |
|||
service.disconnect() |
|||
} |
|||
} |
|||
// if let service = bleService { |
|||
// service.disconnect() |
|||
// } |
|||
// } |
|||
|
|||
private func handleSessionInitError(_ error: Error) { |
|||
isInitializing = false |
|||
session = nil |
|||
} |
|||
// private func handleSessionInitError(_ error: Error) { |
|||
// isInitializing = false |
|||
// session = nil |
|||
// } |
|||
|
|||
// MARK: - 蓝牙状态检查 |
|||
func isBluetoothAvailable() -> Bool { |
|||
do { |
|||
let audioSession = AVAudioSession.sharedInstance() |
|||
let outputs = audioSession.currentRoute.outputs |
|||
// // MARK: - 蓝牙状态检查 |
|||
// func isBluetoothAvailable() -> Bool { |
|||
// do { |
|||
// let audioSession = AVAudioSession.sharedInstance() |
|||
// let outputs = audioSession.currentRoute.outputs |
|||
|
|||
for output in outputs { |
|||
if output.portType == .bluetoothA2DP || |
|||
output.portType == .bluetoothHFP || |
|||
output.portType == .bluetoothLE { |
|||
return true |
|||
} |
|||
} |
|||
} catch { |
|||
os_log("检查蓝牙状态出错: %{public}@", log: logger, type: .error, error.localizedDescription) |
|||
} |
|||
// for output in outputs { |
|||
// if output.portType == .bluetoothA2DP || |
|||
// output.portType == .bluetoothHFP || |
|||
// output.portType == .bluetoothLE { |
|||
// return true |
|||
// } |
|||
// } |
|||
// } catch { |
|||
// os_log("检查蓝牙状态出错: %{public}@", log: logger, type: .error, error.localizedDescription) |
|||
// } |
|||
|
|||
return true |
|||
} |
|||
// return true |
|||
// } |
|||
|
|||
// MARK: - 清除配对关系 |
|||
func clearAssociations() { |
|||
resetBluetoothState() |
|||
// // MARK: - 清除配对关系 |
|||
// func clearAssociations() { |
|||
// resetBluetoothState() |
|||
|
|||
// 清除保存的配对设备UUID |
|||
clearPairedMacAddress() |
|||
// // 清除保存的配对设备UUID |
|||
// clearPairedMacAddress() |
|||
|
|||
#if canImport(AccessorySetupKit) && swift(>=5.9) |
|||
if #available(iOS 18.0, *) { |
|||
resetSession() |
|||
initializeSession() |
|||
} |
|||
#endif |
|||
} |
|||
// #if canImport(AccessorySetupKit) && swift(>=5.9) |
|||
// if #available(iOS 18.0, *) { |
|||
// resetSession() |
|||
// initializeSession() |
|||
// } |
|||
// #endif |
|||
// } |
|||
|
|||
// MARK: - 配对设备UUID管理 |
|||
// // MARK: - 配对设备UUID管理 |
|||
|
|||
/// 获取已配对设备的UUID字符串 |
|||
/// 返回存储的配对设备UUID,如果没有则返回nil |
|||
func getPairedMacAddress() -> String? { |
|||
let uuidString = UserDefaults.standard.string(forKey: pairedDeviceUUIDKey) |
|||
if let uuidString = uuidString, !uuidString.isEmpty { |
|||
os_log("获取已配对设备UUID: %{public}@", log: logger, type: .info, uuidString) |
|||
return uuidString |
|||
} |
|||
os_log("未找到已配对设备UUID", log: logger, type: .info) |
|||
return nil |
|||
} |
|||
// /// 获取已配对设备的UUID字符串 |
|||
// /// 返回存储的配对设备UUID,如果没有则返回nil |
|||
// func getPairedMacAddress() -> String? { |
|||
// let uuidString = UserDefaults.standard.string(forKey: pairedDeviceUUIDKey) |
|||
// if let uuidString = uuidString, !uuidString.isEmpty { |
|||
// os_log("获取已配对设备UUID: %{public}@", log: logger, type: .info, uuidString) |
|||
// return uuidString |
|||
// } |
|||
// os_log("未找到已配对设备UUID", log: logger, type: .info) |
|||
// return nil |
|||
// } |
|||
|
|||
/// 保存配对设备的UUID |
|||
/// - Parameter uuid: 要保存的设备UUID |
|||
func savePairedMacAddress(_ uuid: UUID) { |
|||
let uuidString = uuid.uuidString |
|||
UserDefaults.standard.set(uuidString, forKey: pairedDeviceUUIDKey) |
|||
UserDefaults.standard.synchronize() |
|||
os_log("已保存配对设备UUID: %{public}@", log: logger, type: .info, uuidString) |
|||
} |
|||
// /// 保存配对设备的UUID |
|||
// /// - Parameter uuid: 要保存的设备UUID |
|||
// func savePairedMacAddress(_ uuid: UUID) { |
|||
// let uuidString = uuid.uuidString |
|||
// UserDefaults.standard.set(uuidString, forKey: pairedDeviceUUIDKey) |
|||
// UserDefaults.standard.synchronize() |
|||
// os_log("已保存配对设备UUID: %{public}@", log: logger, type: .info, uuidString) |
|||
// } |
|||
|
|||
/// 清除已保存的配对设备UUID |
|||
private func clearPairedMacAddress() { |
|||
UserDefaults.standard.removeObject(forKey: pairedDeviceUUIDKey) |
|||
UserDefaults.standard.synchronize() |
|||
os_log("已清除配对设备UUID", log: logger, type: .info) |
|||
} |
|||
} |
|||
// /// 清除已保存的配对设备UUID |
|||
// private func clearPairedMacAddress() { |
|||
// UserDefaults.standard.removeObject(forKey: pairedDeviceUUIDKey) |
|||
// UserDefaults.standard.synchronize() |
|||
// os_log("已清除配对设备UUID", log: logger, type: .info) |
|||
// } |
|||
// } |
|||
File diff suppressed because it is too large
@ -1,29 +0,0 @@ |
|||
# |
|||
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. |
|||
# Run `pod lib lint user_portrait.podspec` to validate before publishing. |
|||
# |
|||
Pod::Spec.new do |s| |
|||
s.name = 'user_portrait' |
|||
s.version = '0.0.1' |
|||
s.summary = '用户画像' |
|||
s.description = <<-DESC |
|||
用户画像 |
|||
DESC |
|||
s.homepage = 'http://example.com' |
|||
s.license = { :file => '../LICENSE' } |
|||
s.author = { 'Your Company' => 'email@example.com' } |
|||
s.source = { :path => '.' } |
|||
s.source_files = 'user_portrait/Sources/user_portrait/**/*' |
|||
s.dependency 'Flutter' |
|||
s.platform = :ios, '12.0' |
|||
|
|||
# Flutter.framework does not contain a i386 slice. |
|||
s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } |
|||
s.swift_version = '5.0' |
|||
|
|||
# If your plugin requires a privacy manifest, for example if it uses any |
|||
# required reason APIs, update the PrivacyInfo.xcprivacy file to describe your |
|||
# plugin's privacy impact, and then uncomment this line. For more information, |
|||
# see https://developer.apple.com/documentation/bundleresources/privacy_manifest_files |
|||
# s.resource_bundles = {'user_portrait_privacy' => ['user_portrait/Sources/user_portrait/PrivacyInfo.xcprivacy']} |
|||
end |
|||
@ -1,14 +0,0 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
|||
<plist version="1.0"> |
|||
<dict> |
|||
<key>NSPrivacyTrackingDomains</key> |
|||
<array/> |
|||
<key>NSPrivacyAccessedAPITypes</key> |
|||
<array/> |
|||
<key>NSPrivacyCollectedDataTypes</key> |
|||
<array/> |
|||
<key>NSPrivacyTracking</key> |
|||
<false/> |
|||
</dict> |
|||
</plist> |
|||
@ -1,27 +0,0 @@ |
|||
import 'package:flutter/services.dart'; |
|||
import 'package:flutter_test/flutter_test.dart'; |
|||
import 'package:user_portrait/user_portrait_method_channel.dart'; |
|||
|
|||
void main() { |
|||
TestWidgetsFlutterBinding.ensureInitialized(); |
|||
|
|||
MethodChannelUserPortrait platform = MethodChannelUserPortrait(); |
|||
const MethodChannel channel = MethodChannel('user_portrait'); |
|||
|
|||
setUp(() { |
|||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler( |
|||
channel, |
|||
(MethodCall methodCall) async { |
|||
return '42'; |
|||
}, |
|||
); |
|||
}); |
|||
|
|||
tearDown(() { |
|||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(channel, null); |
|||
}); |
|||
|
|||
test('getPlatformVersion', () async { |
|||
expect(await platform.getPlatformVersion(), '42'); |
|||
}); |
|||
} |
|||
@ -1,29 +0,0 @@ |
|||
import 'package:flutter_test/flutter_test.dart'; |
|||
import 'package:user_portrait/user_portrait.dart'; |
|||
import 'package:user_portrait/user_portrait_platform_interface.dart'; |
|||
import 'package:user_portrait/user_portrait_method_channel.dart'; |
|||
import 'package:plugin_platform_interface/plugin_platform_interface.dart'; |
|||
|
|||
class MockUserPortraitPlatform |
|||
with MockPlatformInterfaceMixin |
|||
implements UserPortraitPlatform { |
|||
|
|||
@override |
|||
Future<String?> getPlatformVersion() => Future.value('42'); |
|||
} |
|||
|
|||
void main() { |
|||
final UserPortraitPlatform initialPlatform = UserPortraitPlatform.instance; |
|||
|
|||
test('$MethodChannelUserPortrait is the default instance', () { |
|||
expect(initialPlatform, isInstanceOf<MethodChannelUserPortrait>()); |
|||
}); |
|||
|
|||
test('getPlatformVersion', () async { |
|||
UserPortrait userPortraitPlugin = UserPortrait(); |
|||
MockUserPortraitPlatform fakePlatform = MockUserPortraitPlatform(); |
|||
UserPortraitPlatform.instance = fakePlatform; |
|||
|
|||
expect(await userPortraitPlugin.getPlatformVersion(), '42'); |
|||
}); |
|||
} |
|||
Loading…
Reference in new issue