fix: 完善设备搜索页

This commit is contained in:
liyi 2025-09-06 15:42:26 +08:00
parent 6f75cf0bcd
commit 799479447c
21 changed files with 530 additions and 307 deletions

View File

@ -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

View File

@ -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

View File

@ -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>

View File

@ -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
View 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 ✨✨✨');
}
}

View File

@ -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
View File

@ -0,0 +1,13 @@
class BleConfig {
//
BleConfig._() {
//
//
}
//
static final BleConfig _instance = BleConfig._();
// 访
factory BleConfig() => _instance;
}

149
lib/ble/ble_service.dart Normal file
View 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'; // 4534
int statusValue = int.parse(pairStatusStr, radix: 16);
// byte01
int byte0 = (statusValue >> 0) & 0x01; //
// byte12
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;
}
}

View 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}';
}
}

View File

@ -0,0 +1,6 @@
import 'package:get/get.dart';
class AppToastMessages {
static String notLocationPermission = '蓝牙权限被拒绝,请在应用设置页面开启蓝牙权限'.tr;
static String notBluetoothPermissions = '蓝牙权限被拒绝,请在应用设置页面开启蓝牙权限'.tr;
}

View File

@ -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,

View File

@ -0,0 +1,5 @@
class DeviceUtils {
static bool checkDeviceServiceUuids() {
return false;
}
}

View File

@ -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);
}
}

View File

@ -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';
}
}
}
}

View File

@ -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);
}
//

View File

@ -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,
});
}

View File

@ -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(

View File

@ -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,
),
);
},
);
}
}

View File

@ -26,7 +26,6 @@ class LoginController extends BaseController {
super.onInit();
//
phoneController.addListener(_validateForm);
phoneController.text = '18269109817';
}

View File

@ -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:

View File

@ -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: