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

View File

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

View File

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

View File

@ -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
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'; 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
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 { 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,

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(); _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);
}
}

View File

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

View File

@ -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('设备搜索刷新完成');
} }
// //

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

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(); super.onInit();
// //
phoneController.addListener(_validateForm); phoneController.addListener(_validateForm);
phoneController.text = '18269109817'; phoneController.text = '18269109817';
} }

View File

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

View File

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