fix: 完善设备搜索页
This commit is contained in:
parent
6f75cf0bcd
commit
799479447c
25
ios/Podfile
25
ios/Podfile
@ -43,5 +43,30 @@ end
|
||||
post_install do |installer|
|
||||
installer.pods_project.targets.each do |target|
|
||||
flutter_additional_ios_build_settings(target)
|
||||
target.build_configurations.each do |config|
|
||||
# You can remove unused permissions here
|
||||
# for more information: https://github.com/Baseflow/flutter-permission-handler/blob/main/permission_handler_apple/ios/Classes/PermissionHandlerEnums.h
|
||||
# e.g. when you don't need camera permission, just add 'PERMISSION_CAMERA=0'
|
||||
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
|
||||
'$(inherited)',
|
||||
|
||||
## dart: PermissionGroup.camera
|
||||
'PERMISSION_CAMERA=1',
|
||||
|
||||
## dart: PermissionGroup.photos
|
||||
'PERMISSION_PHOTOS=1',
|
||||
|
||||
## dart: PermissionGroup.bluetooth
|
||||
'PERMISSION_BLUETOOTH=1',
|
||||
|
||||
## dart: PermissionGroup.MICROPHONE
|
||||
'PERMISSION_MICROPHONE=1',
|
||||
|
||||
## dart: PermissionGroup.location
|
||||
'PERMISSION_LOCATION=1',
|
||||
'PERMISSION_LOCATION_WHENINUSE=0',
|
||||
]
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -8,15 +8,12 @@ PODS:
|
||||
- shared_preferences_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- starcloud (0.0.1):
|
||||
- Flutter
|
||||
|
||||
DEPENDENCIES:
|
||||
- Flutter (from `Flutter`)
|
||||
- flutter_blue_plus_darwin (from `.symlinks/plugins/flutter_blue_plus_darwin/darwin`)
|
||||
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
|
||||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||
- starcloud (from `.symlinks/plugins/starcloud/ios`)
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
Flutter:
|
||||
@ -27,16 +24,13 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/permission_handler_apple/ios"
|
||||
shared_preferences_foundation:
|
||||
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
|
||||
starcloud:
|
||||
:path: ".symlinks/plugins/starcloud/ios"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
||||
flutter_blue_plus_darwin: 20a08bfeaa0f7804d524858d3d8744bcc1b6dbc3
|
||||
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
|
||||
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
|
||||
starcloud: 0d2034397e2a0d81b8c187b99bd2de7371ea5b2d
|
||||
|
||||
PODFILE CHECKSUM: fbdae9471aca60c39ad623cd945ad8e2e75a0c21
|
||||
PODFILE CHECKSUM: 41883e5e56033ab6e4e0f607734d753c5bb7c460
|
||||
|
||||
COCOAPODS: 1.16.2
|
||||
|
||||
@ -45,5 +45,46 @@
|
||||
<true/>
|
||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||
<true/>
|
||||
|
||||
<!-- 蓝牙权限说明 -->
|
||||
<!-- iOS 13+ 蓝牙使用权限 -->
|
||||
<key>NSBluetoothAlwaysUsageDescription</key>
|
||||
<string>应用需要使用蓝牙功能来搜索和连接设备,实现设备配对和数据传输</string>
|
||||
|
||||
<!-- iOS 12及以下蓝牙外设权限 -->
|
||||
<key>NSBluetoothPeripheralUsageDescription</key>
|
||||
<string>应用需要使用蓝牙外设功能来连接和管理设备</string>
|
||||
|
||||
<!-- 位置权限说明 -->
|
||||
<!-- iOS 11+ 使用时位置权限 -->
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
<string>应用需要位置权限来搜索附近的蓝牙设备</string>
|
||||
|
||||
<!-- iOS 11+ 始终位置权限 -->
|
||||
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
|
||||
<string>应用需要位置权限来搜索附近的蓝牙设备</string>
|
||||
|
||||
<!-- iOS 10及以下始终位置权限 -->
|
||||
<key>NSLocationAlwaysUsageDescription</key>
|
||||
<string>应用需要位置权限来搜索附近的蓝牙设备</string>
|
||||
|
||||
<!-- 相机权限说明(如果后续需要) -->
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>应用需要相机权限来扫描二维码</string>
|
||||
|
||||
<!-- 麦克风权限说明(如果后续需要) -->
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>应用需要麦克风权限来录制音频</string>
|
||||
|
||||
<!-- 通知权限说明 -->
|
||||
<key>NSUserNotificationAlertStyle</key>
|
||||
<string>banner</string>
|
||||
|
||||
<!-- 支持的后台模式 -->
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>bluetooth-central</string>
|
||||
<string>bluetooth-peripheral</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@ -4,7 +4,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_easyloading/flutter_easyloading.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:starcloud/sdk/starcloud.dart';
|
||||
|
||||
import 'package:starwork_flutter/api/base_api_service.dart';
|
||||
import 'package:starwork_flutter/api/service/common_api_service.dart';
|
||||
import 'package:starwork_flutter/api/service/user_api_service.dart';
|
||||
@ -20,11 +20,7 @@ class AppInitialization {
|
||||
setSystemStatusBar();
|
||||
await SharedPreferencesUtils.init();
|
||||
initEasyLoading();
|
||||
StarCloudSDK.init(
|
||||
clientId: F.starCloudClientId,
|
||||
clientSecret: F.starCloudSecret,
|
||||
environmentUrl: F.starCloudUrl,
|
||||
);
|
||||
|
||||
Get.put(BaseApiService());
|
||||
Get.put(CommonApiService(Get.find<BaseApiService>()));
|
||||
Get.put(UserApiService(Get.find<BaseApiService>()));
|
||||
@ -49,6 +45,9 @@ class AppInitialization {
|
||||
),
|
||||
);
|
||||
}
|
||||
SystemChrome.setPreferredOrientations([
|
||||
DeviceOrientation.portraitUp,
|
||||
]);
|
||||
}
|
||||
|
||||
static void initEasyLoading() {
|
||||
|
||||
65
lib/base/app_logger.dart
Normal file
65
lib/base/app_logger.dart
Normal file
@ -0,0 +1,65 @@
|
||||
// utils/app_logger.dart
|
||||
|
||||
import 'package:flutter/foundation.dart' show kReleaseMode;
|
||||
import 'dart:developer' as developer;
|
||||
|
||||
// 日志级别枚举(必须在类外)
|
||||
enum AppLogLevel { debug, info, warn, error }
|
||||
|
||||
/// 静态日志工具类,支持 Debug/Release 模式控制
|
||||
class AppLogger {
|
||||
// 🔒 禁止外部实例化(虽然是静态类,但防止误用)
|
||||
AppLogger._();
|
||||
|
||||
// 是否启用日志(Release 模式下关闭)
|
||||
static bool get _isLoggingEnabled => !kReleaseMode;
|
||||
|
||||
// === 日志输出主方法 ===
|
||||
static void _log(AppLogLevel level, String message,
|
||||
{Object? error, StackTrace? stackTrace}) {
|
||||
if (!_isLoggingEnabled) return;
|
||||
|
||||
final String levelStr = level.name.toUpperCase();
|
||||
final String time = DateTime.now().toIso8601String().split('.').first;
|
||||
|
||||
String logMessage = '[$time] [$levelStr] $message';
|
||||
if (error != null) {
|
||||
logMessage += '\n🔥 Error: $error';
|
||||
}
|
||||
|
||||
developer.log(
|
||||
logMessage,
|
||||
name: 'AppLogger',
|
||||
error: error,
|
||||
stackTrace: stackTrace,
|
||||
);
|
||||
}
|
||||
|
||||
// === 便捷日志方法 ===
|
||||
|
||||
/// 调试日志
|
||||
static void debug(String message) {
|
||||
_log(AppLogLevel.debug, message);
|
||||
}
|
||||
|
||||
/// 一般信息
|
||||
static void info(String message) {
|
||||
_log(AppLogLevel.info, message);
|
||||
}
|
||||
|
||||
/// 警告日志
|
||||
static void warn(String message, {Object? error}) {
|
||||
_log(AppLogLevel.warn, message, error: error);
|
||||
}
|
||||
|
||||
/// 错误日志
|
||||
static void error(String message, {Object? error, StackTrace? stackTrace}) {
|
||||
_log(AppLogLevel.error, message, error: error, stackTrace: stackTrace);
|
||||
}
|
||||
|
||||
/// 高亮日志(用于关键节点)
|
||||
static void highlight(String message) {
|
||||
if (!_isLoggingEnabled) return;
|
||||
debug('✨✨✨ $message ✨✨✨');
|
||||
}
|
||||
}
|
||||
@ -1,3 +1,4 @@
|
||||
import 'dart:io';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
|
||||
class AppPermission {
|
||||
@ -46,26 +47,44 @@ class AppPermission {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// 请求蓝牙权限(Android 12及以上需要)
|
||||
static Future<bool> requestBluetoothPermissions() async {
|
||||
try {
|
||||
// Android 12及以上需要的蓝牙权限
|
||||
List<Permission> bluetoothPermissions = [
|
||||
Permission.bluetoothScan,
|
||||
Permission.bluetoothConnect,
|
||||
Permission.bluetoothAdvertise,
|
||||
];
|
||||
|
||||
List<Permission> bluetoothPermissions = [];
|
||||
|
||||
if (Platform.isAndroid) {
|
||||
// Android 12+ 需要细分权限
|
||||
if (Platform.version.startsWith('Android 12') || Platform.isAndroid) {
|
||||
bluetoothPermissions = [
|
||||
Permission.bluetoothScan,
|
||||
Permission.bluetoothConnect,
|
||||
Permission.bluetoothAdvertise,
|
||||
];
|
||||
} else {
|
||||
// Android 11 及以下
|
||||
bluetoothPermissions = [
|
||||
Permission.bluetooth,
|
||||
Permission.location, // 扫描 BLE 可能需要位置
|
||||
];
|
||||
}
|
||||
} else if (Platform.isIOS) {
|
||||
// iOS 只需要通用 bluetooth 权限
|
||||
bluetoothPermissions = [
|
||||
Permission.bluetooth,
|
||||
];
|
||||
}
|
||||
|
||||
// Android 12以下需要的位置权限
|
||||
List<Permission> locationPermissions = [
|
||||
Permission.location,
|
||||
Permission.locationWhenInUse,
|
||||
];
|
||||
|
||||
|
||||
bool bluetoothGranted = true;
|
||||
bool locationGranted = true;
|
||||
|
||||
|
||||
// 检查并请求蓝牙权限(Android 12+)
|
||||
Map<Permission, PermissionStatus> bluetoothStatuses = await bluetoothPermissions.request();
|
||||
for (var status in bluetoothStatuses.values) {
|
||||
@ -74,7 +93,7 @@ class AppPermission {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 检查并请求位置权限(Android 12以下或蓝牙扫描需要)
|
||||
Map<Permission, PermissionStatus> locationStatuses = await locationPermissions.request();
|
||||
for (var status in locationStatuses.values) {
|
||||
@ -83,23 +102,22 @@ class AppPermission {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool hasPermission = bluetoothGranted && locationGranted;
|
||||
|
||||
|
||||
if (hasPermission) {
|
||||
print("蓝牙权限已授予");
|
||||
} else {
|
||||
print("蓝牙权限被拒绝");
|
||||
}
|
||||
|
||||
|
||||
return hasPermission;
|
||||
|
||||
} catch (e) {
|
||||
print("请求蓝牙权限失败: $e");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 检查蓝牙扫描权限
|
||||
static Future<bool> checkBluetoothScanPermission() async {
|
||||
try {
|
||||
@ -110,7 +128,7 @@ class AppPermission {
|
||||
return await checkLocationPermission();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 检查蓝牙连接权限
|
||||
static Future<bool> checkBluetoothConnectPermission() async {
|
||||
try {
|
||||
@ -122,7 +140,7 @@ class AppPermission {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 检查蓝牙广播权限
|
||||
static Future<bool> checkBluetoothAdvertisePermission() async {
|
||||
try {
|
||||
@ -134,18 +152,18 @@ class AppPermission {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 检查位置权限(Android 12以下蓝牙扫描需要)
|
||||
static Future<bool> checkLocationPermission() async {
|
||||
var status = await Permission.location.status;
|
||||
return status == PermissionStatus.granted;
|
||||
}
|
||||
|
||||
|
||||
// 请求位置权限
|
||||
static Future<bool> requestLocationPermission() async {
|
||||
try {
|
||||
final PermissionStatus status = await Permission.location.request();
|
||||
|
||||
|
||||
if (status == PermissionStatus.granted) {
|
||||
print("位置权限已授予");
|
||||
return true;
|
||||
@ -154,24 +172,30 @@ class AppPermission {
|
||||
return false;
|
||||
} else if (status.isPermanentlyDenied) {
|
||||
print("位置权限被永久拒绝,跳转到设置页面");
|
||||
openAppSettings();
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
} catch (e) {
|
||||
print("请求位置权限失败: $e");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 检查所有蓝牙相关权限
|
||||
static Future<bool> checkAllBluetoothPermissions() async {
|
||||
bool scanPermission = await checkBluetoothScanPermission();
|
||||
bool connectPermission = await checkBluetoothConnectPermission();
|
||||
bool advertisePermission = await checkBluetoothAdvertisePermission();
|
||||
bool locationPermission = await checkLocationPermission();
|
||||
|
||||
return scanPermission && connectPermission && advertisePermission && locationPermission;
|
||||
if (Platform.isIOS) {
|
||||
// iOS主要检查位置权限,蓝牙权限由系统自动处理
|
||||
bool locationPermission = await checkLocationPermission();
|
||||
return locationPermission;
|
||||
} else {
|
||||
// Android检查所有相关权限
|
||||
bool scanPermission = await checkBluetoothScanPermission();
|
||||
bool connectPermission = await checkBluetoothConnectPermission();
|
||||
bool advertisePermission = await checkBluetoothAdvertisePermission();
|
||||
bool locationPermission = await checkLocationPermission();
|
||||
|
||||
return scanPermission && connectPermission && advertisePermission && locationPermission;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
13
lib/ble/ble_config.dart
Normal file
13
lib/ble/ble_config.dart
Normal file
@ -0,0 +1,13 @@
|
||||
class BleConfig {
|
||||
// 私有构造函数
|
||||
BleConfig._() {
|
||||
// ✅ 这里就是单例初始化的地方
|
||||
// 只会执行一次(第一次获取实例时)
|
||||
}
|
||||
|
||||
// 静态实例
|
||||
static final BleConfig _instance = BleConfig._();
|
||||
|
||||
// 工厂构造函数,提供全局访问点
|
||||
factory BleConfig() => _instance;
|
||||
}
|
||||
149
lib/ble/ble_service.dart
Normal file
149
lib/ble/ble_service.dart
Normal file
@ -0,0 +1,149 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
|
||||
import 'package:starwork_flutter/base/app_logger.dart';
|
||||
import 'package:starwork_flutter/ble/model/scan_device_info.dart';
|
||||
import 'package:starwork_flutter/common/constant/device_type.dart';
|
||||
|
||||
class BleService {
|
||||
// 私有构造函数
|
||||
BleService._() {
|
||||
// ✅ 这里就是单例初始化的地方
|
||||
// 只会执行一次(第一次获取实例时)
|
||||
_initialize();
|
||||
}
|
||||
|
||||
// 静态实例
|
||||
static final BleService _instance = BleService._();
|
||||
|
||||
// 工厂构造函数,提供全局访问点
|
||||
factory BleService() => _instance;
|
||||
|
||||
/// 用来存储搜索到的设备,并用于去重和过滤
|
||||
final Map<String, ScanResult> _discoveredDevices = {};
|
||||
|
||||
/// 用来监听蓝牙适配器状态的订阅流
|
||||
StreamSubscription<BluetoothAdapterState>? _adapterStateSubscription;
|
||||
|
||||
/// 用来监听搜索到的设备的订阅流
|
||||
StreamSubscription<List<ScanResult>>? _scanResultSubscription;
|
||||
|
||||
// 内部维护的蓝牙状态
|
||||
BluetoothAdapterState _bluetoothAdapterState = BluetoothAdapterState.unknown;
|
||||
|
||||
/// 提供外部获取蓝牙适配器方法
|
||||
BluetoothAdapterState get bluetoothAdapterState => _bluetoothAdapterState;
|
||||
|
||||
/// 搜索状态
|
||||
bool get isScanningNow => FlutterBluePlus.isScanningNow;
|
||||
|
||||
/// 初始化服务时执行的
|
||||
Future<void> _initialize() async {
|
||||
AppLogger.highlight('🚀 BleService 正在初始化...');
|
||||
|
||||
/// 监听蓝牙适配器状态
|
||||
_adapterStateSubscription = FlutterBluePlus.adapterState.listen((BluetoothAdapterState state) {
|
||||
_bluetoothAdapterState = state;
|
||||
AppLogger.highlight('蓝牙适配器状态发送变化:${state}');
|
||||
});
|
||||
|
||||
AppLogger.highlight('✅ BleService 初始化完成');
|
||||
}
|
||||
|
||||
/// 开启蓝牙搜索
|
||||
void enableBluetoothSearch({
|
||||
DeviceType deviceType = DeviceType.all,
|
||||
Duration searchTime = const Duration(seconds: 30),
|
||||
required void Function(ScanDeviceInfo device) onDeviceFound,
|
||||
}) async {
|
||||
// 如果正在搜索中,直接返回
|
||||
var isScanningNow = FlutterBluePlus.isScanningNow;
|
||||
if (isScanningNow) {
|
||||
AppLogger.warn('正处于搜索状态,请勿重复搜索');
|
||||
return;
|
||||
}
|
||||
if (_bluetoothAdapterState == BluetoothAdapterState.on) {
|
||||
FlutterBluePlus.startScan(timeout: searchTime);
|
||||
|
||||
/// 取消旧的订阅,防止重复
|
||||
_scanResultSubscription?.cancel();
|
||||
_discoveredDevices.clear();
|
||||
|
||||
/// 监听搜索到的设备
|
||||
_scanResultSubscription = FlutterBluePlus.onScanResults.listen(
|
||||
(List<ScanResult> results) {
|
||||
for (var result in results) {
|
||||
var device = result.device;
|
||||
final deviceId = device.remoteId.toString();
|
||||
final platformName = device.platformName;
|
||||
var serviceUuids = result.advertisementData.serviceUuids;
|
||||
|
||||
// ✅ 只有新设备才回调
|
||||
if (!_discoveredDevices.containsKey(deviceId) && platformName.isNotEmpty) {
|
||||
_discoveredDevices[deviceId] = result;
|
||||
bool pairStatus = false;
|
||||
bool hasNewEvent = false;
|
||||
for (var uuid in serviceUuids) {
|
||||
String uuidStr = uuid.toString().replaceAll('-', '');
|
||||
if (uuidStr.length == 8) {
|
||||
var pairStatusStr = uuidStr.substring(4, 6);
|
||||
var hasNewEventStr = uuidStr.substring(6, 8);
|
||||
pairStatus = pairStatusStr == '01';
|
||||
hasNewEvent = hasNewEventStr == '01';
|
||||
var scanDeviceInfo = ScanDeviceInfo(
|
||||
isBinding: pairStatus,
|
||||
advName: device.advName,
|
||||
rawDeviceInfo: result,
|
||||
hasNewEvent: hasNewEvent,
|
||||
);
|
||||
onDeviceFound.call(scanDeviceInfo);
|
||||
} else if (uuidStr.length == 32) {
|
||||
var pairStatusStr = uuidStr.substring(26, 28);
|
||||
pairStatus = pairStatusStr == '00'; // 第4、5位(索引3和4)
|
||||
int statusValue = int.parse(pairStatusStr, radix: 16);
|
||||
// 提取 byte0(配对状态:第1位)
|
||||
int byte0 = (statusValue >> 0) & 0x01; // 取最低位
|
||||
// 提取 byte1(事件状态:第2位)
|
||||
int byte1 = (statusValue >> 1) & 0x01; // 取次低位
|
||||
// 判断是否未配对
|
||||
pairStatus = (byte0 == 1);
|
||||
// 判断是否有新事件
|
||||
hasNewEvent = (byte1 == 1);
|
||||
var scanDeviceInfo = ScanDeviceInfo(
|
||||
isBinding: pairStatus,
|
||||
advName: device.advName,
|
||||
rawDeviceInfo: result,
|
||||
hasNewEvent: hasNewEvent,
|
||||
);
|
||||
onDeviceFound.call(scanDeviceInfo);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 可选:更新 RSSI
|
||||
_discoveredDevices[deviceId] = result;
|
||||
}
|
||||
}
|
||||
},
|
||||
onError: (e) => AppLogger.error('搜索设备时遇到错误:' + e),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 停止扫描
|
||||
void stopBluetoothSearch() async {
|
||||
var isScanningNow = FlutterBluePlus.isScanningNow;
|
||||
if (isScanningNow) {
|
||||
FlutterBluePlus.stopScan();
|
||||
|
||||
/// 清空搜索到的设备
|
||||
_discoveredDevices.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void cancel() {
|
||||
/// 销毁蓝牙适配器监听
|
||||
_adapterStateSubscription?.cancel();
|
||||
_scanResultSubscription?.cancel();
|
||||
_bluetoothAdapterState = BluetoothAdapterState.unknown;
|
||||
}
|
||||
}
|
||||
26
lib/ble/model/scan_device_info.dart
Normal file
26
lib/ble/model/scan_device_info.dart
Normal file
@ -0,0 +1,26 @@
|
||||
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
|
||||
|
||||
class ScanDeviceInfo {
|
||||
//设备名字
|
||||
final String advName;
|
||||
|
||||
// 是否绑定
|
||||
final bool isBinding;
|
||||
// 是否有新事件
|
||||
final bool hasNewEvent;
|
||||
|
||||
// 原始设备信息
|
||||
final ScanResult rawDeviceInfo;
|
||||
|
||||
ScanDeviceInfo({
|
||||
required this.advName,
|
||||
this.isBinding = false, // 默认值为 false
|
||||
this.hasNewEvent = false, // 默认值为 false
|
||||
required this.rawDeviceInfo,
|
||||
});
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ScanDeviceInfo{advName: $advName, isBinding: $isBinding, hasNewEvent: $hasNewEvent,rawDeviceInfo:$rawDeviceInfo}';
|
||||
}
|
||||
}
|
||||
6
lib/common/constant/app_toast_messages.dart
Normal file
6
lib/common/constant/app_toast_messages.dart
Normal file
@ -0,0 +1,6 @@
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class AppToastMessages {
|
||||
static String notLocationPermission = '蓝牙权限被拒绝,请在应用设置页面开启蓝牙权限'.tr;
|
||||
static String notBluetoothPermissions = '蓝牙权限被拒绝,请在应用设置页面开启蓝牙权限'.tr;
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
class DeviceType {
|
||||
static const all = DeviceType('0', '所有');
|
||||
static const lock = DeviceType('1', '锁');
|
||||
static const gateway = DeviceType('2', '网关');
|
||||
static const attendanceMachine = DeviceType('3', '考勤机');
|
||||
@ -10,6 +11,7 @@ class DeviceType {
|
||||
// 支持通过字符串值查找枚举实例
|
||||
static DeviceType? fromValue(String? value) {
|
||||
return {
|
||||
'0': all,
|
||||
'1': lock,
|
||||
'2': gateway,
|
||||
'3': attendanceMachine,
|
||||
|
||||
5
lib/common/utils/device_utils.dart
Normal file
5
lib/common/utils/device_utils.dart
Normal file
@ -0,0 +1,5 @@
|
||||
class DeviceUtils {
|
||||
static bool checkDeviceServiceUuids() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -9,16 +9,15 @@ class SharedPreferencesUtils {
|
||||
_prefs = await SharedPreferences.getInstance();
|
||||
}
|
||||
|
||||
|
||||
/// 存储带过期时间的字符串
|
||||
/// [key] 键
|
||||
/// [value] 值
|
||||
/// [expiry] 过期时间(如 Duration(hours: 1))
|
||||
static Future<bool> setStringWithExpiry(
|
||||
String key,
|
||||
String value,
|
||||
Duration expiry,
|
||||
) async {
|
||||
String key,
|
||||
String value,
|
||||
Duration expiry,
|
||||
) async {
|
||||
final prefs = _prefs;
|
||||
if (prefs == null) return false;
|
||||
|
||||
@ -65,7 +64,6 @@ class SharedPreferencesUtils {
|
||||
await prefs.remove(expiryKey);
|
||||
}
|
||||
|
||||
|
||||
static Future<bool> setString(String key, String value) async {
|
||||
return _prefs?.setString(key, value) ?? Future.value(false);
|
||||
}
|
||||
@ -73,4 +71,13 @@ class SharedPreferencesUtils {
|
||||
static String? getString(String key) {
|
||||
return _prefs?.getString(key);
|
||||
}
|
||||
}
|
||||
|
||||
// bool
|
||||
static Future<void> setBool(String key, dynamic value) async {
|
||||
_prefs?.setBool(key, value);
|
||||
}
|
||||
|
||||
static Future<bool?> getBool(String key) async {
|
||||
return _prefs?.getBool(key);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,11 +1,6 @@
|
||||
enum Flavor {
|
||||
sky,
|
||||
skyPre,
|
||||
skyRelease,
|
||||
xhj,
|
||||
xhjPre,
|
||||
xhjRelease,
|
||||
}
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
enum Flavor { sky, xhj }
|
||||
|
||||
class F {
|
||||
static late final Flavor appFlavor;
|
||||
@ -15,34 +10,29 @@ class F {
|
||||
static String get title {
|
||||
switch (appFlavor) {
|
||||
case Flavor.sky:
|
||||
return '星勤-sky-dev';
|
||||
case Flavor.skyPre:
|
||||
return '星勤-sky-pre';
|
||||
case Flavor.skyRelease:
|
||||
return '星勤-sky-release';
|
||||
return '星勤-sky';
|
||||
case Flavor.xhj:
|
||||
return '星勤-xhj-dev';
|
||||
case Flavor.xhjPre:
|
||||
return '星勤-xhj-pre';
|
||||
case Flavor.xhjRelease:
|
||||
return '星勤-xhj-release';
|
||||
return '星勤-xhj';
|
||||
}
|
||||
}
|
||||
|
||||
static String get apiHost {
|
||||
switch (appFlavor) {
|
||||
case Flavor.sky:
|
||||
return 'http://192.168.1.136/api';
|
||||
case Flavor.skyPre:
|
||||
return 'https://loacl.work.star-lock.cn/api';
|
||||
case Flavor.skyRelease:
|
||||
return 'https://loacl.work.star-lock.cn/api';
|
||||
case Flavor.xhj:
|
||||
return 'https://loacl.work.star-lock.cn/api';
|
||||
case Flavor.xhjPre:
|
||||
return 'https://loacl.work.star-lock.cn/api';
|
||||
case Flavor.xhjRelease:
|
||||
return 'https://loacl.work.star-lock.cn/api';
|
||||
if (kReleaseMode) {
|
||||
// Release环境的API地址
|
||||
switch (appFlavor) {
|
||||
case Flavor.sky:
|
||||
return 'https://api.skychip.top/api'; // 生产环境API
|
||||
case Flavor.xhj:
|
||||
return 'https://api.xhjcn.ltd/api'; // 生产环境API
|
||||
}
|
||||
} else {
|
||||
// Debug/Profile环境的API地址(开发环境)
|
||||
switch (appFlavor) {
|
||||
case Flavor.sky:
|
||||
return 'http://197o136q43.oicp.vip/api';
|
||||
case Flavor.xhj:
|
||||
return 'https://loacl.work.star-lock.cn/api';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -50,16 +40,8 @@ class F {
|
||||
switch (appFlavor) {
|
||||
case Flavor.sky:
|
||||
return '0JLrKMhBSSHH0VlRLcIko5NrESfzDJ8B';
|
||||
case Flavor.skyPre:
|
||||
return '0JLrKMhBSSHH0VlRLcIko5NrESfzDJ8B';
|
||||
case Flavor.skyRelease:
|
||||
return '0JLrKMhBSSHH0VlRLcIko5NrESfzDJ8B';
|
||||
case Flavor.xhj:
|
||||
return '0JLrKMhBSSHH0VlRLcIko5NrESfzDJ8B';
|
||||
case Flavor.xhjPre:
|
||||
return '0JLrKMhBSSHH0VlRLcIko5NrESfzDJ8B';
|
||||
case Flavor.xhjRelease:
|
||||
return '0JLrKMhBSSHH0VlRLcIko5NrESfzDJ8B';
|
||||
}
|
||||
}
|
||||
|
||||
@ -67,32 +49,28 @@ class F {
|
||||
switch (appFlavor) {
|
||||
case Flavor.sky:
|
||||
return 'KS8KvZKPKKHgsoDbcfQCCScvyyqeolDt';
|
||||
case Flavor.skyPre:
|
||||
return 'KS8KvZKPKKHgsoDbcfQCCScvyyqeolDt';
|
||||
case Flavor.skyRelease:
|
||||
return 'KS8KvZKPKKHgsoDbcfQCCScvyyqeolDt';
|
||||
case Flavor.xhj:
|
||||
return 'KS8KvZKPKKHgsoDbcfQCCScvyyqeolDt';
|
||||
case Flavor.xhjPre:
|
||||
return 'KS8KvZKPKKHgsoDbcfQCCScvyyqeolDt';
|
||||
case Flavor.xhjRelease:
|
||||
return 'KS8KvZKPKKHgsoDbcfQCCScvyyqeolDt';
|
||||
}
|
||||
}
|
||||
|
||||
static String get starCloudUrl {
|
||||
switch (appFlavor) {
|
||||
case Flavor.sky:
|
||||
return 'http://local.cloud.star-lock.cn';
|
||||
case Flavor.skyPre:
|
||||
return 'http://local.cloud.star-lock.cn';
|
||||
case Flavor.skyRelease:
|
||||
return 'http://local.cloud.star-lock.cn';
|
||||
case Flavor.xhj:
|
||||
return 'http://local.cloud.star-lock.cn';
|
||||
case Flavor.xhjPre:
|
||||
return 'http://local.cloud.star-lock.cn';
|
||||
case Flavor.xhjRelease:
|
||||
return 'http://local.cloud.star-lock.cn';
|
||||
if (kReleaseMode) {
|
||||
// Release环境的StarCloud地址
|
||||
switch (appFlavor) {
|
||||
case Flavor.sky:
|
||||
return 'https://cloud.star-lock.cn'; // 生产环境
|
||||
case Flavor.xhj:
|
||||
return 'https://cloud.star-lock.cn'; // 生产环境
|
||||
}
|
||||
} else {
|
||||
// Debug/Profile环境的StarCloud地址(开发环境)
|
||||
switch (appFlavor) {
|
||||
case Flavor.sky:
|
||||
return 'http://192.168.1.121:8111';
|
||||
case Flavor.xhj:
|
||||
return 'http://local.cloud.star-lock.cn';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,60 +1,63 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:starwork_flutter/base/app_logger.dart';
|
||||
import 'package:starwork_flutter/base/app_permission.dart';
|
||||
import 'package:starwork_flutter/base/base_controller.dart';
|
||||
import 'package:starwork_flutter/ble/ble_service.dart';
|
||||
import 'package:starwork_flutter/ble/model/scan_device_info.dart';
|
||||
import 'package:starwork_flutter/common/constant/app_toast_messages.dart';
|
||||
import 'package:starwork_flutter/routes/app_routes.dart';
|
||||
import 'package:starwork_flutter/views/device/searchDevice/search_device_model.dart';
|
||||
|
||||
class SearchDeviceController extends BaseController {
|
||||
// 搜索状态管理
|
||||
final RxBool _isSearching = false.obs;
|
||||
final RxBool isSearching = BleService().isScanningNow.obs;
|
||||
|
||||
// 设备列表管理
|
||||
final RxList<SearchDeviceItem> deviceList = <SearchDeviceItem>[].obs;
|
||||
final RxList<ScanDeviceInfo> deviceList = <ScanDeviceInfo>[].obs;
|
||||
|
||||
// Getter
|
||||
bool get isSearching => _isSearching.value;
|
||||
late void Function(ScanDeviceInfo device) onDeviceFound;
|
||||
|
||||
@override
|
||||
void onInit() async {
|
||||
super.onInit();
|
||||
_initializeDevices();
|
||||
var locationPermission = await AppPermission.requestLocationPermission();
|
||||
if (!locationPermission) {
|
||||
showToast(AppToastMessages.notLocationPermission);
|
||||
return;
|
||||
}
|
||||
var bluetoothPermissions = await AppPermission.requestBluetoothPermissions();
|
||||
if (!bluetoothPermissions) {
|
||||
showToast(AppToastMessages.notBluetoothPermissions);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化设备数据
|
||||
void _initializeDevices() {
|
||||
deviceList.value = [
|
||||
SearchDeviceItem(
|
||||
id: 'TMH_4564sa121dfsda',
|
||||
name: 'TMH_4564sa121dfsda',
|
||||
deviceType: '门禁设备',
|
||||
isOnline: true,
|
||||
),
|
||||
SearchDeviceItem(
|
||||
id: 'TMH_4564sa121dfsdv',
|
||||
name: 'TMH_4564sa121dfsdv',
|
||||
deviceType: '门禁设备',
|
||||
isOnline: true,
|
||||
),
|
||||
];
|
||||
@override
|
||||
void onReady() {
|
||||
super.onReady();
|
||||
// 启动搜索
|
||||
BleService().enableBluetoothSearch(onDeviceFound: _onDeviceFound);
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
// 停止搜索
|
||||
BleService().stopBluetoothSearch();
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
/// 搜索结果回调
|
||||
void _onDeviceFound(ScanDeviceInfo device) {
|
||||
deviceList.add(device);
|
||||
deviceList.refresh();
|
||||
}
|
||||
|
||||
// 刷新设备数据
|
||||
Future<void> refreshDevices() async {
|
||||
// 设置搜索状态
|
||||
_isSearching.value = true;
|
||||
showLoading();
|
||||
|
||||
BleService().stopBluetoothSearch();
|
||||
deviceList.clear();
|
||||
// 模拟网络请求延迟
|
||||
await Future.delayed(const Duration(seconds: 2));
|
||||
|
||||
// 这里可以添加实际的设备搜索API调用
|
||||
// 模拟刷新数据
|
||||
_initializeDevices();
|
||||
|
||||
// 结束搜索状态
|
||||
_isSearching.value = false;
|
||||
|
||||
hideLoading();
|
||||
print('设备搜索刷新完成');
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
BleService().enableBluetoothSearch(onDeviceFound: _onDeviceFound);
|
||||
}
|
||||
|
||||
// 连接设备
|
||||
|
||||
@ -1,16 +0,0 @@
|
||||
|
||||
|
||||
// 设备模型类
|
||||
class SearchDeviceItem {
|
||||
final String id;
|
||||
final String name;
|
||||
final String deviceType;
|
||||
final bool isOnline;
|
||||
|
||||
SearchDeviceItem({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.deviceType,
|
||||
required this.isOnline,
|
||||
});
|
||||
}
|
||||
@ -3,8 +3,8 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/src/widgets/framework.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:starwork_flutter/ble/model/scan_device_info.dart';
|
||||
import 'package:starwork_flutter/views/device/searchDevice/search_device_controller.dart';
|
||||
import 'package:starwork_flutter/views/device/searchDevice/widget/search_device_rotating_icon_widget.dart';
|
||||
|
||||
class SearchDeviceView extends GetView<SearchDeviceController> {
|
||||
const SearchDeviceView({super.key});
|
||||
@ -17,23 +17,13 @@ class SearchDeviceView extends GetView<SearchDeviceController> {
|
||||
title: Row(
|
||||
children: [
|
||||
Text(
|
||||
'搜索设备中'.tr,
|
||||
'搜索设备'.tr,
|
||||
style: TextStyle(
|
||||
fontSize: 18.sp,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: 8.w,
|
||||
),
|
||||
Obx(
|
||||
() => SearchDeviceRotatingIconWidget(
|
||||
isRotating: controller.isSearching,
|
||||
radius: 10.w,
|
||||
rotationDuration: 1500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
@ -53,9 +43,7 @@ class SearchDeviceView extends GetView<SearchDeviceController> {
|
||||
body: Column(
|
||||
children: [
|
||||
// 设备列表
|
||||
Expanded(
|
||||
child: _buildRefreshableDeviceList(),
|
||||
),
|
||||
_buildRefreshableDeviceList(),
|
||||
// 固定在底部的提示信息
|
||||
_buildDeviceTip(),
|
||||
],
|
||||
@ -94,18 +82,43 @@ class SearchDeviceView extends GetView<SearchDeviceController> {
|
||||
// 控制滚动物理特性
|
||||
parent: BouncingScrollPhysics(), // 添加弹性滚动效果
|
||||
),
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
minHeight: MediaQuery.of(Get.context!).size.height -
|
||||
MediaQuery.of(Get.context!).padding.top -
|
||||
kToolbarHeight -
|
||||
60.h, // 减去AppBar高度、状态栏高度和底部提示区域高度
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
_buildDeviceList(),
|
||||
SizedBox(height: 16.h),
|
||||
],
|
||||
child: Obx(
|
||||
() => ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
minHeight: MediaQuery.of(Get.context!).size.height -
|
||||
MediaQuery.of(Get.context!).padding.top -
|
||||
kToolbarHeight -
|
||||
60.h, // 减去AppBar高度、状态栏高度和底部提示区域高度
|
||||
),
|
||||
child: controller.deviceList.isNotEmpty
|
||||
? Column(
|
||||
children: [
|
||||
_buildDeviceList(),
|
||||
SizedBox(height: 16.h),
|
||||
],
|
||||
)
|
||||
: Center(
|
||||
child: Column(
|
||||
children: [
|
||||
CircularProgressIndicator(
|
||||
strokeWidth: 2.w,
|
||||
valueColor: const AlwaysStoppedAnimation<Color>(
|
||||
Colors.blue,
|
||||
),
|
||||
backgroundColor: Colors.grey[200],
|
||||
),
|
||||
SizedBox(height: 20.h),
|
||||
Text(
|
||||
'正在搜索附近设备...\n请确保蓝牙处于正常状态'.tr,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 12.sp,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -134,8 +147,7 @@ class SearchDeviceView extends GetView<SearchDeviceController> {
|
||||
if (index == 0) SizedBox(height: 10.h),
|
||||
_buildItem(device: device, index: index),
|
||||
// 如果不是最后一项,显示间距
|
||||
if (index < controller.deviceList.length - 1)
|
||||
SizedBox(height: 10.h),
|
||||
if (index < controller.deviceList.length - 1) SizedBox(height: 10.h),
|
||||
],
|
||||
);
|
||||
}).toList(),
|
||||
@ -159,7 +171,7 @@ class SearchDeviceView extends GetView<SearchDeviceController> {
|
||||
);
|
||||
}
|
||||
|
||||
_buildItem({required device, required int index}) {
|
||||
_buildItem({required ScanDeviceInfo device, required int index}) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
controller.connectingDevices();
|
||||
@ -186,7 +198,7 @@ class SearchDeviceView extends GetView<SearchDeviceController> {
|
||||
width: 8.w,
|
||||
),
|
||||
Text(
|
||||
device.name,
|
||||
device.advName,
|
||||
style: TextStyle(
|
||||
fontSize: 16.sp,
|
||||
color: Colors.black87,
|
||||
@ -198,7 +210,7 @@ class SearchDeviceView extends GetView<SearchDeviceController> {
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
// 处理添加设备事件
|
||||
print('添加设备 ${device.name}');
|
||||
print('添加设备 ${device.advName}');
|
||||
// 这里可以添加具体的添加设备逻辑
|
||||
},
|
||||
child: const Icon(
|
||||
|
||||
@ -1,100 +0,0 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
|
||||
class SearchDeviceRotatingIconWidget extends StatefulWidget {
|
||||
/// 是否正在旋转
|
||||
final bool isRotating;
|
||||
/// 旋转速度(毫秒)
|
||||
final int rotationDuration;
|
||||
/// 图标颜色
|
||||
final Color? color;
|
||||
/// 图标半径
|
||||
final double? radius;
|
||||
|
||||
const SearchDeviceRotatingIconWidget({
|
||||
super.key,
|
||||
required this.isRotating,
|
||||
this.rotationDuration = 1000,
|
||||
this.color = Colors.grey,
|
||||
this.radius,
|
||||
});
|
||||
|
||||
@override
|
||||
_SearchDeviceRotatingIconWidgetState createState() => _SearchDeviceRotatingIconWidgetState();
|
||||
}
|
||||
|
||||
class _SearchDeviceRotatingIconWidgetState extends State<SearchDeviceRotatingIconWidget>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late AnimationController _animationController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_animationController = AnimationController(
|
||||
duration: Duration(milliseconds: widget.rotationDuration),
|
||||
vsync: this,
|
||||
);
|
||||
|
||||
// 根据初始状态决定是否开始动画
|
||||
if (widget.isRotating) {
|
||||
_animationController.repeat();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(SearchDeviceRotatingIconWidget oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
|
||||
// 当isRotating状态发生变化时,控制动画的开始和停止
|
||||
if (widget.isRotating != oldWidget.isRotating) {
|
||||
if (widget.isRotating) {
|
||||
_startRotation();
|
||||
} else {
|
||||
_stopRotation();
|
||||
}
|
||||
}
|
||||
|
||||
// 当旋转速度发生变化时,更新动画时长
|
||||
if (widget.rotationDuration != oldWidget.rotationDuration) {
|
||||
_animationController.duration = Duration(milliseconds: widget.rotationDuration);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_animationController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
/// 开始旋转动画
|
||||
void _startRotation() {
|
||||
if (!_animationController.isAnimating) {
|
||||
_animationController.repeat();
|
||||
}
|
||||
}
|
||||
|
||||
/// 停止旋转动画
|
||||
void _stopRotation() {
|
||||
if (_animationController.isAnimating) {
|
||||
_animationController.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AnimatedBuilder(
|
||||
animation: _animationController,
|
||||
builder: (context, child) {
|
||||
return Transform.rotate(
|
||||
angle: _animationController.value * 2 * 3.14159,
|
||||
child: CupertinoActivityIndicator(
|
||||
radius: widget.radius ?? 10.w,
|
||||
color: widget.color,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -26,7 +26,6 @@ class LoginController extends BaseController {
|
||||
super.onInit();
|
||||
// 监听输入变化
|
||||
phoneController.addListener(_validateForm);
|
||||
|
||||
phoneController.text = '18269109817';
|
||||
}
|
||||
|
||||
|
||||
@ -151,7 +151,7 @@ packages:
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_blue_plus:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_blue_plus
|
||||
sha256: "1901a42ade7a8f9793a3655983ef0899565294b12a2a57a2c3e33813930f4a34"
|
||||
@ -578,13 +578,6 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.11.1"
|
||||
starcloud:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "../starcloud-sdk-flutter"
|
||||
relative: true
|
||||
source: path
|
||||
version: "0.1.2"
|
||||
stream_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@ -36,10 +36,8 @@ dependencies:
|
||||
carousel_slider: ^5.1.1
|
||||
# 气泡提示框
|
||||
super_tooltip: ^2.0.8
|
||||
# 星云flutter SDK
|
||||
starcloud:
|
||||
path: ../starcloud-sdk-flutter
|
||||
|
||||
# 蓝牙库
|
||||
flutter_blue_plus: ^1.35.7
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user