From 799479447c1a16e3c6603138b6298d7d51cf6714 Mon Sep 17 00:00:00 2001 From: liyi Date: Sat, 6 Sep 2025 15:42:26 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=AE=8C=E5=96=84=E8=AE=BE=E5=A4=87?= =?UTF-8?q?=E6=90=9C=E7=B4=A2=E9=A1=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ios/Podfile | 25 +++ ios/Podfile.lock | 8 +- ios/Runner/Info.plist | 41 +++++ lib/base/app_initialization.dart | 11 +- lib/base/app_logger.dart | 65 ++++++++ lib/base/app_permission.dart | 82 ++++++---- lib/ble/ble_config.dart | 13 ++ lib/ble/ble_service.dart | 149 ++++++++++++++++++ lib/ble/model/scan_device_info.dart | 26 +++ lib/common/constant/app_toast_messages.dart | 6 + lib/common/constant/device_type.dart | 2 + lib/common/utils/device_utils.dart | 5 + .../utils/shared_preferences_utils.dart | 21 ++- lib/flavors.dart | 98 +++++------- .../search_device_controller.dart | 77 ++++----- .../searchDevice/search_device_model.dart | 16 -- .../searchDevice/search_device_view.dart | 76 +++++---- .../search_device_rotating_icon_widget.dart | 100 ------------ lib/views/login/login_controller.dart | 1 - pubspec.lock | 9 +- pubspec.yaml | 6 +- 21 files changed, 530 insertions(+), 307 deletions(-) create mode 100644 lib/base/app_logger.dart create mode 100644 lib/ble/ble_config.dart create mode 100644 lib/ble/ble_service.dart create mode 100644 lib/ble/model/scan_device_info.dart create mode 100644 lib/common/constant/app_toast_messages.dart create mode 100644 lib/common/utils/device_utils.dart delete mode 100644 lib/views/device/searchDevice/search_device_model.dart delete mode 100644 lib/views/device/searchDevice/widget/search_device_rotating_icon_widget.dart diff --git a/ios/Podfile b/ios/Podfile index b484d90..ebc92d4 100644 --- a/ios/Podfile +++ b/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 diff --git a/ios/Podfile.lock b/ios/Podfile.lock index d268cc8..451ed7f 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -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 diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index f3a1419..f102bf0 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -45,5 +45,46 @@ UIApplicationSupportsIndirectInputEvents + + + + NSBluetoothAlwaysUsageDescription + 应用需要使用蓝牙功能来搜索和连接设备,实现设备配对和数据传输 + + + NSBluetoothPeripheralUsageDescription + 应用需要使用蓝牙外设功能来连接和管理设备 + + + + NSLocationWhenInUseUsageDescription + 应用需要位置权限来搜索附近的蓝牙设备 + + + NSLocationAlwaysAndWhenInUseUsageDescription + 应用需要位置权限来搜索附近的蓝牙设备 + + + NSLocationAlwaysUsageDescription + 应用需要位置权限来搜索附近的蓝牙设备 + + + NSCameraUsageDescription + 应用需要相机权限来扫描二维码 + + + NSMicrophoneUsageDescription + 应用需要麦克风权限来录制音频 + + + NSUserNotificationAlertStyle + banner + + + UIBackgroundModes + + bluetooth-central + bluetooth-peripheral + \ No newline at end of file diff --git a/lib/base/app_initialization.dart b/lib/base/app_initialization.dart index b2c267a..86c71c3 100644 --- a/lib/base/app_initialization.dart +++ b/lib/base/app_initialization.dart @@ -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())); Get.put(UserApiService(Get.find())); @@ -49,6 +45,9 @@ class AppInitialization { ), ); } + SystemChrome.setPreferredOrientations([ + DeviceOrientation.portraitUp, + ]); } static void initEasyLoading() { diff --git a/lib/base/app_logger.dart b/lib/base/app_logger.dart new file mode 100644 index 0000000..3fb2aad --- /dev/null +++ b/lib/base/app_logger.dart @@ -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 ✨✨✨'); + } +} \ No newline at end of file diff --git a/lib/base/app_permission.dart b/lib/base/app_permission.dart index fd117c4..a6dfa7d 100644 --- a/lib/base/app_permission.dart +++ b/lib/base/app_permission.dart @@ -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 requestBluetoothPermissions() async { try { // Android 12及以上需要的蓝牙权限 - List bluetoothPermissions = [ - Permission.bluetoothScan, - Permission.bluetoothConnect, - Permission.bluetoothAdvertise, - ]; - + List 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 locationPermissions = [ Permission.location, Permission.locationWhenInUse, ]; - + bool bluetoothGranted = true; bool locationGranted = true; - + // 检查并请求蓝牙权限(Android 12+) Map bluetoothStatuses = await bluetoothPermissions.request(); for (var status in bluetoothStatuses.values) { @@ -74,7 +93,7 @@ class AppPermission { break; } } - + // 检查并请求位置权限(Android 12以下或蓝牙扫描需要) Map 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 checkBluetoothScanPermission() async { try { @@ -110,7 +128,7 @@ class AppPermission { return await checkLocationPermission(); } } - + // 检查蓝牙连接权限 static Future checkBluetoothConnectPermission() async { try { @@ -122,7 +140,7 @@ class AppPermission { return true; } } - + // 检查蓝牙广播权限 static Future checkBluetoothAdvertisePermission() async { try { @@ -134,18 +152,18 @@ class AppPermission { return true; } } - + // 检查位置权限(Android 12以下蓝牙扫描需要) static Future checkLocationPermission() async { var status = await Permission.location.status; return status == PermissionStatus.granted; } - + // 请求位置权限 static Future 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 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; + } } } diff --git a/lib/ble/ble_config.dart b/lib/ble/ble_config.dart new file mode 100644 index 0000000..b2ffd14 --- /dev/null +++ b/lib/ble/ble_config.dart @@ -0,0 +1,13 @@ +class BleConfig { + // 私有构造函数 + BleConfig._() { + // ✅ 这里就是单例初始化的地方 + // 只会执行一次(第一次获取实例时) + } + + // 静态实例 + static final BleConfig _instance = BleConfig._(); + + // 工厂构造函数,提供全局访问点 + factory BleConfig() => _instance; +} diff --git a/lib/ble/ble_service.dart b/lib/ble/ble_service.dart new file mode 100644 index 0000000..0e31721 --- /dev/null +++ b/lib/ble/ble_service.dart @@ -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 _discoveredDevices = {}; + + /// 用来监听蓝牙适配器状态的订阅流 + StreamSubscription? _adapterStateSubscription; + + /// 用来监听搜索到的设备的订阅流 + StreamSubscription>? _scanResultSubscription; + + // 内部维护的蓝牙状态 + BluetoothAdapterState _bluetoothAdapterState = BluetoothAdapterState.unknown; + + /// 提供外部获取蓝牙适配器方法 + BluetoothAdapterState get bluetoothAdapterState => _bluetoothAdapterState; + + /// 搜索状态 + bool get isScanningNow => FlutterBluePlus.isScanningNow; + + /// 初始化服务时执行的 + Future _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 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; + } +} diff --git a/lib/ble/model/scan_device_info.dart b/lib/ble/model/scan_device_info.dart new file mode 100644 index 0000000..0bfa1cb --- /dev/null +++ b/lib/ble/model/scan_device_info.dart @@ -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}'; + } +} diff --git a/lib/common/constant/app_toast_messages.dart b/lib/common/constant/app_toast_messages.dart new file mode 100644 index 0000000..b1d48ee --- /dev/null +++ b/lib/common/constant/app_toast_messages.dart @@ -0,0 +1,6 @@ +import 'package:get/get.dart'; + +class AppToastMessages { + static String notLocationPermission = '蓝牙权限被拒绝,请在应用设置页面开启蓝牙权限'.tr; + static String notBluetoothPermissions = '蓝牙权限被拒绝,请在应用设置页面开启蓝牙权限'.tr; +} diff --git a/lib/common/constant/device_type.dart b/lib/common/constant/device_type.dart index e807d9a..2f0146b 100644 --- a/lib/common/constant/device_type.dart +++ b/lib/common/constant/device_type.dart @@ -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, diff --git a/lib/common/utils/device_utils.dart b/lib/common/utils/device_utils.dart new file mode 100644 index 0000000..e3ffa4c --- /dev/null +++ b/lib/common/utils/device_utils.dart @@ -0,0 +1,5 @@ +class DeviceUtils { + static bool checkDeviceServiceUuids() { + return false; + } +} diff --git a/lib/common/utils/shared_preferences_utils.dart b/lib/common/utils/shared_preferences_utils.dart index 34a505d..5c8f2c5 100644 --- a/lib/common/utils/shared_preferences_utils.dart +++ b/lib/common/utils/shared_preferences_utils.dart @@ -9,16 +9,15 @@ class SharedPreferencesUtils { _prefs = await SharedPreferences.getInstance(); } - /// 存储带过期时间的字符串 /// [key] 键 /// [value] 值 /// [expiry] 过期时间(如 Duration(hours: 1)) static Future 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 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); } -} \ No newline at end of file + + // bool + static Future setBool(String key, dynamic value) async { + _prefs?.setBool(key, value); + } + + static Future getBool(String key) async { + return _prefs?.getBool(key); + } +} diff --git a/lib/flavors.dart b/lib/flavors.dart index 17167fd..7b7bc3e 100644 --- a/lib/flavors.dart +++ b/lib/flavors.dart @@ -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'; + } } } } diff --git a/lib/views/device/searchDevice/search_device_controller.dart b/lib/views/device/searchDevice/search_device_controller.dart index 83e9486..731edc1 100644 --- a/lib/views/device/searchDevice/search_device_controller.dart +++ b/lib/views/device/searchDevice/search_device_controller.dart @@ -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 deviceList = [].obs; + final RxList deviceList = [].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 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); } // 连接设备 diff --git a/lib/views/device/searchDevice/search_device_model.dart b/lib/views/device/searchDevice/search_device_model.dart deleted file mode 100644 index 5c72a59..0000000 --- a/lib/views/device/searchDevice/search_device_model.dart +++ /dev/null @@ -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, - }); -} \ No newline at end of file diff --git a/lib/views/device/searchDevice/search_device_view.dart b/lib/views/device/searchDevice/search_device_view.dart index 66d7f32..79876ea 100644 --- a/lib/views/device/searchDevice/search_device_view.dart +++ b/lib/views/device/searchDevice/search_device_view.dart @@ -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 { const SearchDeviceView({super.key}); @@ -17,23 +17,13 @@ class SearchDeviceView extends GetView { 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 { body: Column( children: [ // 设备列表 - Expanded( - child: _buildRefreshableDeviceList(), - ), + _buildRefreshableDeviceList(), // 固定在底部的提示信息 _buildDeviceTip(), ], @@ -94,18 +82,43 @@ class SearchDeviceView extends GetView { // 控制滚动物理特性 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( + 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 { 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 { ); } - _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 { width: 8.w, ), Text( - device.name, + device.advName, style: TextStyle( fontSize: 16.sp, color: Colors.black87, @@ -198,7 +210,7 @@ class SearchDeviceView extends GetView { GestureDetector( onTap: () { // 处理添加设备事件 - print('添加设备 ${device.name}'); + print('添加设备 ${device.advName}'); // 这里可以添加具体的添加设备逻辑 }, child: const Icon( diff --git a/lib/views/device/searchDevice/widget/search_device_rotating_icon_widget.dart b/lib/views/device/searchDevice/widget/search_device_rotating_icon_widget.dart deleted file mode 100644 index 07140f6..0000000 --- a/lib/views/device/searchDevice/widget/search_device_rotating_icon_widget.dart +++ /dev/null @@ -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 - 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, - ), - ); - }, - ); - } -} \ No newline at end of file diff --git a/lib/views/login/login_controller.dart b/lib/views/login/login_controller.dart index 5d1de43..1907acd 100644 --- a/lib/views/login/login_controller.dart +++ b/lib/views/login/login_controller.dart @@ -26,7 +26,6 @@ class LoginController extends BaseController { super.onInit(); // 监听输入变化 phoneController.addListener(_validateForm); - phoneController.text = '18269109817'; } diff --git a/pubspec.lock b/pubspec.lock index 193b029..fde5355 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -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: diff --git a/pubspec.yaml b/pubspec.yaml index 2c9ac14..4833b06 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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: