优化了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
# 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.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'

View File

@@ -62,7 +62,10 @@ class AuthCubit extends Cubit<AuthState> {
if (user != null) {
// 🔥 冷启动时重新初始化 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 状态
appCubit.setAuth(user);

View File

@@ -21,6 +21,12 @@ class DeviceStatusBloc extends Bloc<DeviceStatusEvent, DeviceStatusState> {
// 🔥 保存订阅引用,用于管理生命周期
StreamSubscription? _tcpSubscription;
// 🔥 节流相关:500ms节流控制0x02数据推送频率
Timer? _throttleTimer;
static const _throttleDuration = Duration(milliseconds: 500);
RunningStatusEntity? _cachedStatus;
GPSEntity? _cachedGps;
DeviceStatusBloc(this._dispatcher, {TcpClient? client})
: tcpClient = client ?? GetIt.I<TcpClient>(),
super(DeviceStatusInitial()) {
@@ -95,6 +101,7 @@ class DeviceStatusBloc extends Bloc<DeviceStatusEvent, DeviceStatusState> {
if (fields.length < 18) {
//debugPrint('⚠️ [DeviceStatusBloc] 字段不足:${fields.length},期望≥18');
// 🔥 错误不节流,立即emit以便UI显示错误
if (!isClosed) {
emit(DeviceStatusError('字段不足,期望≥18,实际:${fields.length}'));
}
@@ -104,13 +111,8 @@ class DeviceStatusBloc extends Bloc<DeviceStatusEvent, DeviceStatusState> {
final status = RunningStatusEntity.fromFields(fields);
final gps = GPSEntity(status.latitude, status.longitude);
//debugPrint('✅ [DeviceStatusBloc] 直接解析成功,更新状态:Lat=${gps.latitude}, Lng=${gps.longitude}');
// _logger.log('✅ [DeviceStatusBloc] 直接解析成功,更新状态');
// 🔥 关键修复:BLoC有Equatable去重机制,必须创建新对象才能触发UI更新
if (!isClosed) {
// 创建全新的status和gps对象,绕过Equatable去重
final newStatus = RunningStatusEntity(
// 🔥 缓存最新数据用于节流发射
_cachedStatus = RunningStatusEntity(
voltage: status.voltage,
leftTargetSpeed: status.leftTargetSpeed,
rightTargetSpeed: status.rightTargetSpeed,
@@ -136,13 +138,17 @@ class DeviceStatusBloc extends Bloc<DeviceStatusEvent, DeviceStatusState> {
workingArea: status.workingArea,
obstacleFlag: status.obstacleFlag,
);
final newGps = GPSEntity(status.latitude, status.longitude);
// debugPrint('📤 [DeviceStatusBloc] emit DeviceStatusUpdated - 电压:${status.voltage}, 电量:${status.battery}, 模式:${status.controlMode}');
emit(DeviceStatusUpdated(newStatus, newGps));
}
_cachedGps = GPSEntity(status.latitude, status.longitude);
// 🔥 节流:取消之前的timer,重新计时500ms
_throttleTimer?.cancel();
_throttleTimer = Timer(_throttleDuration, () {
_emitCachedStatus();
});
} catch (e, stack) {
//debugPrint('❌ [DeviceStatusBloc] 直接解析异常:$e\n$stack');
// _logger.log('❌ [DeviceStatusBloc] 直接解析异常:$e');
// 🔥 解析错误不节流,立即emit以便UI显示错误
if (!isClosed) {
emit(DeviceStatusError('解析失败:$e'));
}
@@ -156,10 +162,23 @@ class DeviceStatusBloc extends Bloc<DeviceStatusEvent, DeviceStatusState> {
_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}');
_logger.log('🔄 [DeviceStatusBloc] 收到重置事件:清空状态 - 当前状态:${state.runtimeType}');
_logger.log(
'🔄 [DeviceStatusBloc] 收到重置事件:清空状态 - 当前状态:${state.runtimeType}',
);
// 只 emit 初始状态,让 UI 清除旧设备的数据
emit(DeviceStatusInitial());
@@ -214,6 +233,11 @@ class DeviceStatusBloc extends Bloc<DeviceStatusEvent, DeviceStatusState> {
debugPrint('🚫 [DeviceStatusBloc] 页面退出,仅取消TCP订阅(不关闭BLoC)');
_logger.log('🚫 [DeviceStatusBloc] 页面退出,仅取消TCP订阅(不关闭BLoC)');
//_tcpSubscription?.cancel();
// 🔥 清理节流timer和缓存
_throttleTimer?.cancel();
_throttleTimer = null;
_cachedStatus = null;
_cachedGps = null;
// 🔥 关键修复:不调用 super.close(),保持 BLoC 活跃
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_state.dart';
class PermissionRequestBloc extends Bloc<PermissionRequestEvent, PermissionRequestState> {
class PermissionRequestBloc
extends Bloc<PermissionRequestEvent, PermissionRequestState> {
final NetMessageDispatcher _dispatcher;
final RemoteControlRepository _repository;
final ILoggerService _logger = GetIt.I<ILoggerService>();
@@ -18,9 +19,11 @@ class PermissionRequestBloc extends Bloc<PermissionRequestEvent, PermissionReque
// 🔥 保存订阅引用,用于管理生命周期
StreamSubscription? _permissionSubscription;
PermissionRequestBloc(this._dispatcher, this._repository) : super(const PermissionRequestInitial()) {
PermissionRequestBloc(this._dispatcher, this._repository)
: super(const PermissionRequestInitial()) {
// 🔥 核心:直接在构造函数中建立 0x12 监听(通过 NetMessageDispatcher)
_initPermissionListener();
// 🔥 注意:RemoteControlCubit 已经在处理 0x12 权限请求,这里不再重复监听
// _initPermissionListener();
// 事件处理
on<PermissionRequestReceived>(_handleRequestReceived);
@@ -28,100 +31,53 @@ class PermissionRequestBloc extends Bloc<PermissionRequestEvent, PermissionReque
}
// 🔥 通过 NetMessageDispatcher 监听 TCP 0x12 指令,解析权限请求
// 🔥 已禁用:RemoteControlCubit 统一处理权限请求,避免重复弹窗
void _initPermissionListener() {
debugPrint('🔗 [PermissionRequestBloc] 初始化 0x12 权限监听器(通过 NetMessageDispatcher)');
_logger.logWithLevel('🔗 [PermissionRequestBloc] 初始化 0x12 权限监听器', shouldLog: true);
debugPrint(
'🔗 [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');
_logger.logWithLevel('>>> [PermissionRequestBloc] 收到 0x12: $jsonString', shouldLog: true);
final jsonMap = jsonDecode(jsonString);
final requestType = jsonMap['request'];
final platform = jsonMap['platform'];
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);
debugPrint(
'🔍 [PermissionRequestBloc] ⚠️ 权限监听已禁用,统一由 RemoteControlCubit 处理',
);
_logger.logWithLevel(
'>>> [PermissionRequestBloc] ⚠️ 权限监听已禁用,统一由 RemoteControlCubit 处理',
shouldLog: true,
);
}
Future<void> _handleRequestReceived(
PermissionRequestReceived event,
Emitter<PermissionRequestState> emit,
) async {
debugPrint('📤 [PermissionRequestBloc] emit DialogVisible - platform=${event.platform}');
_logger.logWithLevel('📤 [PermissionRequestBloc] emit DialogVisible - platform=${event.platform}', shouldLog: true);
debugPrint(
'📤 [PermissionRequestBloc] emit DialogVisible - platform=${event.platform}',
);
_logger.logWithLevel(
'📤 [PermissionRequestBloc] emit DialogVisible - platform=${event.platform}',
shouldLog: true,
);
emit(PermissionRequestDialogVisible(
emit(
PermissionRequestDialogVisible(
platform: event.platform,
requestType: event.requestType,
));
),
);
_logger.logWithLevel('📤 [PermissionRequestBloc] ✅ 已 emit DialogVisible 状态', shouldLog: true);
_logger.logWithLevel(
'📤 [PermissionRequestBloc] ✅ 已 emit DialogVisible 状态',
shouldLog: true,
);
}
Future<void> _handleDialogDismissed(
@@ -133,7 +89,9 @@ class PermissionRequestBloc extends Bloc<PermissionRequestEvent, PermissionReque
// 🔥 发送响应到机器人(和远程遥控页面一样)
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);
}
@@ -151,7 +109,10 @@ class PermissionRequestBloc extends Bloc<PermissionRequestEvent, PermissionReque
/// 🔥 公开方法:重新初始化监听器(TCP 重连后调用)
void reinitListener() {
debugPrint('🔄 [PermissionRequestBloc] 重新初始化监听器');
_logger.logWithLevel('🔄 [PermissionRequestBloc] 重新初始化监听器', shouldLog: true);
_logger.logWithLevel(
'🔄 [PermissionRequestBloc] 重新初始化监听器',
shouldLog: true,
);
_permissionSubscription?.cancel();
_initPermissionListener();
}

View File

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

View File

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

View File

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

View File

@@ -33,6 +33,7 @@ class _RemoteControlPageState extends State<RemoteControlPage> {
DevicesCubit? _devicesCubit;
StreamSubscription? _permissionSubscription; // 🔥 权限监听订阅
bool _isShowingPermissionDialog = false; // 🔥 防止弹窗重复显示
bool? _lastPermissionDialogState; // 🔥 记录上次弹窗状态,检测状态变化
bool _isLoadingPermission = true; // 🔥 标记是否正在加载权限状态
@override
@@ -44,10 +45,28 @@ class _RemoteControlPageState extends State<RemoteControlPage> {
// 🔥 监听权限弹窗状态变化 (只订阅一次)
if (_permissionSubscription == null) {
_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 &&
!_isShowingPermissionDialog) {
!_isShowingPermissionDialog &&
wasFalseBefore;
if (mounted && shouldShow) {
_lastPermissionDialogState = true; // 记录当前状态
_isShowingPermissionDialog = true;
debugPrint('🔔 [权限弹窗] 检测到 showPermissionRequestDialog=true,准备显示弹窗');
// 使用微任务确保标志位已设置
@@ -55,10 +74,17 @@ class _RemoteControlPageState extends State<RemoteControlPage> {
_showPermissionDialog(state).then((_) {
debugPrint('🔔 [权限弹窗] 弹窗已关闭,重置标志位');
_isShowingPermissionDialog = false; // 弹窗关闭后重置标志
_lastPermissionDialogState = false; // 重置状态记录
});
});
} else if (!state.showPermissionRequestDialog) {
// 状态变为 false 时,重置记录
_lastPermissionDialogState = false;
}
});
} else {
// 🔥 如果已经订阅,重置状态记录,确保下次能正确检测状态变化
_lastPermissionDialogState = _cubit.state.showPermissionRequestDialog;
}
}
@@ -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);
debugPrint('🔔 [权限弹窗] 弹窗已关闭,重置标志位');
},
child: Text(
AppLocalizations.of(
@@ -434,9 +470,7 @@ class _RemoteControlPageState extends State<RemoteControlPage> {
debugPrint('🔑 [权限弹窗] 用户同意,调用 confirmPermissionResponse');
final targetDevice = remoteCubit.state.targetDevice;
if (targetDevice != null) {
debugPrint(
'✅ [权限弹窗] targetDevice 不为空',
);
debugPrint('✅ [权限弹窗] targetDevice 不为空');
await remoteCubit.confirmPermissionResponse(
targetDevice.deviceName,
'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);
debugPrint('🔔 [权限弹窗] 弹窗已关闭,重置标志位');
},
child: Text(
AppLocalizations.of(