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: