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|
|
post_install do |installer|
|
||||||
installer.pods_project.targets.each do |target|
|
installer.pods_project.targets.each do |target|
|
||||||
flutter_additional_ios_build_settings(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
|
||||||
end
|
end
|
||||||
|
|||||||
@ -8,15 +8,12 @@ PODS:
|
|||||||
- shared_preferences_foundation (0.0.1):
|
- shared_preferences_foundation (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- starcloud (0.0.1):
|
|
||||||
- Flutter
|
|
||||||
|
|
||||||
DEPENDENCIES:
|
DEPENDENCIES:
|
||||||
- Flutter (from `Flutter`)
|
- Flutter (from `Flutter`)
|
||||||
- flutter_blue_plus_darwin (from `.symlinks/plugins/flutter_blue_plus_darwin/darwin`)
|
- flutter_blue_plus_darwin (from `.symlinks/plugins/flutter_blue_plus_darwin/darwin`)
|
||||||
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
|
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
|
||||||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||||
- starcloud (from `.symlinks/plugins/starcloud/ios`)
|
|
||||||
|
|
||||||
EXTERNAL SOURCES:
|
EXTERNAL SOURCES:
|
||||||
Flutter:
|
Flutter:
|
||||||
@ -27,16 +24,13 @@ EXTERNAL SOURCES:
|
|||||||
:path: ".symlinks/plugins/permission_handler_apple/ios"
|
:path: ".symlinks/plugins/permission_handler_apple/ios"
|
||||||
shared_preferences_foundation:
|
shared_preferences_foundation:
|
||||||
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
|
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
|
||||||
starcloud:
|
|
||||||
:path: ".symlinks/plugins/starcloud/ios"
|
|
||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
||||||
flutter_blue_plus_darwin: 20a08bfeaa0f7804d524858d3d8744bcc1b6dbc3
|
flutter_blue_plus_darwin: 20a08bfeaa0f7804d524858d3d8744bcc1b6dbc3
|
||||||
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
|
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
|
||||||
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
|
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
|
||||||
starcloud: 0d2034397e2a0d81b8c187b99bd2de7371ea5b2d
|
|
||||||
|
|
||||||
PODFILE CHECKSUM: fbdae9471aca60c39ad623cd945ad8e2e75a0c21
|
PODFILE CHECKSUM: 41883e5e56033ab6e4e0f607734d753c5bb7c460
|
||||||
|
|
||||||
COCOAPODS: 1.16.2
|
COCOAPODS: 1.16.2
|
||||||
|
|||||||
@ -45,5 +45,46 @@
|
|||||||
<true/>
|
<true/>
|
||||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||||
<true/>
|
<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>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
@ -4,7 +4,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_easyloading/flutter_easyloading.dart';
|
import 'package:flutter_easyloading/flutter_easyloading.dart';
|
||||||
import 'package:get/get.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/base_api_service.dart';
|
||||||
import 'package:starwork_flutter/api/service/common_api_service.dart';
|
import 'package:starwork_flutter/api/service/common_api_service.dart';
|
||||||
import 'package:starwork_flutter/api/service/user_api_service.dart';
|
import 'package:starwork_flutter/api/service/user_api_service.dart';
|
||||||
@ -20,11 +20,7 @@ class AppInitialization {
|
|||||||
setSystemStatusBar();
|
setSystemStatusBar();
|
||||||
await SharedPreferencesUtils.init();
|
await SharedPreferencesUtils.init();
|
||||||
initEasyLoading();
|
initEasyLoading();
|
||||||
StarCloudSDK.init(
|
|
||||||
clientId: F.starCloudClientId,
|
|
||||||
clientSecret: F.starCloudSecret,
|
|
||||||
environmentUrl: F.starCloudUrl,
|
|
||||||
);
|
|
||||||
Get.put(BaseApiService());
|
Get.put(BaseApiService());
|
||||||
Get.put(CommonApiService(Get.find<BaseApiService>()));
|
Get.put(CommonApiService(Get.find<BaseApiService>()));
|
||||||
Get.put(UserApiService(Get.find<BaseApiService>()));
|
Get.put(UserApiService(Get.find<BaseApiService>()));
|
||||||
@ -49,6 +45,9 @@ class AppInitialization {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
SystemChrome.setPreferredOrientations([
|
||||||
|
DeviceOrientation.portraitUp,
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void initEasyLoading() {
|
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';
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
|
|
||||||
class AppPermission {
|
class AppPermission {
|
||||||
@ -46,26 +47,44 @@ class AppPermission {
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 请求蓝牙权限(Android 12及以上需要)
|
// 请求蓝牙权限(Android 12及以上需要)
|
||||||
static Future<bool> requestBluetoothPermissions() async {
|
static Future<bool> requestBluetoothPermissions() async {
|
||||||
try {
|
try {
|
||||||
// Android 12及以上需要的蓝牙权限
|
// Android 12及以上需要的蓝牙权限
|
||||||
List<Permission> bluetoothPermissions = [
|
List<Permission> bluetoothPermissions = [];
|
||||||
Permission.bluetoothScan,
|
|
||||||
Permission.bluetoothConnect,
|
if (Platform.isAndroid) {
|
||||||
Permission.bluetoothAdvertise,
|
// 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以下需要的位置权限
|
// Android 12以下需要的位置权限
|
||||||
List<Permission> locationPermissions = [
|
List<Permission> locationPermissions = [
|
||||||
Permission.location,
|
Permission.location,
|
||||||
Permission.locationWhenInUse,
|
Permission.locationWhenInUse,
|
||||||
];
|
];
|
||||||
|
|
||||||
bool bluetoothGranted = true;
|
bool bluetoothGranted = true;
|
||||||
bool locationGranted = true;
|
bool locationGranted = true;
|
||||||
|
|
||||||
// 检查并请求蓝牙权限(Android 12+)
|
// 检查并请求蓝牙权限(Android 12+)
|
||||||
Map<Permission, PermissionStatus> bluetoothStatuses = await bluetoothPermissions.request();
|
Map<Permission, PermissionStatus> bluetoothStatuses = await bluetoothPermissions.request();
|
||||||
for (var status in bluetoothStatuses.values) {
|
for (var status in bluetoothStatuses.values) {
|
||||||
@ -74,7 +93,7 @@ class AppPermission {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查并请求位置权限(Android 12以下或蓝牙扫描需要)
|
// 检查并请求位置权限(Android 12以下或蓝牙扫描需要)
|
||||||
Map<Permission, PermissionStatus> locationStatuses = await locationPermissions.request();
|
Map<Permission, PermissionStatus> locationStatuses = await locationPermissions.request();
|
||||||
for (var status in locationStatuses.values) {
|
for (var status in locationStatuses.values) {
|
||||||
@ -83,23 +102,22 @@ class AppPermission {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool hasPermission = bluetoothGranted && locationGranted;
|
bool hasPermission = bluetoothGranted && locationGranted;
|
||||||
|
|
||||||
if (hasPermission) {
|
if (hasPermission) {
|
||||||
print("蓝牙权限已授予");
|
print("蓝牙权限已授予");
|
||||||
} else {
|
} else {
|
||||||
print("蓝牙权限被拒绝");
|
print("蓝牙权限被拒绝");
|
||||||
}
|
}
|
||||||
|
|
||||||
return hasPermission;
|
return hasPermission;
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print("请求蓝牙权限失败: $e");
|
print("请求蓝牙权限失败: $e");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查蓝牙扫描权限
|
// 检查蓝牙扫描权限
|
||||||
static Future<bool> checkBluetoothScanPermission() async {
|
static Future<bool> checkBluetoothScanPermission() async {
|
||||||
try {
|
try {
|
||||||
@ -110,7 +128,7 @@ class AppPermission {
|
|||||||
return await checkLocationPermission();
|
return await checkLocationPermission();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查蓝牙连接权限
|
// 检查蓝牙连接权限
|
||||||
static Future<bool> checkBluetoothConnectPermission() async {
|
static Future<bool> checkBluetoothConnectPermission() async {
|
||||||
try {
|
try {
|
||||||
@ -122,7 +140,7 @@ class AppPermission {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查蓝牙广播权限
|
// 检查蓝牙广播权限
|
||||||
static Future<bool> checkBluetoothAdvertisePermission() async {
|
static Future<bool> checkBluetoothAdvertisePermission() async {
|
||||||
try {
|
try {
|
||||||
@ -134,18 +152,18 @@ class AppPermission {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查位置权限(Android 12以下蓝牙扫描需要)
|
// 检查位置权限(Android 12以下蓝牙扫描需要)
|
||||||
static Future<bool> checkLocationPermission() async {
|
static Future<bool> checkLocationPermission() async {
|
||||||
var status = await Permission.location.status;
|
var status = await Permission.location.status;
|
||||||
return status == PermissionStatus.granted;
|
return status == PermissionStatus.granted;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 请求位置权限
|
// 请求位置权限
|
||||||
static Future<bool> requestLocationPermission() async {
|
static Future<bool> requestLocationPermission() async {
|
||||||
try {
|
try {
|
||||||
final PermissionStatus status = await Permission.location.request();
|
final PermissionStatus status = await Permission.location.request();
|
||||||
|
|
||||||
if (status == PermissionStatus.granted) {
|
if (status == PermissionStatus.granted) {
|
||||||
print("位置权限已授予");
|
print("位置权限已授予");
|
||||||
return true;
|
return true;
|
||||||
@ -154,24 +172,30 @@ class AppPermission {
|
|||||||
return false;
|
return false;
|
||||||
} else if (status.isPermanentlyDenied) {
|
} else if (status.isPermanentlyDenied) {
|
||||||
print("位置权限被永久拒绝,跳转到设置页面");
|
print("位置权限被永久拒绝,跳转到设置页面");
|
||||||
openAppSettings();
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print("请求位置权限失败: $e");
|
print("请求位置权限失败: $e");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查所有蓝牙相关权限
|
// 检查所有蓝牙相关权限
|
||||||
static Future<bool> checkAllBluetoothPermissions() async {
|
static Future<bool> checkAllBluetoothPermissions() async {
|
||||||
bool scanPermission = await checkBluetoothScanPermission();
|
if (Platform.isIOS) {
|
||||||
bool connectPermission = await checkBluetoothConnectPermission();
|
// iOS主要检查位置权限,蓝牙权限由系统自动处理
|
||||||
bool advertisePermission = await checkBluetoothAdvertisePermission();
|
bool locationPermission = await checkLocationPermission();
|
||||||
bool locationPermission = await checkLocationPermission();
|
return locationPermission;
|
||||||
|
} else {
|
||||||
return scanPermission && connectPermission && advertisePermission && locationPermission;
|
// 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 {
|
class DeviceType {
|
||||||
|
static const all = DeviceType('0', '所有');
|
||||||
static const lock = DeviceType('1', '锁');
|
static const lock = DeviceType('1', '锁');
|
||||||
static const gateway = DeviceType('2', '网关');
|
static const gateway = DeviceType('2', '网关');
|
||||||
static const attendanceMachine = DeviceType('3', '考勤机');
|
static const attendanceMachine = DeviceType('3', '考勤机');
|
||||||
@ -10,6 +11,7 @@ class DeviceType {
|
|||||||
// 支持通过字符串值查找枚举实例
|
// 支持通过字符串值查找枚举实例
|
||||||
static DeviceType? fromValue(String? value) {
|
static DeviceType? fromValue(String? value) {
|
||||||
return {
|
return {
|
||||||
|
'0': all,
|
||||||
'1': lock,
|
'1': lock,
|
||||||
'2': gateway,
|
'2': gateway,
|
||||||
'3': attendanceMachine,
|
'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();
|
_prefs = await SharedPreferences.getInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// 存储带过期时间的字符串
|
/// 存储带过期时间的字符串
|
||||||
/// [key] 键
|
/// [key] 键
|
||||||
/// [value] 值
|
/// [value] 值
|
||||||
/// [expiry] 过期时间(如 Duration(hours: 1))
|
/// [expiry] 过期时间(如 Duration(hours: 1))
|
||||||
static Future<bool> setStringWithExpiry(
|
static Future<bool> setStringWithExpiry(
|
||||||
String key,
|
String key,
|
||||||
String value,
|
String value,
|
||||||
Duration expiry,
|
Duration expiry,
|
||||||
) async {
|
) async {
|
||||||
final prefs = _prefs;
|
final prefs = _prefs;
|
||||||
if (prefs == null) return false;
|
if (prefs == null) return false;
|
||||||
|
|
||||||
@ -65,7 +64,6 @@ class SharedPreferencesUtils {
|
|||||||
await prefs.remove(expiryKey);
|
await prefs.remove(expiryKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static Future<bool> setString(String key, String value) async {
|
static Future<bool> setString(String key, String value) async {
|
||||||
return _prefs?.setString(key, value) ?? Future.value(false);
|
return _prefs?.setString(key, value) ?? Future.value(false);
|
||||||
}
|
}
|
||||||
@ -73,4 +71,13 @@ class SharedPreferencesUtils {
|
|||||||
static String? getString(String key) {
|
static String? getString(String key) {
|
||||||
return _prefs?.getString(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 {
|
import 'package:flutter/foundation.dart';
|
||||||
sky,
|
|
||||||
skyPre,
|
enum Flavor { sky, xhj }
|
||||||
skyRelease,
|
|
||||||
xhj,
|
|
||||||
xhjPre,
|
|
||||||
xhjRelease,
|
|
||||||
}
|
|
||||||
|
|
||||||
class F {
|
class F {
|
||||||
static late final Flavor appFlavor;
|
static late final Flavor appFlavor;
|
||||||
@ -15,34 +10,29 @@ class F {
|
|||||||
static String get title {
|
static String get title {
|
||||||
switch (appFlavor) {
|
switch (appFlavor) {
|
||||||
case Flavor.sky:
|
case Flavor.sky:
|
||||||
return '星勤-sky-dev';
|
return '星勤-sky';
|
||||||
case Flavor.skyPre:
|
|
||||||
return '星勤-sky-pre';
|
|
||||||
case Flavor.skyRelease:
|
|
||||||
return '星勤-sky-release';
|
|
||||||
case Flavor.xhj:
|
case Flavor.xhj:
|
||||||
return '星勤-xhj-dev';
|
return '星勤-xhj';
|
||||||
case Flavor.xhjPre:
|
|
||||||
return '星勤-xhj-pre';
|
|
||||||
case Flavor.xhjRelease:
|
|
||||||
return '星勤-xhj-release';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static String get apiHost {
|
static String get apiHost {
|
||||||
switch (appFlavor) {
|
if (kReleaseMode) {
|
||||||
case Flavor.sky:
|
// Release环境的API地址
|
||||||
return 'http://192.168.1.136/api';
|
switch (appFlavor) {
|
||||||
case Flavor.skyPre:
|
case Flavor.sky:
|
||||||
return 'https://loacl.work.star-lock.cn/api';
|
return 'https://api.skychip.top/api'; // 生产环境API
|
||||||
case Flavor.skyRelease:
|
case Flavor.xhj:
|
||||||
return 'https://loacl.work.star-lock.cn/api';
|
return 'https://api.xhjcn.ltd/api'; // 生产环境API
|
||||||
case Flavor.xhj:
|
}
|
||||||
return 'https://loacl.work.star-lock.cn/api';
|
} else {
|
||||||
case Flavor.xhjPre:
|
// Debug/Profile环境的API地址(开发环境)
|
||||||
return 'https://loacl.work.star-lock.cn/api';
|
switch (appFlavor) {
|
||||||
case Flavor.xhjRelease:
|
case Flavor.sky:
|
||||||
return 'https://loacl.work.star-lock.cn/api';
|
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) {
|
switch (appFlavor) {
|
||||||
case Flavor.sky:
|
case Flavor.sky:
|
||||||
return '0JLrKMhBSSHH0VlRLcIko5NrESfzDJ8B';
|
return '0JLrKMhBSSHH0VlRLcIko5NrESfzDJ8B';
|
||||||
case Flavor.skyPre:
|
|
||||||
return '0JLrKMhBSSHH0VlRLcIko5NrESfzDJ8B';
|
|
||||||
case Flavor.skyRelease:
|
|
||||||
return '0JLrKMhBSSHH0VlRLcIko5NrESfzDJ8B';
|
|
||||||
case Flavor.xhj:
|
case Flavor.xhj:
|
||||||
return '0JLrKMhBSSHH0VlRLcIko5NrESfzDJ8B';
|
return '0JLrKMhBSSHH0VlRLcIko5NrESfzDJ8B';
|
||||||
case Flavor.xhjPre:
|
|
||||||
return '0JLrKMhBSSHH0VlRLcIko5NrESfzDJ8B';
|
|
||||||
case Flavor.xhjRelease:
|
|
||||||
return '0JLrKMhBSSHH0VlRLcIko5NrESfzDJ8B';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,32 +49,28 @@ class F {
|
|||||||
switch (appFlavor) {
|
switch (appFlavor) {
|
||||||
case Flavor.sky:
|
case Flavor.sky:
|
||||||
return 'KS8KvZKPKKHgsoDbcfQCCScvyyqeolDt';
|
return 'KS8KvZKPKKHgsoDbcfQCCScvyyqeolDt';
|
||||||
case Flavor.skyPre:
|
|
||||||
return 'KS8KvZKPKKHgsoDbcfQCCScvyyqeolDt';
|
|
||||||
case Flavor.skyRelease:
|
|
||||||
return 'KS8KvZKPKKHgsoDbcfQCCScvyyqeolDt';
|
|
||||||
case Flavor.xhj:
|
case Flavor.xhj:
|
||||||
return 'KS8KvZKPKKHgsoDbcfQCCScvyyqeolDt';
|
return 'KS8KvZKPKKHgsoDbcfQCCScvyyqeolDt';
|
||||||
case Flavor.xhjPre:
|
|
||||||
return 'KS8KvZKPKKHgsoDbcfQCCScvyyqeolDt';
|
|
||||||
case Flavor.xhjRelease:
|
|
||||||
return 'KS8KvZKPKKHgsoDbcfQCCScvyyqeolDt';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static String get starCloudUrl {
|
static String get starCloudUrl {
|
||||||
switch (appFlavor) {
|
if (kReleaseMode) {
|
||||||
case Flavor.sky:
|
// Release环境的StarCloud地址
|
||||||
return 'http://local.cloud.star-lock.cn';
|
switch (appFlavor) {
|
||||||
case Flavor.skyPre:
|
case Flavor.sky:
|
||||||
return 'http://local.cloud.star-lock.cn';
|
return 'https://cloud.star-lock.cn'; // 生产环境
|
||||||
case Flavor.skyRelease:
|
case Flavor.xhj:
|
||||||
return 'http://local.cloud.star-lock.cn';
|
return 'https://cloud.star-lock.cn'; // 生产环境
|
||||||
case Flavor.xhj:
|
}
|
||||||
return 'http://local.cloud.star-lock.cn';
|
} else {
|
||||||
case Flavor.xhjPre:
|
// Debug/Profile环境的StarCloud地址(开发环境)
|
||||||
return 'http://local.cloud.star-lock.cn';
|
switch (appFlavor) {
|
||||||
case Flavor.xhjRelease:
|
case Flavor.sky:
|
||||||
return 'http://local.cloud.star-lock.cn';
|
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: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/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/routes/app_routes.dart';
|
||||||
import 'package:starwork_flutter/views/device/searchDevice/search_device_model.dart';
|
|
||||||
|
|
||||||
class SearchDeviceController extends BaseController {
|
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
|
late void Function(ScanDeviceInfo device) onDeviceFound;
|
||||||
bool get isSearching => _isSearching.value;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() async {
|
void onInit() async {
|
||||||
super.onInit();
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 初始化设备数据
|
@override
|
||||||
void _initializeDevices() {
|
void onReady() {
|
||||||
deviceList.value = [
|
super.onReady();
|
||||||
SearchDeviceItem(
|
// 启动搜索
|
||||||
id: 'TMH_4564sa121dfsda',
|
BleService().enableBluetoothSearch(onDeviceFound: _onDeviceFound);
|
||||||
name: 'TMH_4564sa121dfsda',
|
}
|
||||||
deviceType: '门禁设备',
|
|
||||||
isOnline: true,
|
@override
|
||||||
),
|
void onClose() {
|
||||||
SearchDeviceItem(
|
// 停止搜索
|
||||||
id: 'TMH_4564sa121dfsdv',
|
BleService().stopBluetoothSearch();
|
||||||
name: 'TMH_4564sa121dfsdv',
|
super.onClose();
|
||||||
deviceType: '门禁设备',
|
}
|
||||||
isOnline: true,
|
|
||||||
),
|
/// 搜索结果回调
|
||||||
];
|
void _onDeviceFound(ScanDeviceInfo device) {
|
||||||
|
deviceList.add(device);
|
||||||
|
deviceList.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 刷新设备数据
|
// 刷新设备数据
|
||||||
Future<void> refreshDevices() async {
|
Future<void> refreshDevices() async {
|
||||||
// 设置搜索状态
|
BleService().stopBluetoothSearch();
|
||||||
_isSearching.value = true;
|
deviceList.clear();
|
||||||
showLoading();
|
|
||||||
|
|
||||||
// 模拟网络请求延迟
|
// 模拟网络请求延迟
|
||||||
await Future.delayed(const Duration(seconds: 2));
|
await Future.delayed(const Duration(seconds: 1));
|
||||||
|
BleService().enableBluetoothSearch(onDeviceFound: _onDeviceFound);
|
||||||
// 这里可以添加实际的设备搜索API调用
|
|
||||||
// 模拟刷新数据
|
|
||||||
_initializeDevices();
|
|
||||||
|
|
||||||
// 结束搜索状态
|
|
||||||
_isSearching.value = false;
|
|
||||||
|
|
||||||
hideLoading();
|
|
||||||
print('设备搜索刷新完成');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 连接设备
|
// 连接设备
|
||||||
|
|||||||
@ -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/src/widgets/framework.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
import 'package:get/get.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/search_device_controller.dart';
|
||||||
import 'package:starwork_flutter/views/device/searchDevice/widget/search_device_rotating_icon_widget.dart';
|
|
||||||
|
|
||||||
class SearchDeviceView extends GetView<SearchDeviceController> {
|
class SearchDeviceView extends GetView<SearchDeviceController> {
|
||||||
const SearchDeviceView({super.key});
|
const SearchDeviceView({super.key});
|
||||||
@ -17,23 +17,13 @@ class SearchDeviceView extends GetView<SearchDeviceController> {
|
|||||||
title: Row(
|
title: Row(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'搜索设备中'.tr,
|
'搜索设备'.tr,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 18.sp,
|
fontSize: 18.sp,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
color: Colors.black87,
|
color: Colors.black87,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(
|
|
||||||
width: 8.w,
|
|
||||||
),
|
|
||||||
Obx(
|
|
||||||
() => SearchDeviceRotatingIconWidget(
|
|
||||||
isRotating: controller.isSearching,
|
|
||||||
radius: 10.w,
|
|
||||||
rotationDuration: 1500,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
@ -53,9 +43,7 @@ class SearchDeviceView extends GetView<SearchDeviceController> {
|
|||||||
body: Column(
|
body: Column(
|
||||||
children: [
|
children: [
|
||||||
// 设备列表
|
// 设备列表
|
||||||
Expanded(
|
_buildRefreshableDeviceList(),
|
||||||
child: _buildRefreshableDeviceList(),
|
|
||||||
),
|
|
||||||
// 固定在底部的提示信息
|
// 固定在底部的提示信息
|
||||||
_buildDeviceTip(),
|
_buildDeviceTip(),
|
||||||
],
|
],
|
||||||
@ -94,18 +82,43 @@ class SearchDeviceView extends GetView<SearchDeviceController> {
|
|||||||
// 控制滚动物理特性
|
// 控制滚动物理特性
|
||||||
parent: BouncingScrollPhysics(), // 添加弹性滚动效果
|
parent: BouncingScrollPhysics(), // 添加弹性滚动效果
|
||||||
),
|
),
|
||||||
child: ConstrainedBox(
|
child: Obx(
|
||||||
constraints: BoxConstraints(
|
() => ConstrainedBox(
|
||||||
minHeight: MediaQuery.of(Get.context!).size.height -
|
constraints: BoxConstraints(
|
||||||
MediaQuery.of(Get.context!).padding.top -
|
minHeight: MediaQuery.of(Get.context!).size.height -
|
||||||
kToolbarHeight -
|
MediaQuery.of(Get.context!).padding.top -
|
||||||
60.h, // 减去AppBar高度、状态栏高度和底部提示区域高度
|
kToolbarHeight -
|
||||||
),
|
60.h, // 减去AppBar高度、状态栏高度和底部提示区域高度
|
||||||
child: Column(
|
),
|
||||||
children: [
|
child: controller.deviceList.isNotEmpty
|
||||||
_buildDeviceList(),
|
? Column(
|
||||||
SizedBox(height: 16.h),
|
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),
|
if (index == 0) SizedBox(height: 10.h),
|
||||||
_buildItem(device: device, index: index),
|
_buildItem(device: device, index: index),
|
||||||
// 如果不是最后一项,显示间距
|
// 如果不是最后一项,显示间距
|
||||||
if (index < controller.deviceList.length - 1)
|
if (index < controller.deviceList.length - 1) SizedBox(height: 10.h),
|
||||||
SizedBox(height: 10.h),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}).toList(),
|
}).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(
|
return GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
controller.connectingDevices();
|
controller.connectingDevices();
|
||||||
@ -186,7 +198,7 @@ class SearchDeviceView extends GetView<SearchDeviceController> {
|
|||||||
width: 8.w,
|
width: 8.w,
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
device.name,
|
device.advName,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 16.sp,
|
fontSize: 16.sp,
|
||||||
color: Colors.black87,
|
color: Colors.black87,
|
||||||
@ -198,7 +210,7 @@ class SearchDeviceView extends GetView<SearchDeviceController> {
|
|||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
// 处理添加设备事件
|
// 处理添加设备事件
|
||||||
print('添加设备 ${device.name}');
|
print('添加设备 ${device.advName}');
|
||||||
// 这里可以添加具体的添加设备逻辑
|
// 这里可以添加具体的添加设备逻辑
|
||||||
},
|
},
|
||||||
child: const Icon(
|
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();
|
super.onInit();
|
||||||
// 监听输入变化
|
// 监听输入变化
|
||||||
phoneController.addListener(_validateForm);
|
phoneController.addListener(_validateForm);
|
||||||
|
|
||||||
phoneController.text = '18269109817';
|
phoneController.text = '18269109817';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -151,7 +151,7 @@ packages:
|
|||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
flutter_blue_plus:
|
flutter_blue_plus:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flutter_blue_plus
|
name: flutter_blue_plus
|
||||||
sha256: "1901a42ade7a8f9793a3655983ef0899565294b12a2a57a2c3e33813930f4a34"
|
sha256: "1901a42ade7a8f9793a3655983ef0899565294b12a2a57a2c3e33813930f4a34"
|
||||||
@ -578,13 +578,6 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.11.1"
|
version: "1.11.1"
|
||||||
starcloud:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
path: "../starcloud-sdk-flutter"
|
|
||||||
relative: true
|
|
||||||
source: path
|
|
||||||
version: "0.1.2"
|
|
||||||
stream_channel:
|
stream_channel:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@ -36,10 +36,8 @@ dependencies:
|
|||||||
carousel_slider: ^5.1.1
|
carousel_slider: ^5.1.1
|
||||||
# 气泡提示框
|
# 气泡提示框
|
||||||
super_tooltip: ^2.0.8
|
super_tooltip: ^2.0.8
|
||||||
# 星云flutter SDK
|
# 蓝牙库
|
||||||
starcloud:
|
flutter_blue_plus: ^1.35.7
|
||||||
path: ../starcloud-sdk-flutter
|
|
||||||
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user