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 Foundation |
||||
import CoreBluetooth |
// import CoreBluetooth |
||||
import os.log |
// import os.log |
||||
import AVFoundation |
// import AVFoundation |
||||
import AccessorySetupKit |
// import AccessorySetupKit |
||||
import UIKit |
// import UIKit |
||||
|
|
||||
@available(iOS 18.0, *) |
// @available(iOS 18.0, *) |
||||
class BleAccessorySetup: NSObject { |
// class BleAccessorySetup: NSObject { |
||||
// MARK: - 属性 |
// // MARK: - 属性 |
||||
static let shared = BleAccessorySetup() |
// static let shared = BleAccessorySetup() |
||||
|
|
||||
private var bleService: BleService? |
// private var bleService: BleService? |
||||
private var session: ASAccessorySession? |
// private var session: ASAccessorySession? |
||||
private var isSessionActivated = false |
// private var isSessionActivated = false |
||||
private var isInitializing = false |
// private var isInitializing = false |
||||
private let logger = OSLog(subsystem: "com.yunqiinnovation.ble_service", category: "BleAccessorySetup") |
// private let logger = OSLog(subsystem: "com.yunqiinnovation.ble_service", category: "BleAccessorySetup") |
||||
|
|
||||
// 杰理蓝牙设备的公司标识符 |
// // 杰理蓝牙设备的公司标识符 |
||||
private let deviceCompanyIdentifier: UInt16 = 0xEFAB |
// private let deviceCompanyIdentifier: UInt16 = 0xEFAB |
||||
private let deviceDisplayName = "语音助理耳机" |
// private let deviceDisplayName = "语音助理耳机" |
||||
|
|
||||
// 用于存储配对设备UUID的UserDefaults键 |
// // 用于存储配对设备UUID的UserDefaults键 |
||||
private let pairedDeviceUUIDKey = "com.yunqiinnovation.ble_service.paired_device_uuid" |
// private let pairedDeviceUUIDKey = "com.yunqiinnovation.ble_service.paired_device_uuid" |
||||
|
|
||||
typealias DeviceDiscoveredCallback = (UUID) -> Void |
// typealias DeviceDiscoveredCallback = (UUID) -> Void |
||||
private var onDeviceDiscovered: DeviceDiscoveredCallback? |
// private var onDeviceDiscovered: DeviceDiscoveredCallback? |
||||
|
|
||||
// MARK: - 初始化 |
// // MARK: - 初始化 |
||||
private override init() { super.init() } |
// private override init() { super.init() } |
||||
|
|
||||
// MARK: - 公共方法 |
// // MARK: - 公共方法 |
||||
func setBleService(_ service: Any) { |
// func setBleService(_ service: Any) { |
||||
self.bleService = service as? BleService |
// self.bleService = service as? BleService |
||||
} |
// } |
||||
|
|
||||
func setDeviceDiscoveredCallback(_ callback: @escaping (UUID) -> Void) { |
// func setDeviceDiscoveredCallback(_ callback: @escaping (UUID) -> Void) { |
||||
self.onDeviceDiscovered = callback |
// self.onDeviceDiscovered = callback |
||||
} |
// } |
||||
|
|
||||
// 处理发现的设备并连接 |
// // 处理发现的设备并连接 |
||||
func connectToAccessory(uuid: UUID) { |
// func connectToAccessory(uuid: UUID) { |
||||
guard let service = bleService else { |
// guard let service = bleService else { |
||||
return |
// return |
||||
} |
// } |
||||
|
|
||||
service.connectToAccessory(uuid: uuid) |
// service.connectToAccessory(uuid: uuid) |
||||
} |
// } |
||||
|
|
||||
func addToReconnectQueue(uuid: UUID) { |
// func addToReconnectQueue(uuid: UUID) { |
||||
guard let service = bleService else { |
// guard let service = bleService else { |
||||
return |
// return |
||||
} |
// } |
||||
|
|
||||
service.addToReconnectQueue(uuid: uuid) |
// service.addToReconnectQueue(uuid: uuid) |
||||
} |
// } |
||||
|
|
||||
// 从视图控制器显示设备选择器 - 主要入口点 |
// // 从视图控制器显示设备选择器 - 主要入口点 |
||||
func showDevicePickerFromViewController(_ viewController: UIViewController) { |
// func showDevicePickerFromViewController(_ viewController: UIViewController) { |
||||
initializeSession() |
// initializeSession() |
||||
} |
// } |
||||
|
|
||||
// 清除蓝牙状态(仅用于测试或重置,不会清除系统授权) |
// // 清除蓝牙状态(仅用于测试或重置,不会清除系统授权) |
||||
func resetBluetoothState() { |
// func resetBluetoothState() { |
||||
if let service = bleService { |
// if let service = bleService { |
||||
service.disconnect() |
// service.disconnect() |
||||
} |
// } |
||||
} |
// } |
||||
|
|
||||
// MARK: - 会话管理 |
// // MARK: - 会话管理 |
||||
func initializeSession() { |
// func initializeSession() { |
||||
if isInitializing { |
// if isInitializing { |
||||
os_log("会话正在初始化中,跳过", log: logger, type: .info) |
// os_log("会话正在初始化中,跳过", log: logger, type: .info) |
||||
return |
// return |
||||
} |
// } |
||||
|
|
||||
// 如果会话已激活,检查是否有配对设备 |
// // 如果会话已激活,检查是否有配对设备 |
||||
if session != nil && isSessionActivated { |
// if session != nil && isSessionActivated { |
||||
os_log("会话已激活,检查配对设备", log: logger, type: .info) |
// os_log("会话已激活,检查配对设备", log: logger, type: .info) |
||||
// 会话已激活,检查是否有配对设备 |
// // 会话已激活,检查是否有配对设备 |
||||
if let accessories = session?.accessories, !accessories.isEmpty { |
// if let accessories = session?.accessories, !accessories.isEmpty { |
||||
os_log("发现 %d 个已配对设备,尝试连接", log: logger, type: .info, accessories.count) |
// os_log("发现 %d 个已配对设备,尝试连接", log: logger, type: .info, accessories.count) |
||||
// 有配对设备,尝试连接 |
// // 有配对设备,尝试连接 |
||||
if let service = bleService { |
// if let service = bleService { |
||||
let _ = service.getCentralManager() |
// let _ = service.getCentralManager() |
||||
|
|
||||
for accessory in accessories { |
// for accessory in accessories { |
||||
if let uuid = accessory.bluetoothIdentifier { |
// if let uuid = accessory.bluetoothIdentifier { |
||||
onDeviceDiscovered?(uuid) |
// onDeviceDiscovered?(uuid) |
||||
connectToAccessory(uuid: uuid) |
// connectToAccessory(uuid: uuid) |
||||
} |
// } |
||||
} |
// } |
||||
} |
// } |
||||
} else { |
// } else { |
||||
os_log("没有已配对设备,显示配对框", log: logger, type: .info) |
// os_log("没有已配对设备,显示配对框", log: logger, type: .info) |
||||
// 没有配对设备,直接显示配对框 |
// // 没有配对设备,直接显示配对框 |
||||
showDevicePicker() |
// showDevicePicker() |
||||
} |
// } |
||||
return |
// return |
||||
} |
// } |
||||
|
|
||||
os_log("开始初始化新会话", log: logger, type: .info) |
// os_log("开始初始化新会话", log: logger, type: .info) |
||||
isInitializing = true |
// isInitializing = true |
||||
|
|
||||
do { |
// do { |
||||
session = try ASAccessorySession() |
// session = try ASAccessorySession() |
||||
session?.activate(on: .main) { [weak self] event in |
// session?.activate(on: .main) { [weak self] event in |
||||
guard let self = self else { return } |
// guard let self = self else { return } |
||||
self.handleSessionEvent(event) |
// self.handleSessionEvent(event) |
||||
} |
// } |
||||
} catch { |
// } catch { |
||||
os_log("会话初始化失败: %{public}@", log: logger, type: .error, error.localizedDescription) |
// os_log("会话初始化失败: %{public}@", log: logger, type: .error, error.localizedDescription) |
||||
handleSessionInitError(error) |
// handleSessionInitError(error) |
||||
} |
// } |
||||
} |
// } |
||||
|
|
||||
func resetSession() { |
// func resetSession() { |
||||
session?.invalidate() |
// session?.invalidate() |
||||
session = nil |
// session = nil |
||||
isSessionActivated = false |
// isSessionActivated = false |
||||
isInitializing = false |
// isInitializing = false |
||||
} |
// } |
||||
|
|
||||
// MARK: - 设备选择器显示 |
// // MARK: - 设备选择器显示 |
||||
private func showDevicePicker() { |
// private func showDevicePicker() { |
||||
guard let session = session, isSessionActivated else { |
// guard let session = session, isSessionActivated else { |
||||
os_log("无法显示配对框:会话未激活", log: logger, type: .error) |
// os_log("无法显示配对框:会话未激活", log: logger, type: .error) |
||||
return |
// return |
||||
} |
// } |
||||
|
|
||||
os_log("准备显示设备配对框", log: logger, type: .info) |
// os_log("准备显示设备配对框", log: logger, type: .info) |
||||
|
|
||||
let descriptor = ASDiscoveryDescriptor() |
// let descriptor = ASDiscoveryDescriptor() |
||||
descriptor.bluetoothCompanyIdentifier = ASBluetoothCompanyIdentifier(deviceCompanyIdentifier) |
// descriptor.bluetoothCompanyIdentifier = ASBluetoothCompanyIdentifier(deviceCompanyIdentifier) |
||||
|
|
||||
let deviceImage = UIImage(systemName: "headphones") ?? UIImage() |
// let deviceImage = UIImage(systemName: "headphones") ?? UIImage() |
||||
let displayItem = ASPickerDisplayItem( |
// let displayItem = ASPickerDisplayItem( |
||||
name: deviceDisplayName, |
// name: deviceDisplayName, |
||||
productImage: deviceImage, |
// productImage: deviceImage, |
||||
descriptor: descriptor |
// descriptor: descriptor |
||||
) |
// ) |
||||
|
|
||||
DispatchQueue.main.async { |
// DispatchQueue.main.async { |
||||
os_log("正在显示设备配对框", log: self.logger, type: .info) |
// os_log("正在显示设备配对框", log: self.logger, type: .info) |
||||
session.showPicker(for: [displayItem]) { [weak self] error in |
// session.showPicker(for: [displayItem]) { [weak self] error in |
||||
guard let self = self else { return } |
// guard let self = self else { return } |
||||
|
|
||||
if let error = error { |
// if let error = error { |
||||
let nsError = error as NSError |
// let nsError = error as NSError |
||||
os_log("设备选择器错误: %{public}@", log: self.logger, type: .error, error.localizedDescription) |
// os_log("设备选择器错误: %{public}@", log: self.logger, type: .error, error.localizedDescription) |
||||
} else { |
// } else { |
||||
os_log("设备选择器完成", log: self.logger, type: .info) |
// os_log("设备选择器完成", log: self.logger, type: .info) |
||||
} |
// } |
||||
} |
// } |
||||
} |
// } |
||||
} |
// } |
||||
|
|
||||
// MARK: - 私有辅助方法 |
// // MARK: - 私有辅助方法 |
||||
private func handleSessionEvent(_ event: ASAccessoryEvent) { |
// private func handleSessionEvent(_ event: ASAccessoryEvent) { |
||||
switch event.eventType { |
// switch event.eventType { |
||||
case .activated: |
// case .activated: |
||||
os_log("会话已激活", log: logger, type: .info) |
// os_log("会话已激活", log: logger, type: .info) |
||||
handleSessionActivated() |
// handleSessionActivated() |
||||
|
|
||||
case .accessoryAdded: |
// case .accessoryAdded: |
||||
os_log("发现新设备: %{public}@", log: logger, type: .info, event.accessory?.bluetoothIdentifier?.uuidString ?? "Unknown") |
// os_log("发现新设备: %{public}@", log: logger, type: .info, event.accessory?.bluetoothIdentifier?.uuidString ?? "Unknown") |
||||
handleAccessoryAdded(event.accessory) |
// handleAccessoryAdded(event.accessory) |
||||
|
|
||||
case .accessoryRemoved: |
// case .accessoryRemoved: |
||||
os_log("设备已移除: %{public}@", log: logger, type: .info, event.accessory?.bluetoothIdentifier?.uuidString ?? "Unknown") |
// os_log("设备已移除: %{public}@", log: logger, type: .info, event.accessory?.bluetoothIdentifier?.uuidString ?? "Unknown") |
||||
handleAccessoryRemoved(event.accessory) |
// handleAccessoryRemoved(event.accessory) |
||||
|
|
||||
case .accessoryChanged: |
// case .accessoryChanged: |
||||
os_log("设备状态已改变", log: logger, type: .info) |
// os_log("设备状态已改变", log: logger, type: .info) |
||||
break |
// break |
||||
|
|
||||
case .pickerDidPresent: |
// case .pickerDidPresent: |
||||
os_log("配对框已显示", log: logger, type: .info) |
// os_log("配对框已显示", log: logger, type: .info) |
||||
break |
// break |
||||
|
|
||||
case .pickerDidDismiss: |
// case .pickerDidDismiss: |
||||
os_log("配对框已关闭", log: logger, type: .info) |
// os_log("配对框已关闭", log: logger, type: .info) |
||||
break |
// break |
||||
|
|
||||
case .pickerSetupFailed: |
// case .pickerSetupFailed: |
||||
os_log("选择器设置失败: %{public}@", log: logger, type: .error, |
// os_log("选择器设置失败: %{public}@", log: logger, type: .error, |
||||
event.error?.localizedDescription ?? "未知错误") |
// event.error?.localizedDescription ?? "未知错误") |
||||
|
|
||||
default: |
// default: |
||||
os_log("未处理的会话事件: %d", log: logger, type: .info, event.eventType.rawValue) |
// os_log("未处理的会话事件: %d", log: logger, type: .info, event.eventType.rawValue) |
||||
break |
// break |
||||
} |
// } |
||||
} |
// } |
||||
|
|
||||
private func handleSessionActivated() { |
// private func handleSessionActivated() { |
||||
isSessionActivated = true |
// isSessionActivated = true |
||||
isInitializing = false |
// isInitializing = false |
||||
|
|
||||
if let accessories = session?.accessories, !accessories.isEmpty { |
// if let accessories = session?.accessories, !accessories.isEmpty { |
||||
if let service = bleService { |
// if let service = bleService { |
||||
let _ = service.getCentralManager() |
// let _ = service.getCentralManager() |
||||
|
|
||||
for accessory in accessories { |
// for accessory in accessories { |
||||
if let uuid = accessory.bluetoothIdentifier { |
// if let uuid = accessory.bluetoothIdentifier { |
||||
onDeviceDiscovered?(uuid) |
// onDeviceDiscovered?(uuid) |
||||
connectToAccessory(uuid: uuid) |
// connectToAccessory(uuid: uuid) |
||||
} |
// } |
||||
} |
// } |
||||
} |
// } |
||||
} else { |
// } else { |
||||
showDevicePicker() |
// showDevicePicker() |
||||
} |
// } |
||||
} |
// } |
||||
|
|
||||
private func handleAccessoryAdded(_ accessory: ASAccessory?) { |
// private func handleAccessoryAdded(_ accessory: ASAccessory?) { |
||||
guard let accessory = accessory, let uuid = accessory.bluetoothIdentifier else { |
// guard let accessory = accessory, let uuid = accessory.bluetoothIdentifier else { |
||||
return |
// return |
||||
} |
// } |
||||
|
|
||||
// 保存配对设备的UUID |
// // 保存配对设备的UUID |
||||
savePairedMacAddress(uuid) |
// savePairedMacAddress(uuid) |
||||
|
|
||||
onDeviceDiscovered?(uuid) |
// onDeviceDiscovered?(uuid) |
||||
connectToAccessory(uuid: uuid) |
// connectToAccessory(uuid: uuid) |
||||
} |
// } |
||||
|
|
||||
private func handleAccessoryRemoved(_ accessory: ASAccessory?) { |
// private func handleAccessoryRemoved(_ accessory: ASAccessory?) { |
||||
guard let accessory = accessory, let uuid = accessory.bluetoothIdentifier else { |
// guard let accessory = accessory, let uuid = accessory.bluetoothIdentifier else { |
||||
return |
// return |
||||
} |
// } |
||||
|
|
||||
if let service = bleService { |
// if let service = bleService { |
||||
service.disconnect() |
// service.disconnect() |
||||
} |
// } |
||||
} |
// } |
||||
|
|
||||
private func handleSessionInitError(_ error: Error) { |
// private func handleSessionInitError(_ error: Error) { |
||||
isInitializing = false |
// isInitializing = false |
||||
session = nil |
// session = nil |
||||
} |
// } |
||||
|
|
||||
// MARK: - 蓝牙状态检查 |
// // MARK: - 蓝牙状态检查 |
||||
func isBluetoothAvailable() -> Bool { |
// func isBluetoothAvailable() -> Bool { |
||||
do { |
// do { |
||||
let audioSession = AVAudioSession.sharedInstance() |
// let audioSession = AVAudioSession.sharedInstance() |
||||
let outputs = audioSession.currentRoute.outputs |
// let outputs = audioSession.currentRoute.outputs |
||||
|
|
||||
for output in outputs { |
// for output in outputs { |
||||
if output.portType == .bluetoothA2DP || |
// if output.portType == .bluetoothA2DP || |
||||
output.portType == .bluetoothHFP || |
// output.portType == .bluetoothHFP || |
||||
output.portType == .bluetoothLE { |
// output.portType == .bluetoothLE { |
||||
return true |
// return true |
||||
} |
// } |
||||
} |
// } |
||||
} catch { |
// } catch { |
||||
os_log("检查蓝牙状态出错: %{public}@", log: logger, type: .error, error.localizedDescription) |
// os_log("检查蓝牙状态出错: %{public}@", log: logger, type: .error, error.localizedDescription) |
||||
} |
// } |
||||
|
|
||||
return true |
// return true |
||||
} |
// } |
||||
|
|
||||
// MARK: - 清除配对关系 |
// // MARK: - 清除配对关系 |
||||
func clearAssociations() { |
// func clearAssociations() { |
||||
resetBluetoothState() |
// resetBluetoothState() |
||||
|
|
||||
// 清除保存的配对设备UUID |
// // 清除保存的配对设备UUID |
||||
clearPairedMacAddress() |
// clearPairedMacAddress() |
||||
|
|
||||
#if canImport(AccessorySetupKit) && swift(>=5.9) |
// #if canImport(AccessorySetupKit) && swift(>=5.9) |
||||
if #available(iOS 18.0, *) { |
// if #available(iOS 18.0, *) { |
||||
resetSession() |
// resetSession() |
||||
initializeSession() |
// initializeSession() |
||||
} |
// } |
||||
#endif |
// #endif |
||||
} |
// } |
||||
|
|
||||
// MARK: - 配对设备UUID管理 |
// // MARK: - 配对设备UUID管理 |
||||
|
|
||||
/// 获取已配对设备的UUID字符串 |
// /// 获取已配对设备的UUID字符串 |
||||
/// 返回存储的配对设备UUID,如果没有则返回nil |
// /// 返回存储的配对设备UUID,如果没有则返回nil |
||||
func getPairedMacAddress() -> String? { |
// func getPairedMacAddress() -> String? { |
||||
let uuidString = UserDefaults.standard.string(forKey: pairedDeviceUUIDKey) |
// let uuidString = UserDefaults.standard.string(forKey: pairedDeviceUUIDKey) |
||||
if let uuidString = uuidString, !uuidString.isEmpty { |
// if let uuidString = uuidString, !uuidString.isEmpty { |
||||
os_log("获取已配对设备UUID: %{public}@", log: logger, type: .info, uuidString) |
// os_log("获取已配对设备UUID: %{public}@", log: logger, type: .info, uuidString) |
||||
return uuidString |
// return uuidString |
||||
} |
// } |
||||
os_log("未找到已配对设备UUID", log: logger, type: .info) |
// os_log("未找到已配对设备UUID", log: logger, type: .info) |
||||
return nil |
// return nil |
||||
} |
// } |
||||
|
|
||||
/// 保存配对设备的UUID |
// /// 保存配对设备的UUID |
||||
/// - Parameter uuid: 要保存的设备UUID |
// /// - Parameter uuid: 要保存的设备UUID |
||||
func savePairedMacAddress(_ uuid: UUID) { |
// func savePairedMacAddress(_ uuid: UUID) { |
||||
let uuidString = uuid.uuidString |
// let uuidString = uuid.uuidString |
||||
UserDefaults.standard.set(uuidString, forKey: pairedDeviceUUIDKey) |
// UserDefaults.standard.set(uuidString, forKey: pairedDeviceUUIDKey) |
||||
UserDefaults.standard.synchronize() |
// UserDefaults.standard.synchronize() |
||||
os_log("已保存配对设备UUID: %{public}@", log: logger, type: .info, uuidString) |
// os_log("已保存配对设备UUID: %{public}@", log: logger, type: .info, uuidString) |
||||
} |
// } |
||||
|
|
||||
/// 清除已保存的配对设备UUID |
// /// 清除已保存的配对设备UUID |
||||
private func clearPairedMacAddress() { |
// private func clearPairedMacAddress() { |
||||
UserDefaults.standard.removeObject(forKey: pairedDeviceUUIDKey) |
// UserDefaults.standard.removeObject(forKey: pairedDeviceUUIDKey) |
||||
UserDefaults.standard.synchronize() |
// UserDefaults.standard.synchronize() |
||||
os_log("已清除配对设备UUID", log: logger, type: .info) |
// 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