优化了tcp实时更新对UI线程的压力和对用弹窗的重复弹窗的影响。

更换了路径规划的的经纬度的字段名和取操作的名字更换。
This commit is contained in:
2026-06-08 13:35:36 +08:00
parent 7c9734d918
commit 354f7d6290
8 changed files with 445 additions and 276 deletions

View File

@@ -1,6 +1,7 @@
# Uncomment this line to define a global platform for your project # Uncomment this line to define a global platform for your project
# platform :ios, '13.0' # platform :ios, '13.0'
source 'https://github.com/volcengine/volcengine-specs.git' source 'https://github.com/volcengine/volcengine-specs.git'error: Classes can only extend other classes. (extends_non_class at [maibu_satabot_v2] lib\features\v2\device_list\domain\entities\video_stream_entity.dart:2)error: Classes can only extend other classes. (extends_non_class at [maibu_satabot_v2] lib\features\v2\device_list\domain\entities\video_stream_entity.dart:2)you
# CocoaPods analytics sends network stats synchronously affecting flutter build latency. # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true' ENV['COCOAPODS_DISABLE_STATS'] = 'true'

View File

@@ -62,8 +62,11 @@ class AuthCubit extends Cubit<AuthState> {
if (user != null) { if (user != null) {
// 🔥 冷启动时重新初始化 TCP 连接 // 🔥 冷启动时重新初始化 TCP 连接
await tcp.initializeTcp(host: TCPConsts.TCP_IP, port: TCPConsts.TCP_PORT); await tcp.initializeTcp(
host: TCPConsts.TCP_IP,
port: TCPConsts.TCP_PORT,
);
// 2. 同步全局 App 状态 // 2. 同步全局 App 状态
appCubit.setAuth(user); appCubit.setAuth(user);
// 3. 进入已登录状态 // 3. 进入已登录状态

View File

@@ -21,9 +21,15 @@ class DeviceStatusBloc extends Bloc<DeviceStatusEvent, DeviceStatusState> {
// 🔥 保存订阅引用,用于管理生命周期 // 🔥 保存订阅引用,用于管理生命周期
StreamSubscription? _tcpSubscription; StreamSubscription? _tcpSubscription;
DeviceStatusBloc(this._dispatcher, {TcpClient? client}) // 🔥 节流相关:500ms节流控制0x02数据推送频率
: tcpClient = client ?? GetIt.I<TcpClient>(), Timer? _throttleTimer;
super(DeviceStatusInitial()) { static const _throttleDuration = Duration(milliseconds: 500);
RunningStatusEntity? _cachedStatus;
GPSEntity? _cachedGps;
DeviceStatusBloc(this._dispatcher, {TcpClient? client})
: tcpClient = client ?? GetIt.I<TcpClient>(),
super(DeviceStatusInitial()) {
// 🔥 核心改动:直接在构造函数中建立TCP监听,类似RemoteControlCubit // 🔥 核心改动:直接在构造函数中建立TCP监听,类似RemoteControlCubit
_initDirectTcpListener(); _initDirectTcpListener();
@@ -69,97 +75,110 @@ class DeviceStatusBloc extends Bloc<DeviceStatusEvent, DeviceStatusState> {
// 🔥 关键修复:直接监听 tcpClient.packetStream,不经过 dispatcher 的 filtered stream // 🔥 关键修复:直接监听 tcpClient.packetStream,不经过 dispatcher 的 filtered stream
// 这样即使没有其他监听者,TCP流也不会暂停 // 这样即使没有其他监听者,TCP流也不会暂停
_tcpSubscription = tcpClient.packetStream _tcpSubscription = tcpClient.packetStream
.where((p) => p.command == 0x02) .where((p) => p.command == 0x02)
.map((p) { .map((p) {
try { try {
final result = utf8.decode(p.payload, allowMalformed: true); final result = utf8.decode(p.payload, allowMalformed: true);
// debugPrint('✅ [DeviceStatusBloc] 收到0x02数据: $result'); // debugPrint('✅ [DeviceStatusBloc] 收到0x02数据: $result');
return result; return result;
} catch (e) { } catch (e) {
// debugPrint('❌ [DeviceStatusBloc] 解码失败: $e'); // debugPrint('❌ [DeviceStatusBloc] 解码失败: $e');
return ''; return '';
} }
}) })
.listen( .listen(
(message) { (message) {
//debugPrint('📩 [DeviceStatusBloc] 直接收到0x02数据,长度:${message.length}'); //debugPrint('📩 [DeviceStatusBloc] 直接收到0x02数据,长度:${message.length}');
if (message.isEmpty) { if (message.isEmpty) {
//debugPrint('⚠️ [DeviceStatusBloc] 消息为空,跳过'); //debugPrint('⚠️ [DeviceStatusBloc] 消息为空,跳过');
return; return;
}
try {
// 🔥 直接解析,不经过事件转换
final fields = message.trim().split(',');
if (fields.length < 18) {
//debugPrint('⚠️ [DeviceStatusBloc] 字段不足:${fields.length},期望≥18');
if (!isClosed) {
emit(DeviceStatusError('字段不足,期望≥18,实际:${fields.length}'));
} }
return;
}
final status = RunningStatusEntity.fromFields(fields); try {
final gps = GPSEntity(status.latitude, status.longitude); // 🔥 直接解析,不经过事件转换
final fields = message.trim().split(',');
//debugPrint('✅ [DeviceStatusBloc] 直接解析成功,更新状态:Lat=${gps.latitude}, Lng=${gps.longitude}'); if (fields.length < 18) {
// _logger.log('✅ [DeviceStatusBloc] 直接解析成功,更新状态'); //debugPrint('⚠️ [DeviceStatusBloc] 字段不足:${fields.length},期望≥18');
// 🔥 错误不节流,立即emit以便UI显示错误
// 🔥 关键修复:BLoC有Equatable去重机制,必须创建新对象才能触发UI更新 if (!isClosed) {
if (!isClosed) { emit(DeviceStatusError('字段不足,期望≥18,实际:${fields.length}'));
// 创建全新的status和gps对象,绕过Equatable去重 }
final newStatus = RunningStatusEntity( return;
voltage: status.voltage, }
leftTargetSpeed: status.leftTargetSpeed,
rightTargetSpeed: status.rightTargetSpeed, final status = RunningStatusEntity.fromFields(fields);
leftMeasureSpeed: status.leftMeasureSpeed, final gps = GPSEntity(status.latitude, status.longitude);
rightMeasureSpeed: status.rightMeasureSpeed,
leftCurrent: status.leftCurrent, // 🔥 缓存最新数据用于节流发射
rightCurrent: status.rightCurrent, _cachedStatus = RunningStatusEntity(
leftMotorTemp: status.leftMotorTemp, voltage: status.voltage,
rightMotorTemp: status.rightMotorTemp, leftTargetSpeed: status.leftTargetSpeed,
chipTemp: status.chipTemp, rightTargetSpeed: status.rightTargetSpeed,
yaw: status.yaw, leftMeasureSpeed: status.leftMeasureSpeed,
pitch: status.pitch, rightMeasureSpeed: status.rightMeasureSpeed,
roll: status.roll, leftCurrent: status.leftCurrent,
satelliteCnt: status.satelliteCnt, rightCurrent: status.rightCurrent,
qual: status.qual, leftMotorTemp: status.leftMotorTemp,
headingStatus: status.headingStatus, rightMotorTemp: status.rightMotorTemp,
latitude: status.latitude, chipTemp: status.chipTemp,
longitude: status.longitude, yaw: status.yaw,
timestamp: status.timestamp, pitch: status.pitch,
knifeCuttingSpeed: status.knifeCuttingSpeed, roll: status.roll,
controlMode: status.controlMode, satelliteCnt: status.satelliteCnt,
battery: status.battery, qual: status.qual,
workingArea: status.workingArea, headingStatus: status.headingStatus,
obstacleFlag: status.obstacleFlag, latitude: status.latitude,
); longitude: status.longitude,
final newGps = GPSEntity(status.latitude, status.longitude); timestamp: status.timestamp,
// debugPrint('📤 [DeviceStatusBloc] emit DeviceStatusUpdated - 电压:${status.voltage}, 电量:${status.battery}, 模式:${status.controlMode}'); knifeCuttingSpeed: status.knifeCuttingSpeed,
emit(DeviceStatusUpdated(newStatus, newGps)); controlMode: status.controlMode,
} battery: status.battery,
} catch (e, stack) { workingArea: status.workingArea,
//debugPrint('❌ [DeviceStatusBloc] 直接解析异常:$e\n$stack'); obstacleFlag: status.obstacleFlag,
// _logger.log('❌ [DeviceStatusBloc] 直接解析异常:$e'); );
if (!isClosed) { _cachedGps = GPSEntity(status.latitude, status.longitude);
emit(DeviceStatusError('解析失败:$e'));
} // 🔥 节流:取消之前的timer,重新计时500ms
} _throttleTimer?.cancel();
}, _throttleTimer = Timer(_throttleDuration, () {
onDone: () => debugPrint('⚠️ [DeviceStatusBloc] TCP流已结束(onDone)'), _emitCachedStatus();
onError: (e) => debugPrint('❌ [DeviceStatusBloc] TCP流错误:$e'), });
); } catch (e, stack) {
//debugPrint('❌ [DeviceStatusBloc] 直接解析异常:$e\n$stack');
// _logger.log('❌ [DeviceStatusBloc] 直接解析异常:$e');
// 🔥 解析错误不节流,立即emit以便UI显示错误
if (!isClosed) {
emit(DeviceStatusError('解析失败:$e'));
}
}
},
onDone: () => debugPrint('⚠️ [DeviceStatusBloc] TCP流已结束(onDone)'),
onError: (e) => debugPrint('❌ [DeviceStatusBloc] TCP流错误:$e'),
);
debugPrint('✅ [DeviceStatusBloc] 直接TCP监听器已建立完成'); debugPrint('✅ [DeviceStatusBloc] 直接TCP监听器已建立完成');
_logger.log('✅ [DeviceStatusBloc] 直接TCP监听器已建立完成'); _logger.log('✅ [DeviceStatusBloc] 直接TCP监听器已建立完成');
} }
// 🔥 节流发射:500ms到期后发射缓存的最新数据
void _emitCachedStatus() {
if (_cachedStatus != null && _cachedGps != null && !isClosed) {
// debugPrint('📤 [DeviceStatusBloc] 🔥节流发射 - 电压:${_cachedStatus!.voltage}, 电量:${_cachedStatus!.battery}');
emit(DeviceStatusUpdated(_cachedStatus!, _cachedGps!));
}
}
// 🔥 重置时仅清空状态 // 🔥 重置时仅清空状态
Future<void> _handleReset(DeviceStatusReset event, Emitter<DeviceStatusState> emit) async { Future<void> _handleReset(
DeviceStatusReset event,
Emitter<DeviceStatusState> emit,
) async {
debugPrint('🔄 [DeviceStatusBloc] 收到重置事件:清空状态 - 当前状态:${state.runtimeType}'); debugPrint('🔄 [DeviceStatusBloc] 收到重置事件:清空状态 - 当前状态:${state.runtimeType}');
_logger.log('🔄 [DeviceStatusBloc] 收到重置事件:清空状态 - 当前状态:${state.runtimeType}'); _logger.log(
'🔄 [DeviceStatusBloc] 收到重置事件:清空状态 - 当前状态:${state.runtimeType}',
);
// 只 emit 初始状态,让 UI 清除旧设备的数据 // 只 emit 初始状态,让 UI 清除旧设备的数据
emit(DeviceStatusInitial()); emit(DeviceStatusInitial());
@@ -167,11 +186,11 @@ class DeviceStatusBloc extends Bloc<DeviceStatusEvent, DeviceStatusState> {
} }
Future<void> _handleDeviceStatusLoaded( Future<void> _handleDeviceStatusLoaded(
DeviceStatusLoaded event, DeviceStatusLoaded event,
Emitter<DeviceStatusState> emit, Emitter<DeviceStatusState> emit,
) async { ) async {
try { try {
// debugPrint('🔍 开始解析数据:${event.jsonString}'); // debugPrint('🔍 开始解析数据:${event.jsonString}');
_logger.log('🔍 开始解析数据:${event.jsonString}'); _logger.log('🔍 开始解析数据:${event.jsonString}');
final fields = event.jsonString.trim().split(','); final fields = event.jsonString.trim().split(',');
@@ -195,13 +214,13 @@ class DeviceStatusBloc extends Bloc<DeviceStatusEvent, DeviceStatusState> {
} }
Future<void> _handlePushMessageReceived( Future<void> _handlePushMessageReceived(
PushMessageReceived event, PushMessageReceived event,
Emitter<DeviceStatusState> emit, Emitter<DeviceStatusState> emit,
) async { ) async {
try { try {
final eventStr = event.jsonData['event'] ?? ''; final eventStr = event.jsonData['event'] ?? '';
final deviceId = event.jsonData['deviceId'] ?? '未知'; final deviceId = event.jsonData['deviceId'] ?? '未知';
// debugPrint('收到推送事件:$eventStr, 设备:$deviceId'); // debugPrint('收到推送事件:$eventStr, 设备:$deviceId');
_logger.log('收到推送事件:$eventStr, 设备:$deviceId'); _logger.log('收到推送事件:$eventStr, 设备:$deviceId');
// 这里可以根据需要 emit 新状态 // 这里可以根据需要 emit 新状态
} catch (e) { } catch (e) {
@@ -214,6 +233,11 @@ class DeviceStatusBloc extends Bloc<DeviceStatusEvent, DeviceStatusState> {
debugPrint('🚫 [DeviceStatusBloc] 页面退出,仅取消TCP订阅(不关闭BLoC)'); debugPrint('🚫 [DeviceStatusBloc] 页面退出,仅取消TCP订阅(不关闭BLoC)');
_logger.log('🚫 [DeviceStatusBloc] 页面退出,仅取消TCP订阅(不关闭BLoC)'); _logger.log('🚫 [DeviceStatusBloc] 页面退出,仅取消TCP订阅(不关闭BLoC)');
//_tcpSubscription?.cancel(); //_tcpSubscription?.cancel();
// 🔥 清理节流timer和缓存
_throttleTimer?.cancel();
_throttleTimer = null;
_cachedStatus = null;
_cachedGps = null;
// 🔥 关键修复:不调用 super.close(),保持 BLoC 活跃 // 🔥 关键修复:不调用 super.close(),保持 BLoC 活跃
return Future.value(); return Future.value();
} }

View File

@@ -10,7 +10,8 @@ import '../../../remote_control/domain/repositories/remote_control_repository.da
import 'permission_request_event.dart'; import 'permission_request_event.dart';
import 'permission_request_state.dart'; import 'permission_request_state.dart';
class PermissionRequestBloc extends Bloc<PermissionRequestEvent, PermissionRequestState> { class PermissionRequestBloc
extends Bloc<PermissionRequestEvent, PermissionRequestState> {
final NetMessageDispatcher _dispatcher; final NetMessageDispatcher _dispatcher;
final RemoteControlRepository _repository; final RemoteControlRepository _repository;
final ILoggerService _logger = GetIt.I<ILoggerService>(); final ILoggerService _logger = GetIt.I<ILoggerService>();
@@ -18,9 +19,11 @@ class PermissionRequestBloc extends Bloc<PermissionRequestEvent, PermissionReque
// 🔥 保存订阅引用,用于管理生命周期 // 🔥 保存订阅引用,用于管理生命周期
StreamSubscription? _permissionSubscription; StreamSubscription? _permissionSubscription;
PermissionRequestBloc(this._dispatcher, this._repository) : super(const PermissionRequestInitial()) { PermissionRequestBloc(this._dispatcher, this._repository)
: super(const PermissionRequestInitial()) {
// 🔥 核心:直接在构造函数中建立 0x12 监听(通过 NetMessageDispatcher) // 🔥 核心:直接在构造函数中建立 0x12 监听(通过 NetMessageDispatcher)
_initPermissionListener(); // 🔥 注意:RemoteControlCubit 已经在处理 0x12 权限请求,这里不再重复监听
// _initPermissionListener();
// 事件处理 // 事件处理
on<PermissionRequestReceived>(_handleRequestReceived); on<PermissionRequestReceived>(_handleRequestReceived);
@@ -28,100 +31,53 @@ class PermissionRequestBloc extends Bloc<PermissionRequestEvent, PermissionReque
} }
// 🔥 通过 NetMessageDispatcher 监听 TCP 0x12 指令,解析权限请求 // 🔥 通过 NetMessageDispatcher 监听 TCP 0x12 指令,解析权限请求
// 🔥 已禁用:RemoteControlCubit 统一处理权限请求,避免重复弹窗
void _initPermissionListener() { void _initPermissionListener() {
debugPrint('🔗 [PermissionRequestBloc] 初始化 0x12 权限监听器(通过 NetMessageDispatcher)'); debugPrint(
_logger.logWithLevel('🔗 [PermissionRequestBloc] 初始化 0x12 权限监听器', shouldLog: true); '🔗 [PermissionRequestBloc] 初始化 0x12 权限监听器(通过 NetMessageDispatcher)',
);
_logger.logWithLevel(
'🔗 [PermissionRequestBloc] 初始化 0x12 权限监听器',
shouldLog: true,
);
_permissionSubscription = _dispatcher.onCommand(0x12).listen((packet) { // 🔥 已禁用:权限请求统一由 RemoteControlCubit 处理,避免重复弹窗
// _permissionSubscription = _dispatcher.onCommand(0x12).listen((packet) {
debugPrint('🔍 [PermissionRequestBloc] ✅✅✅ 0x12 包被监听到了!'); // ...
_logger.logWithLevel('>>> [PermissionRequestBloc] ✅✅✅ 0x12 包被监听到了!', shouldLog: true); // });
try {
_logger.logWithLevel('>>> [PermissionRequestBloc] ✅ 收到 0x12 原始包', shouldLog: true);
// 🔥 关键:手动去掉最后 2 个 CRC 字节
String jsonString;
if (packet.payload.length > 2) {
jsonString = utf8.decode(packet.payload.sublist(0, packet.payload.length - 2));
} else {
jsonString = utf8.decode(packet.payload);
}
debugPrint('>>> [PermissionRequestBloc] 收到 0x12: $jsonString'); debugPrint(
_logger.logWithLevel('>>> [PermissionRequestBloc] 收到 0x12: $jsonString', shouldLog: true); '🔍 [PermissionRequestBloc] ⚠️ 权限监听已禁用,统一由 RemoteControlCubit 处理',
);
final jsonMap = jsonDecode(jsonString); _logger.logWithLevel(
final requestType = jsonMap['request']; '>>> [PermissionRequestBloc] ⚠️ 权限监听已禁用,统一由 RemoteControlCubit 处理',
final platform = jsonMap['platform']; shouldLog: true,
final respondData = jsonMap['respond']; );
_logger.logWithLevel('>>> [PermissionRequestBloc] 解析结果: requestType=$requestType, platform=$platform', shouldLog: true);
// 情况 1: 响应格式(忽略)
if (respondData != null && respondData is Map) {
debugPrint('>>> [PermissionRequestBloc] 📊 收到响应格式,忽略');
_logger.logWithLevel('>>> [PermissionRequestBloc] 📊 收到响应格式,忽略', shouldLog: true);
return;
}
// 情况 2: 请求格式 - switch_control
if (requestType == 'switch_control') {
// 🔥 关键判断:只有当是其他平台(web)请求时才弹窗
if (platform != null && platform.toString().toLowerCase() != 'app') {
debugPrint('>>> [PermissionRequestBloc] 🚨 $platform 端请求控制权');
_logger.logWithLevel('>>> [PermissionRequestBloc] 🚨 $platform 端请求控制权', shouldLog: true);
if (!isClosed) {
_logger.logWithLevel('>>> [PermissionRequestBloc] 📤 准备 add PermissionRequestReceived 事件', shouldLog: true);
add(PermissionRequestReceived(
platform: platform.toString(),
requestType: requestType,
));
} else {
_logger.logWithLevel('>>> [PermissionRequestBloc] ⚠️ Bloc 已关闭,无法添加事件', shouldLog: true);
}
} else {
debugPrint('>>> [PermissionRequestBloc] ℹ️ APP 自己的请求回显,忽略');
_logger.logWithLevel('>>> [PermissionRequestBloc] ℹ️ APP 自己的请求回显,忽略', shouldLog: true);
}
}
// 情况 3: 异地登录通知
else if (requestType == 'have_logged_in') {
debugPrint('>>> [PermissionRequestBloc] ⚠️ 检测到异地登录');
_logger.logWithLevel('>>> [PermissionRequestBloc] ⚠️ 检测到异地登录', shouldLog: true);
if (!isClosed) {
_logger.logWithLevel('>>> [PermissionRequestBloc] 📤 准备 add PermissionRequestReceived 事件(异地登录)', shouldLog: true);
add(PermissionRequestReceived(
platform: 'unknown',
requestType: requestType,
));
}
}
} catch (e) {
debugPrint('>>> [PermissionRequestBloc] ❌ 解析失败:$e');
_logger.logWithLevel('>>> [PermissionRequestBloc] ❌ 解析失败:$e', shouldLog: true);
}
});
debugPrint('✅ [PermissionRequestBloc] 0x12 监听器已建立完成');
_logger.logWithLevel('✅ [PermissionRequestBloc] 0x12 监听器已建立完成', shouldLog: true);
} }
Future<void> _handleRequestReceived( Future<void> _handleRequestReceived(
PermissionRequestReceived event, PermissionRequestReceived event,
Emitter<PermissionRequestState> emit, Emitter<PermissionRequestState> emit,
) async { ) async {
debugPrint('📤 [PermissionRequestBloc] emit DialogVisible - platform=${event.platform}'); debugPrint(
_logger.logWithLevel('📤 [PermissionRequestBloc] emit DialogVisible - platform=${event.platform}', shouldLog: true); '📤 [PermissionRequestBloc] emit DialogVisible - platform=${event.platform}',
);
emit(PermissionRequestDialogVisible( _logger.logWithLevel(
platform: event.platform, '📤 [PermissionRequestBloc] emit DialogVisible - platform=${event.platform}',
requestType: event.requestType, shouldLog: true,
)); );
_logger.logWithLevel('📤 [PermissionRequestBloc] ✅ 已 emit DialogVisible 状态', shouldLog: true); emit(
PermissionRequestDialogVisible(
platform: event.platform,
requestType: event.requestType,
),
);
_logger.logWithLevel(
'📤 [PermissionRequestBloc] ✅ 已 emit DialogVisible 状态',
shouldLog: true,
);
} }
Future<void> _handleDialogDismissed( Future<void> _handleDialogDismissed(
@@ -130,13 +86,15 @@ class PermissionRequestBloc extends Bloc<PermissionRequestEvent, PermissionReque
) async { ) async {
debugPrint('📤 [PermissionRequestBloc] emit DialogHidden'); debugPrint('📤 [PermissionRequestBloc] emit DialogHidden');
_logger.logWithLevel('📤 [PermissionRequestBloc] emit DialogHidden'); _logger.logWithLevel('📤 [PermissionRequestBloc] emit DialogHidden');
// 🔥 发送响应到机器人(和远程遥控页面一样) // 🔥 发送响应到机器人(和远程遥控页面一样)
if (event.deviceId.isNotEmpty) { if (event.deviceId.isNotEmpty) {
debugPrint('📤 [PermissionRequestBloc] 发送权限响应: agree=${event.agree}, deviceId=${event.deviceId}'); debugPrint(
'📤 [PermissionRequestBloc] 发送权限响应: agree=${event.agree}, deviceId=${event.deviceId}',
);
_repository.respondPermission(event.agree, event.deviceId); _repository.respondPermission(event.agree, event.deviceId);
} }
emit(const PermissionRequestDialogHidden()); emit(const PermissionRequestDialogHidden());
} }
@@ -147,11 +105,14 @@ class PermissionRequestBloc extends Bloc<PermissionRequestEvent, PermissionReque
_permissionSubscription?.cancel(); _permissionSubscription?.cancel();
return super.close(); return super.close();
} }
/// 🔥 公开方法:重新初始化监听器(TCP 重连后调用) /// 🔥 公开方法:重新初始化监听器(TCP 重连后调用)
void reinitListener() { void reinitListener() {
debugPrint('🔄 [PermissionRequestBloc] 重新初始化监听器'); debugPrint('🔄 [PermissionRequestBloc] 重新初始化监听器');
_logger.logWithLevel('🔄 [PermissionRequestBloc] 重新初始化监听器', shouldLog: true); _logger.logWithLevel(
'🔄 [PermissionRequestBloc] 重新初始化监听器',
shouldLog: true,
);
_permissionSubscription?.cancel(); _permissionSubscription?.cancel();
_initPermissionListener(); _initPermissionListener();
} }

View File

@@ -1472,13 +1472,13 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
List<LatLng> newOuterPoints = []; List<LatLng> newOuterPoints = [];
for (var item in pathList) { for (var item in pathList) {
double? lat = safeToDouble(item['lat']); double? lat = safeToDouble(item['lat']);
double? lon = safeToDouble(item['lon']); double? lon = safeToDouble(item['lng'] ?? item['lon']);
if (lat != null && lon != null) { if (lat != null && lon != null) {
newPathPoints.add(convertWGS84ToGCJ02(lat, lon)); newPathPoints.add(convertWGS84ToGCJ02(lat, lon));
} }
} }
for (var item in outerList) { for (var item in outerList) {
double? lng = safeToDouble(item['lng']); double? lng = safeToDouble(item['lng'] ?? item['lon']);
double? lat = safeToDouble(item['lat']); double? lat = safeToDouble(item['lat']);
if (lng != null && lat != null) { if (lng != null && lat != null) {
newOuterPoints.add(convertWGS84ToGCJ02(lat, lng)); newOuterPoints.add(convertWGS84ToGCJ02(lat, lng));

View File

@@ -1,6 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'dart:math';
import 'package:dart_ping/dart_ping.dart'; import 'package:dart_ping/dart_ping.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
@@ -39,13 +40,12 @@ class RemoteControlCubit extends Cubit<RemoteControlState> {
int _currentOriginX = 0; int _currentOriginX = 0;
int _currentOriginY = 0; int _currentOriginY = 0;
// 🔥 权限请求冷却期机制 - 防止Web端持续发送请求导致弹窗不断显示 // 🔥 权限请求处理标志位 - 防止竞态条件,避免重复显示弹窗
DateTime? _lastPermissionResponseTime; // 记录上次响应权限请求的时间
static const _coolDownDuration = Duration(seconds: 5); // 冷却期5秒
// 🔥 同步锁 - 防止状态更新期间接收新请求导致重复弹窗
bool _isProcessingPermissionRequest = false; bool _isProcessingPermissionRequest = false;
// 🔥 模拟数据推送定时器
// Timer? _simulationTimer;
// 🔥 获取带时间戳的日志前缀 // 🔥 获取带时间戳的日志前缀
String _getTimePrefix() { String _getTimePrefix() {
final now = DateTime.now(); final now = DateTime.now();
@@ -69,40 +69,138 @@ class RemoteControlCubit extends Cubit<RemoteControlState> {
} }
// 🔥 新增:订阅 DeviceStatusBloc 的状态流,而不是直接监听TCP // 🔥 新增:订阅 DeviceStatusBloc 的状态流,而不是直接监听TCP
// 类全局变量
DateTime? _lastUiUpdateTime;
String? _cacheVoltage;
String? _cacheBattery;
String? _cacheCtrlMode;
int? _cachePing;
void _initDeviceStatusListener() { void _initDeviceStatusListener() {
// // _logger.logWithLevel('>>> [RemoteControl] begin 订阅 DeviceStatusBloc 状态流');
_deviceStatusSub?.cancel(); _deviceStatusSub?.cancel();
_deviceStatusSub = deviceStatusBloc.stream.listen((deviceState) async { _deviceStatusSub = deviceStatusBloc.stream.listen((deviceState) async {
if (deviceState is DeviceStatusUpdated) { if (deviceState is DeviceStatusUpdated) {
// 第一步:所有数据先存入缓存,不管来多频繁都存最新值
final voltage = deviceState.status.voltage; final voltage = deviceState.status.voltage;
final battery = deviceState.status.battery; final battery = deviceState.status.battery;
final controlMode = deviceState.status.controlMode == '3' final controlMode = deviceState.status.controlMode == '3'
? '远程模式' ? '远程模式'
: '本地模式'; : '本地模式';
final c = await getNetworkDelay();
final c = getNetworkDelay(); _cacheVoltage = voltage.toString();
_cacheBattery = battery.toString();
_cacheCtrlMode = controlMode;
_cachePing = c;
// 500ms节流,不到时间不刷新UI
final now = DateTime.now();
if (_lastUiUpdateTime != null &&
now.difference(_lastUiUpdateTime!) <
const Duration(milliseconds: 500)) {
return;
}
_lastUiUpdateTime = now;
// 间隔达标,统一一次刷新UI
emit( emit(
state.copyWith( state.copyWith(
runningStatusModel: state.runningStatusModel.copyWith( runningStatusModel: state.runningStatusModel.copyWith(
voltage: voltage.toString(), voltage: _cacheVoltage,
battery: battery.toString(), battery: _cacheBattery,
controlMode: controlMode, controlMode: _cacheCtrlMode,
), ),
battery: int.tryParse(battery) ?? 0, battery: int.tryParse(_cacheBattery ?? '') ?? 0,
ping: await c, ping: _cachePing,
// 🔥 标记为设备状态更新
updateType: 'device_status',
), ),
); );
// // _logger.logWithLevel('✅ [RemoteControl] 从 DeviceStatusBloc 收到更新: 电压=$voltage, 电量=$battery, 模式=$controlMode');
} }
}); });
// _logger.logWithLevel('>>> [RemoteControl] ✅ DeviceStatusBloc 订阅已建立完成');
} }
// 🔥 超简单方法:传入 IP,得到 ping 值 // 🔥 超简单方法:传入 IP,得到 ping 值
// 🔥 模拟设备状态更新 - 用于测试
// void simulateDeviceStatusUpdate({
// String? voltage,
// String? battery,
// String? controlMode,
// int? ping,
// // 🔥 是否随机生成数据
// bool random = false,
// }) {
// // 如果开启随机模式,生成随机数据
// final rand = Random();
/* if (random) {
voltage = (22.0 + rand.nextDouble() * 4.0).toStringAsFixed(
1,
); // 22.0-26.0V
battery = (rand.nextInt(100) + 1).toString(); // 1-100%
controlMode = rand.nextBool() ? '远程模式' : '本地模式';
ping = rand.nextInt(150) + 20; // 20-170ms
} */
/* debugPrint(
'🔧 [模拟设备状态更新] voltage=$voltage V, battery=$battery%, controlMode=$controlMode, ping=$ping ms',
); */
// 更新缓存
/* if (voltage != null) _cacheVoltage = voltage;
if (battery != null) _cacheBattery = battery;
if (controlMode != null) _cacheCtrlMode = controlMode;
if (ping != null) _cachePing = ping;
// 直接触发状态更新(跳过节流,立即更新)
emit(
state.copyWith(
runningStatusModel: state.runningStatusModel.copyWith(
voltage: _cacheVoltage,
battery: _cacheBattery,
controlMode: _cacheCtrlMode,
),
battery: int.tryParse(_cacheBattery ?? '') ?? 0,
ping: _cachePing,
// 🔥 标记为设备状态更新
updateType: 'device_status',
),
);
} */
// 🔥 开始模拟设备状态推送(随机数据)
/* void startSimulation({int intervalMs = 500}) {
// 如果已经在运行,先停止
stopSimulation();
// debugPrint('🔔 [模拟推送] 开始模拟设备状态推送,间隔:${intervalMs}ms');
// 立即发送一次初始数据
simulateDeviceStatusUpdate(random: true);
// 定时推送随机数据
_simulationTimer = Timer.periodic(Duration(milliseconds: intervalMs), (
timer,
) {
if (!isClosed) {
simulateDeviceStatusUpdate(random: true);
} else {
stopSimulation();
}
});
} */
// 🔥 停止模拟设备状态推送
/* void stopSimulation() {
if (_simulationTimer != null) {
_simulationTimer!.cancel();
_simulationTimer = null;
debugPrint('🔔 [模拟推送] 已停止模拟设备状态推送');
}
} */
// 🔥 辅助方法:更新运行状态 // 🔥 辅助方法:更新运行状态
void _updateStatusFromDevice(RunningStatusModel newStatus) { void _updateStatusFromDevice(RunningStatusModel newStatus) {
// // debugPrint('✅ [_updateStatusFromDevice] 收到运行状态更新:$newStatus'); // // debugPrint('✅ [_updateStatusFromDevice] 收到运行状态更新:$newStatus');
@@ -141,14 +239,37 @@ class RemoteControlCubit extends Cubit<RemoteControlState> {
_kickOutSub = dispatcher.onCommand(0x12).listen((packet) { _kickOutSub = dispatcher.onCommand(0x12).listen((packet) {
final timeNow = _getTimePrefix(); final timeNow = _getTimePrefix();
debugPrint('$timeNow 🔍 [RemoteControl] [0x12监听器] ✅ 收到 0x12 原始包'); debugPrint('$timeNow 🔍 [RemoteControl] [0x12监听器] ✅ 收到 0x12 原始包');
debugPrint( /* debugPrint(
'$timeNow 🔍 [RemoteControl] [0x12监听器] payload长度: ${packet.payload.length}', '$timeNow 🔍 [RemoteControl] [0x12监听器] payload长度: ${packet.payload.length}',
); );*/
_logger.logWithLevel( _logger.logWithLevel(
'$timeNow 🔍 [RemoteControl] [0x12监听器] 收到 0x12 原始包,payload长度=${packet.payload.length}', '$timeNow 🔍 [RemoteControl] [0x12监听器] 收到 0x12 原始包,payload长度=${packet.payload.length}',
shouldLog: true, shouldLog: true,
); );
// 先解析JSON判断是否为响应格式
String jsonStringForCheck;
try {
if (packet.payload.length > 2) {
jsonStringForCheck = utf8.decode(
packet.payload.sublist(0, packet.payload.length - 2),
);
} else {
jsonStringForCheck = utf8.decode(packet.payload);
}
final jsonMap = jsonDecode(jsonStringForCheck);
final respondData = jsonMap['respond'];
// 如果是响应格式,继续处理(更新权限状态)
// 如果是请求格式且弹窗已显示,忽略
if (respondData == null && state.showPermissionRequestDialog) {
debugPrint('$timeNow 🔍 [RemoteControl] [0x12监听器] ⚠️ 弹窗已显示(状态),忽略请求');
return;
}
} catch (e) {
// 解析失败,继续处理
}
try { try {
// 🔥 关键:手动去掉最后 2 个 CRC 字节 // 🔥 关键:手动去掉最后 2 个 CRC 字节
String jsonString; String jsonString;
@@ -179,11 +300,13 @@ class RemoteControlCubit extends Cubit<RemoteControlState> {
// 情况 1: 响应格式 - {"respond":{"switchResult":true,"deviceId":"...","holder":"you"}} // 情况 1: 响应格式 - {"respond":{"switchResult":true,"deviceId":"...","holder":"you"}}
if (respondData != null && respondData is Map) { if (respondData != null && respondData is Map) {
// 🔥 收到响应格式,更新权限状态
final switchResult = respondData['switchResult']; final switchResult = respondData['switchResult'];
debugPrint('$timeNow 🔍 [RemoteControl] [0x12监听器] 📊 收到切换结果响应'); /* debugPrint('$timeNow 🔍 [RemoteControl] [0x12监听器] 📊 收到切换结果响应');
debugPrint( debugPrint(
'$timeNow 🔍 [RemoteControl] [0x12监听器] switchResult: $switchResult', '$timeNow 🔍 [RemoteControl] [0x12监听器] switchResult: $switchResult',
); );*/
_logger.logWithLevel( _logger.logWithLevel(
'$timeNow 🔍 [RemoteControl] [0x12监听器] 📊 收到切换结果响应 - switchResult: $switchResult', '$timeNow 🔍 [RemoteControl] [0x12监听器] 📊 收到切换结果响应 - switchResult: $switchResult',
shouldLog: true, shouldLog: true,
@@ -218,6 +341,15 @@ class RemoteControlCubit extends Cubit<RemoteControlState> {
final webLogPrefix = final webLogPrefix =
'${_getTimePrefix()} 🚨 [RemoteControl] [Web端权限请求]'; '${_getTimePrefix()} 🚨 [RemoteControl] [Web端权限请求]';
// 🔥 新增:记录收到请求的时间,便于排查是否为后端持续推送
debugPrint(
'$webLogPrefix ⚡️⚡️⚡️ 收到Web端权限请求 - ${DateTime.now().toString()}',
);
_logger.logWithLevel(
'$webLogPrefix ⚡️⚡️⚡️ 收到Web端权限请求 - ${DateTime.now().toString()}',
shouldLog: true,
);
debugPrint('$webLogPrefix ========================================='); debugPrint('$webLogPrefix =========================================');
debugPrint('$webLogPrefix 收到 switch_control 请求'); debugPrint('$webLogPrefix 收到 switch_control 请求');
debugPrint('$webLogPrefix platform: $platform'); debugPrint('$webLogPrefix platform: $platform');
@@ -235,28 +367,32 @@ class RemoteControlCubit extends Cubit<RemoteControlState> {
if (currentDeviceId != null && requestDeviceId == currentDeviceId) { if (currentDeviceId != null && requestDeviceId == currentDeviceId) {
debugPrint('$webLogPrefix 设备ID匹配'); debugPrint('$webLogPrefix 设备ID匹配');
// 🔥 添加防重复检查:只有当弹窗还没显示时才弹出
if (!state.showPermissionRequestDialog) { // 🔥 只有当弹窗还没显示时才弹出,防止重复弹窗叠加
debugPrint('$webLogPrefix 弹出权限请求对话框'); if (state.showPermissionRequestDialog) {
_logger.logWithLevel(
'$webLogPrefix 设备ID匹配,弹出权限请求对话框',
shouldLog: true,
);
if (!isClosed) {
emit(
state.copyWith(
showPermissionRequestDialog: true,
requestingDeviceId: requestDeviceId?.toString(),
requestingPlatform: platform.toString(),
),
);
}
} else {
debugPrint('$webLogPrefix ⚠️ 弹窗已显示,忽略重复请求'); debugPrint('$webLogPrefix ⚠️ 弹窗已显示,忽略重复请求');
_logger.logWithLevel( _logger.logWithLevel(
'$webLogPrefix ⚠️ 弹窗已显示,忽略重复请求', '$webLogPrefix ⚠️ 弹窗已显示,忽略重复请求',
shouldLog: true, shouldLog: true,
); );
return;
}
debugPrint('$webLogPrefix 弹出权限请求对话框');
_logger.logWithLevel(
'$webLogPrefix 设备ID匹配,弹出权限请求对话框',
shouldLog: true,
);
if (!isClosed) {
emit(
state.copyWith(
showPermissionRequestDialog: true,
requestingDeviceId: requestDeviceId?.toString(),
requestingPlatform: platform.toString(),
// 🔥 标记为弹窗状态更新
updateType: 'permission_dialog',
),
);
} }
} else { } else {
debugPrint('$webLogPrefix ⚠️ 设备ID不匹配,忽略'); debugPrint('$webLogPrefix ⚠️ 设备ID不匹配,忽略');
@@ -284,7 +420,8 @@ class RemoteControlCubit extends Cubit<RemoteControlState> {
'${_getTimePrefix()} ⚠️ [RemoteControl] 检测到异地登录,打开弹窗提示', '${_getTimePrefix()} ⚠️ [RemoteControl] 检测到异地登录,打开弹窗提示',
shouldLog: true, shouldLog: true,
); );
if (!isClosed) { // 🔥 只有当弹窗还没显示时才弹出
if (!isClosed && !state.showPermissionRequestDialog) {
emit(state.copyWith(showPermissionRequestDialog: true)); emit(state.copyWith(showPermissionRequestDialog: true));
} }
} else { } else {
@@ -331,6 +468,9 @@ class RemoteControlCubit extends Cubit<RemoteControlState> {
//debugPrint('$logPrefix 启动控制循环,间隔: 100ms'); //debugPrint('$logPrefix 启动控制循环,间隔: 100ms');
///debugPrint('$logPrefix ========================================='); ///debugPrint('$logPrefix =========================================');
// 🔥 启动模拟设备状态推送(用于测试)
// startSimulation();
_timer = Timer.periodic(const Duration(milliseconds: 100), (timer) { _timer = Timer.periodic(const Duration(milliseconds: 100), (timer) {
if (isClosed) { if (isClosed) {
// debugPrint('$logPrefix ❌ Cubit已关闭,取消定时器'); // debugPrint('$logPrefix ❌ Cubit已关闭,取消定时器');
@@ -538,27 +678,16 @@ class RemoteControlCubit extends Cubit<RemoteControlState> {
} }
void respondPermission(bool agreed, String deviceId) { void respondPermission(bool agreed, String deviceId) {
// 🔥 记录响应时间,开启冷却期
_lastPermissionResponseTime = DateTime.now();
//debugPrint('$logPrefix 开启权限请求冷却期,持续${_coolDownDuration.inSeconds}秒');
// 1. 关闭弹窗(立即关闭,防止重复点击) // 1. 关闭弹窗(立即关闭,防止重复点击)
emit(state.copyWith(showPermissionRequestDialog: false)); emit(state.copyWith(showPermissionRequestDialog: false));
// 2. 发送响应到服务器 // 2. 发送响应到服务器
//debugPrint('$logPrefix 📤 发送权限响应命令到服务器');
try { try {
// 🔥 关键调用:发送 TCP 指令
debugPrint('{_coolDownDuration.inSeconds}秒');
_repository.respondPermission(agreed, deviceId); _repository.respondPermission(agreed, deviceId);
} catch (e) { } catch (e) {
debugPrint(' ❌ TCP权限响应指令发送失败: $e'); debugPrint(' ❌ TCP权限响应指令发送失败: $e');
debugPrint(' ❌ 错误类型: ${e.runtimeType}'); debugPrint(' ❌ 错误类型: ${e.runtimeType}');
_logger.logWithLevel(' TCP权限响应指令发送失败 $e', shouldLog: true); _logger.logWithLevel(' TCP权限响应指令发送失败 $e', shouldLog: true);
// 即使发送失败,也要更新状态
rethrow; rethrow;
} }
@@ -577,9 +706,7 @@ class RemoteControlCubit extends Cubit<RemoteControlState> {
state.copyWith(hasPermission: true, showPermissionRequestDialog: false), state.copyWith(hasPermission: true, showPermissionRequestDialog: false),
); );
} }
debugPrint( debugPrint('${_getTimePrefix()} ====权限弹窗响应结束=====');
'${_getTimePrefix()} ====权限弹窗响应结束=====',
);
} }
Future<void> requestControlPermissionS( Future<void> requestControlPermissionS(
@@ -625,12 +752,13 @@ class RemoteControlCubit extends Cubit<RemoteControlState> {
final failLogPrefix = final failLogPrefix =
'${_getTimePrefix()} 🔑 [RemoteControl] [请求权限接口-$source]'; '${_getTimePrefix()} 🔑 [RemoteControl] [请求权限接口-$source]';
debugPrint('$failLogPrefix APP请求控制权限失败: ${failure.message}'); debugPrint('$failLogPrefix APP请求控制权限失败: ${failure.message}');
debugPrint('$failLogPrefix 重新打开权限请求弹窗');
_logger.logWithLevel( _logger.logWithLevel(
'$failLogPrefix APP请求控制权限失败: ${failure.message}', '$failLogPrefix APP请求控制权限失败: ${failure.message}',
shouldLog: true, shouldLog: true,
); );
emit(state.copyWith(showPermissionRequestDialog: true)); // 🔥 修复:HTTP请求失败时不要自动打开弹窗,避免形成循环
// emit(state.copyWith(showPermissionRequestDialog: true));
debugPrint('$failLogPrefix HTTP请求失败,不自动打开弹窗');
}, },
(permissionInfo) async { (permissionInfo) async {
final bool hasPermission = final bool hasPermission =
@@ -689,7 +817,11 @@ class RemoteControlCubit extends Cubit<RemoteControlState> {
); );
} }
/// 🔥 权限弹窗确认后调用- 发送TCP响应 + HTTP确认最终权限状态 /// 🔥 重置弹窗状态 - 在弹窗关闭后调用(已简化,不再需要标志位)
void resetPermissionCoolDown() {
// 标志位已移除,此方法保留以保持向后兼容性
}
Future<void> confirmPermissionResponse( Future<void> confirmPermissionResponse(
String deviceName, String deviceName,
String platform, String platform,
@@ -702,11 +834,12 @@ class RemoteControlCubit extends Cubit<RemoteControlState> {
debugPrint('$logPrefix 用户操作: ${agreed ? "同意" : "拒绝"}'); debugPrint('$logPrefix 用户操作: ${agreed ? "同意" : "拒绝"}');
debugPrint('$logPrefix deviceName: $deviceName'); debugPrint('$logPrefix deviceName: $deviceName');
debugPrint('$logPrefix platform: $platform'); debugPrint('$logPrefix platform: $platform');
respondPermission(agreed, deviceName); respondPermission(agreed, deviceName);
// 2. 调用 HTTP 接口获取最终权限状态 // 2. 调用 HTTP 接口获取最终权限状态
debugPrint('$logPrefix 📡 调用 HTTP 接口确认最终权限状态..'); debugPrint('$logPrefix 📡 调用 HTTP 接口确认最终权限状态..');
/* final result = await _requestControlPermissionUseCase( /* final result = await _requestControlPermissionUseCase(
RequestControlPermissionParams( RequestControlPermissionParams(
deviceName: deviceName, deviceName: deviceName,
deviceId: platform, deviceId: platform,
@@ -714,7 +847,7 @@ class RemoteControlCubit extends Cubit<RemoteControlState> {
);*/ );*/
// 3. 根据 HTTP 返回的真实权限状态更新UI // 3. 根据 HTTP 返回的真实权限状态更新UI
/* result.fold( /* result.fold(
(failure) { (failure) {
final failLogPrefix = '${_getTimePrefix()} 🔑 [RemoteControl] [权限确认]'; final failLogPrefix = '${_getTimePrefix()} 🔑 [RemoteControl] [权限确认]';
debugPrint('$failLogPrefix APP HTTP请求失败: ${failure.message}'); debugPrint('$failLogPrefix APP HTTP请求失败: ${failure.message}');
@@ -734,7 +867,7 @@ class RemoteControlCubit extends Cubit<RemoteControlState> {
'$successLogPrefix 🔄 正在更新UI - hasPermission: ${state.hasPermission} -> $hasPermission', '$successLogPrefix 🔄 正在更新UI - hasPermission: ${state.hasPermission} -> $hasPermission',
); );
// 🔥 直接用HTTP 返回的权限状态覆盖 // 🔥 直接用HTTP 返回的权限状态覆盖
emit(state.copyWith(hasPermission: hasPermission)); // emit(state.copyWith(hasPermission: hasPermission));
debugPrint( debugPrint(
'$successLogPrefix 📊 UI已同步完成- 当前 hasPermission=$hasPermission', '$successLogPrefix 📊 UI已同步完成- 当前 hasPermission=$hasPermission',
); );

View File

@@ -14,8 +14,8 @@ class RemoteControlState extends Equatable {
final bool isEmergency; final bool isEmergency;
final bool isLocked; final bool isLocked;
final int ping; final int ping;
final int battery;//电池量 final int battery; //电池量
final int voltage;//电压 final int voltage; //电压
final String permissionPlatform; final String permissionPlatform;
final String currentPlatform; final String currentPlatform;
final bool showPermissionRequestDialog; final bool showPermissionRequestDialog;
@@ -28,8 +28,11 @@ class RemoteControlState extends Equatable {
final bool obstacleRecognitionFlag; // 障碍物识别标志位(这是UI显示的) final bool obstacleRecognitionFlag; // 障碍物识别标志位(这是UI显示的)
final String obstacleFlag; //障碍物标志位 final String obstacleFlag; //障碍物标志位
final DeviceEntity? targetDevice; // 🔥 待控制的设备
final RunningStatusModel runningStatusModel; final RunningStatusModel runningStatusModel;
final DeviceEntity? targetDevice;
// 🔥 状态更新类型标记 - 用于区分是设备状态更新还是弹窗状态更新
final String? updateType;
const RemoteControlState({ const RemoteControlState({
this.status = RemoteControlStatus.initial, this.status = RemoteControlStatus.initial,
@@ -53,6 +56,7 @@ class RemoteControlState extends Equatable {
this.topRightIsExpanded = false, this.topRightIsExpanded = false,
this.obstacleRecognitionFlag = true, this.obstacleRecognitionFlag = true,
this.targetDevice, this.targetDevice,
this.updateType,
}); });
// 便利 UI 更新部分属性 // 便利 UI 更新部分属性
@@ -78,7 +82,7 @@ class RemoteControlState extends Equatable {
bool? topRightIsExpanded, bool? topRightIsExpanded,
bool? obstacleRecognitionFlag, bool? obstacleRecognitionFlag,
DeviceEntity? targetDevice, DeviceEntity? targetDevice,
String? updateType,
}) { }) {
return RemoteControlState( return RemoteControlState(
status: status ?? this.status, status: status ?? this.status,
@@ -101,9 +105,10 @@ class RemoteControlState extends Equatable {
runningStatusModel: runningStatusModel ?? this.runningStatusModel, runningStatusModel: runningStatusModel ?? this.runningStatusModel,
obstacleFlag: obstacleFlag ?? this.obstacleFlag, obstacleFlag: obstacleFlag ?? this.obstacleFlag,
topRightIsExpanded: topRightIsExpanded ?? this.topRightIsExpanded, topRightIsExpanded: topRightIsExpanded ?? this.topRightIsExpanded,
obstacleRecognitionFlag: obstacleRecognitionFlag ?? this.obstacleRecognitionFlag, obstacleRecognitionFlag:
obstacleRecognitionFlag ?? this.obstacleRecognitionFlag,
targetDevice: targetDevice ?? this.targetDevice, targetDevice: targetDevice ?? this.targetDevice,
updateType: updateType, // 不使用 ??,因为我们想要 null 的时候就是 null
); );
} }
@@ -130,8 +135,6 @@ class RemoteControlState extends Equatable {
topRightIsExpanded, topRightIsExpanded,
obstacleRecognitionFlag, obstacleRecognitionFlag,
targetDevice, targetDevice,
updateType,
]; ];
}
}

View File

@@ -33,6 +33,7 @@ class _RemoteControlPageState extends State<RemoteControlPage> {
DevicesCubit? _devicesCubit; DevicesCubit? _devicesCubit;
StreamSubscription? _permissionSubscription; // 🔥 权限监听订阅 StreamSubscription? _permissionSubscription; // 🔥 权限监听订阅
bool _isShowingPermissionDialog = false; // 🔥 防止弹窗重复显示 bool _isShowingPermissionDialog = false; // 🔥 防止弹窗重复显示
bool? _lastPermissionDialogState; // 🔥 记录上次弹窗状态,检测状态变化
bool _isLoadingPermission = true; // 🔥 标记是否正在加载权限状态 bool _isLoadingPermission = true; // 🔥 标记是否正在加载权限状态
@override @override
@@ -44,10 +45,28 @@ class _RemoteControlPageState extends State<RemoteControlPage> {
// 🔥 监听权限弹窗状态变化 (只订阅一次) // 🔥 监听权限弹窗状态变化 (只订阅一次)
if (_permissionSubscription == null) { if (_permissionSubscription == null) {
_permissionSubscription = _cubit.stream.listen((state) { _permissionSubscription = _cubit.stream.listen((state) {
// 🔥 加强防重复逻辑:只在状态真正变化且不在显示弹窗时才显示 // 🔥 关键修复:只有当状态更新类型是弹窗更新时才处理弹窗逻辑
if (mounted && // 设备状态更新(500ms)不会触发弹窗显示
if (state.updateType != 'permission_dialog') {
// 如果是设备状态更新,只重置状态记录,不显示弹窗
if (!state.showPermissionRequestDialog) {
_lastPermissionDialogState = false;
}
return; // 跳过设备状态更新
}
// 🔥 只有当状态从 false 变为 true 时才显示弹窗
// 使用三个条件确保不会重复显示
final wasFalseBefore =
_lastPermissionDialogState == null ||
_lastPermissionDialogState == false;
final shouldShow =
state.showPermissionRequestDialog && state.showPermissionRequestDialog &&
!_isShowingPermissionDialog) { !_isShowingPermissionDialog &&
wasFalseBefore;
if (mounted && shouldShow) {
_lastPermissionDialogState = true; // 记录当前状态
_isShowingPermissionDialog = true; _isShowingPermissionDialog = true;
debugPrint('🔔 [权限弹窗] 检测到 showPermissionRequestDialog=true,准备显示弹窗'); debugPrint('🔔 [权限弹窗] 检测到 showPermissionRequestDialog=true,准备显示弹窗');
// 使用微任务确保标志位已设置 // 使用微任务确保标志位已设置
@@ -55,10 +74,17 @@ class _RemoteControlPageState extends State<RemoteControlPage> {
_showPermissionDialog(state).then((_) { _showPermissionDialog(state).then((_) {
debugPrint('🔔 [权限弹窗] 弹窗已关闭,重置标志位'); debugPrint('🔔 [权限弹窗] 弹窗已关闭,重置标志位');
_isShowingPermissionDialog = false; // 弹窗关闭后重置标志 _isShowingPermissionDialog = false; // 弹窗关闭后重置标志
_lastPermissionDialogState = false; // 重置状态记录
}); });
}); });
} else if (!state.showPermissionRequestDialog) {
// 状态变为 false 时,重置记录
_lastPermissionDialogState = false;
} }
}); });
} else {
// 🔥 如果已经订阅,重置状态记录,确保下次能正确检测状态变化
_lastPermissionDialogState = _cubit.state.showPermissionRequestDialog;
} }
} }
@@ -344,7 +370,7 @@ class _RemoteControlPageState extends State<RemoteControlPage> {
actions: [ actions: [
TextButton( TextButton(
onPressed: () async { onPressed: () async {
// debugPrint('👆 [权限弹窗] 用户点击了拒绝按钮'); // debugPrint('👆 [权限弹窗] 用户点击了拒绝按钮');
final deviceId = final deviceId =
context context
.read<DevicesCubit>() .read<DevicesCubit>()
@@ -355,19 +381,19 @@ class _RemoteControlPageState extends State<RemoteControlPage> {
final remoteCubit = context.read<RemoteControlCubit>(); final remoteCubit = context.read<RemoteControlCubit>();
// 🔥 添加详细状态日志 - 检查为什么TCP指令发不出去 // 🔥 添加详细状态日志 - 检查为什么TCP指令发不出去
// debugPrint('👆 [权限弹窗] ====================状态检查===================='); // debugPrint('👆 [权限弹窗] ====================状态检查====================');
// debugPrint('👆 [权限弹窗] showPermissionRequestDialog: ${remoteCubit.state.showPermissionRequestDialog}'); // debugPrint('👆 [权限弹窗] showPermissionRequestDialog: ${remoteCubit.state.showPermissionRequestDialog}');
// debugPrint('👆 [权限弹窗] targetDevice: ${remoteCubit.state.targetDevice}'); // debugPrint('👆 [权限弹窗] targetDevice: ${remoteCubit.state.targetDevice}');
// debugPrint('👆 [权限弹窗] requestingDeviceId: ${remoteCubit.state.requestingDeviceId}'); // debugPrint('👆 [权限弹窗] requestingDeviceId: ${remoteCubit.state.requestingDeviceId}');
// debugPrint('👆 [权限弹窗] hasPermission: ${remoteCubit.state.hasPermission}'); // debugPrint('👆 [权限弹窗] hasPermission: ${remoteCubit.state.hasPermission}');
// debugPrint('👆 [权限弹窗] =============================================='); // debugPrint('👆 [权限弹窗] ==============================================');
/* debugPrint( /* debugPrint(
'📋 [权限弹窗] targetDevice=${remoteCubit.state.targetDevice}', '📋 [权限弹窗] targetDevice=${remoteCubit.state.targetDevice}',
);*/ );*/
// 🔥 先发送 TCP 响应,等完成后再关闭弹窗 // 🔥 先发送 TCP 响应,等完成后再关闭弹窗
// debugPrint('🔑 [权限弹窗] 用户拒绝,调用 confirmPermissionResponse'); // debugPrint('🔑 [权限弹窗] 用户拒绝,调用 confirmPermissionResponse');
final targetDevice = remoteCubit.state.targetDevice; final targetDevice = remoteCubit.state.targetDevice;
if (targetDevice != null) { if (targetDevice != null) {
debugPrint( debugPrint(
@@ -385,8 +411,18 @@ class _RemoteControlPageState extends State<RemoteControlPage> {
); );
} }
// 🔥 关闭弹窗(在 TCP 发送完成后) // 🔥 关键修复:先重置弹窗标志位,再关闭弹窗
// 避免 Navigator.pop() 后上下文失效导致后续代码不执行
context.read<RemoteControlCubit>().resetPermissionCoolDown();
context.read<RemoteControlCubit>().emit(
context.read<RemoteControlCubit>().state.copyWith(
showPermissionRequestDialog: false,
),
);
// 🔥 关闭弹窗(在状态重置之后)
Navigator.pop(dialogContext); Navigator.pop(dialogContext);
debugPrint('🔔 [权限弹窗] 弹窗已关闭,重置标志位');
}, },
child: Text( child: Text(
AppLocalizations.of( AppLocalizations.of(
@@ -407,7 +443,7 @@ class _RemoteControlPageState extends State<RemoteControlPage> {
final remoteCubit = context.read<RemoteControlCubit>(); final remoteCubit = context.read<RemoteControlCubit>();
// 🔥 添加详细状态日志 - 检查为什么TCP指令发不出去 // 🔥 添加详细状态日志 - 检查为什么TCP指令发不出去
/* debugPrint( /* debugPrint(
'👆 [权限弹窗] ====================状态检查====================', '👆 [权限弹窗] ====================状态检查====================',
); );
debugPrint( debugPrint(
@@ -434,9 +470,7 @@ class _RemoteControlPageState extends State<RemoteControlPage> {
debugPrint('🔑 [权限弹窗] 用户同意,调用 confirmPermissionResponse'); debugPrint('🔑 [权限弹窗] 用户同意,调用 confirmPermissionResponse');
final targetDevice = remoteCubit.state.targetDevice; final targetDevice = remoteCubit.state.targetDevice;
if (targetDevice != null) { if (targetDevice != null) {
debugPrint( debugPrint('✅ [权限弹窗] targetDevice 不为空');
'✅ [权限弹窗] targetDevice 不为空',
);
await remoteCubit.confirmPermissionResponse( await remoteCubit.confirmPermissionResponse(
targetDevice.deviceName, targetDevice.deviceName,
'app', 'app',
@@ -449,8 +483,18 @@ class _RemoteControlPageState extends State<RemoteControlPage> {
); );
} }
// 🔥 关闭弹窗(在 TCP 发送完成后) // 🔥 关键修复:先重置弹窗标志位,再关闭弹窗
// 避免 Navigator.pop() 后上下文失效导致后续代码不执行
context.read<RemoteControlCubit>().resetPermissionCoolDown();
context.read<RemoteControlCubit>().emit(
context.read<RemoteControlCubit>().state.copyWith(
showPermissionRequestDialog: false,
),
);
// 🔥 关闭弹窗(在状态重置之后)
Navigator.pop(dialogContext); Navigator.pop(dialogContext);
debugPrint('🔔 [权限弹窗] 弹窗已关闭,重置标志位');
}, },
child: Text( child: Text(
AppLocalizations.of( AppLocalizations.of(