完成修复登录缓慢问题-主要接口响应时间决定

完成切换设备机器状态的实时接受和UI实时刷新
完成优化tcp重连的体检,手动切换设备或自动断开后重连的区分
This commit is contained in:
2026-03-05 21:07:33 +08:00
parent 5653c044a1
commit 83a5dc7f32
9 changed files with 357 additions and 57 deletions

View File

@@ -164,9 +164,10 @@ Future<void> init() async {
/// 5. 状态管理 (Cubit/Bloc)
sl.registerLazySingleton(() => AppUserCubit()); // AuthCubit 依赖它,必须先注册
sl.registerLazySingleton(() => GetDeviceLocationUseCase(sl()));
sl.registerLazySingleton(() => DevicesCubit(sl(), sl(), sl(), sl(), sl(), sl(), sl(), sl(), sl(), sl(), sl(), sl()));
sl.registerLazySingleton(() => DevicesCubit(sl(), sl(), sl(), sl(), sl(), sl(), sl(), sl(), sl(), sl(), sl(), sl(),sl(),sl()));
sl.registerFactory(() => RemoteControlCubit(sl()));
/* // 工厂模式(留存,每次获取新实例)
sl.registerFactory(() => DeviceStatusBloc(sl<NetMessageDispatcher>()));
*/
//单例模式(全局共享一个实例)

View File

@@ -33,11 +33,28 @@ class TcpClient {
String? _lastHost;
int? _lastPort;
bool isUserSwitch = false; // 用户是否切换设备
bool _isSwitching = false;
final UserStorage _userStorage;
bool get isConnected => _socket != null;
void disconnects({bool forSwitch = false}) {
if (forSwitch) {
_isSwitching = true;
debugPrint('🚫 [TCP] 标记为切换断开,将禁止自动重连');
}
// 🔥 关键:立即取消可能已经存在或即将触发的重连定时器
_reconnectTimer?.cancel();
_heartbeatTimer?.cancel();
if (_socket != null) {
debugPrint('🔌 [TCP] 物理断开 Socket...');
_socket!.destroy(); // 或者 .close()
_socket = null;
}
}
// 通过参数配置,不硬编码
Future<void> connect({required String host, required int port}) async {
debugPrint('🔌 [TCP] 开始连接:$host:$port'); // ✅ 必须看到这条
@@ -89,19 +106,29 @@ class TcpClient {
},
onDone: (){
// TODO: 断线重连
debugPrint('onDone❌ [TCP] 连接已断开!');
debugPrint('来到断线重连!');
if (!_isSwitching) {
debugPrint('onDone❌ [TCP] 连接已断开!');
_handleDisconnect();
}
_isSwitching = false; // 重置标志,以免影响下次
},
onError: (e) {
debugPrint('❌ [TCP] 发生错误:$e');
_handleDisconnect(); // 统一走重连逻辑,保护 Controller 不被关闭
debugPrint('来到断线重连!error');
if (!_isSwitching) {
debugPrint('❌ [TCP] 发生错误:$e');
_handleDisconnect();
} // 统一走重连逻辑,保护 Controller 不被关闭
_isSwitching = false; // 重置标志,以免影响下次
},
);
} catch (e) {
rethrow; // 向上抛出连接异常
}
}
//
void _handleDisconnect() {
stopHeartbeat();
@@ -113,6 +140,7 @@ class TcpClient {
// ✅ 新增:调度重连
Future<void> _scheduleReconnect() async {
if (isUserSwitch) return;
if (_reconnectTimer != null) return;
debugPrint('⏳ 调度重连:Host=${_lastHost}, Port=${_lastPort}'); // ✅ 检查 Host/Port 是否为空
@@ -128,6 +156,25 @@ class TcpClient {
});
await _sendAuthPacket();
}
// ✅ 新增:调度重连
Future<void> _scheduleReconnectBySwitch(devname) async {
debugPrint('⏳ 被动调度重连:Host=${_lastHost}, Port=${_lastPort}'); // ✅ 检查 Host/Port 是否为空
if (_lastHost == null || _lastPort == null) {
debugPrint('❌ 无法重连:Host 或 Port 为空!');
return;
}
_reconnectTimer = Timer(const Duration(seconds: 5), () {
_reconnectTimer = null;
debugPrint('⏰ 被动-定时器触发,开始执行重连...');
connectBySwitch(host: _lastHost!, port: _lastPort!, deviceName: devname);
});
await _sendAuthPacketBySwitch(devname);
}
/// 发送数据
void send(Map<String, dynamic> data) {
if (_socket == null) throw Exception("Socket not connected");
@@ -182,16 +229,42 @@ class TcpClient {
}
// void disconnect() {
// stopHeartbeat(); // 先停心跳
// _socket?.destroy();
// _socket = null;
// // 2. 🔥 关键:取消重连定时器!防止断开后自动触发旧逻辑重连回第一个设备
// _reconnectTimer?.cancel();
// _reconnectTimer = null;
// // _controller.close();
// debugPrint("TCP Disconnected");
//
// }
void disconnect() {
stopHeartbeat(); // 先停心跳
_socket?.destroy();
_socket = null;
_controller.close();
debugPrint("TCP Disconnected");
debugPrint('🛑 [TCP] 主动断开连接...');
// 1. 停止心跳
stopHeartbeat();
// 2. 🔥 关键:取消重连定时器!防止断开后自动触发旧逻辑重连回第一个设备
_reconnectTimer?.cancel();
_reconnectTimer = null;
// 3. 销毁 Socket
if (_socket != null) {
_socket!.destroy();
_socket = null;
debugPrint('✅ [TCP] Socket 已物理销毁');
}
// ❌ 绝对不要关闭 _controller!否则数据流断裂,重连后收不到数据
// _controller.close();
debugPrint('✅ [TCP] 连接已断开,等待手动重连');
}
void sendPathPoint(RoutePlanSendEntity routePlanSendEntity) {
void sendPathPoint(RoutePlanSendEntity routePlanSendEntity) {
if (_socket == null) return;
final payload = routePlanSendEntity.toBytes();
final packet = sendRaw(0x01, payload); // 第1种的测试 声明指令结构和类型0x01 为命令类型
@@ -270,7 +343,7 @@ class TcpClient {
final DeviceEntity targetDevice = devices.first;
debugPrint('📱 [AuthTcp] 准备切换至默认设备:${targetDevice.deviceName}');
// 切换设备 (同样,如果 switchDeviceUseCase 也返回 Either,也需要 fold 处理)
// 切换设备
final switchResult = await switchDeviceUseCase.deviceRepository.switchDevice("app",targetDevice.deviceName);
await switchResult.fold(
@@ -295,4 +368,132 @@ class TcpClient {
if (_socket == null) return;
_socket!.add(bytes);
}
Future<void> connectBySwitch({required String host, required int port, required String deviceName}) async {
isUserSwitch=true;
debugPrint('🔌被动 [TCP] 开始连接:$host:$port'); // ✅ 必须看到这条
_lastHost = host;
_lastPort = port;
if (_socket != null) {
_socket!.destroy();
_socket = null;
}
try {
_socket = await Socket.connect(
host,
port,
timeout: const Duration(seconds: 5),
);
debugPrint('✅ 被动[TCP] 连接成功!'); // ✅ 必须看到这条
_socket!.listen((data) {
debugPrint('📥 被动[TCP] 收到原始数据:${data.length} 字节, 内容:$data');
// var packets = _decoder.decode(data);
// debugPrint('📦 [TCP] 解码成功,包数量:${packets.length}');
//for (var packet in packets) {
// // 🔥若收到服务端心跳(cmd == 0xFF),立即回复一个心跳包
// if (packet.command == 0xFF) {
// debugPrint('收到服务端心跳,自动回复...');
// sendHeartbeat(); // 回复 AB AA FF AA AB
// }
// }
// _controller.add(packet);
// }
try {
var packets = _decoder.decode(data);
debugPrint('📦 被动[TCP] 解码成功,包数量:${packets.length}');
for (var packet in packets) {
if (!_controller.isClosed) {
_controller.add(packet);
debugPrint('➡️被动 [TCP] 已分发 CMD: 0x${packet.command.toRadixString(16)}');
}
if (packet.command == 0xFF) {
debugPrint('收到服务端心跳,自动回复...');
sendHeartbeat(); // 回复 AB AA FF AA AB
}
if (packet.command == 0x03) {
debugPrint('⚠️ 收到认证响应:${packet.payload}');
// 解析 payload 看是否有错误信息
}
}
} catch (e, stackTrace) {
debugPrint('❌ 被动[TCP] 解码数据时发生异常:$e\n$stackTrace'); // 🔥 捕获解码异常
}
},
onDone: (){
// TODO: 断线重连
debugPrint('onDone❌ 被动[TCP] 连接已断开!');
_handleDisconnectBySwitch(deviceName);
},
onError: (e) {
debugPrint('❌ 被动[TCP] 发生错误:$e');
_handleDisconnectBySwitch(deviceName); // 统一走重连逻辑,保护 Controller 不被关闭
},
);
// 开始认证tcp
await _sendAuthPacketBySwitch(deviceName);
} catch (e) {
rethrow; // 向上抛出连接异常
}
}
Future<void> _sendAuthPacketBySwitch(String deviceName) async {
if (_socket == null) return;
String? username= "";
String? token= "";
final user = await _userStorage.getUser();
deviceName= deviceName;
debugPrint('tcp被动切换认证:$user');
if (user == null || user.token == null) {
debugPrint('❌ 被动[TCP] 认证失败:用户未登录或 Token 为空,无法发送认证包');
disconnect();
return; // 直接返回,不要发送无效包
}
username = user.username;
token = user.token;
final authString = '$username:app:$token';
debugPrint('🔑被动[TCP] 认证字符串:$authString');
final authBytes = utf8.encode(authString);
// 构造包结构:Head(2) + Cmd(1) + Payload(N) + CRC(2) + Foot(2)
// 总长度 = 3 + N + 4
final builder = BytesBuilder()
..addByte(0xAB)
..addByte(0xAA)
..addByte(0x03) // 认证指令
..add(authBytes);
// 添加 CRC (这里简单用 0x00 0x00 占位,如果协议严格校验需计算真实 CRC)
// Android 代码中是 packet[tailStart] = 0x00; packet[tailStart+1] = 0x00;
builder.addByte(0x00);
builder.addByte(0x00);
builder.addByte(0xAA);
builder.addByte(0xAB);
_socket!.add(builder.takeBytes());
debugPrint('🔑 被动[TCP] 已发送认证包 (0x03): $authString');
try {
// 1. 获取 Either 结果
await switchDeviceUseCase.deviceRepository.switchDevice("app",deviceName);
} catch (e) {
debugPrint('❌ 被动[AuthTcp] 设备订阅流程异常:$e');
rethrow;
}
}
void _handleDisconnectBySwitch( String deviceName) {
stopHeartbeat();
_socket = null;
// 注意:这里不要关闭 _controller!否则监听者会丢失数据流
// _controller?.close();
_scheduleReconnectBySwitch(deviceName);
}
}

View File

@@ -52,7 +52,7 @@ class AuthCubit extends Cubit<AuthState> {
Future<void> loginSuccess(UserEntity user) async {
await storage.saveUser(user);
await tcp.connect(host: TCPConsts.TCP_IP, port: TCPConsts.TCP_PORT);
await _authTcpDatasource.sendAuthPacket(); //包括发送认证包和获取列表和切换函数
// await _authTcpDatasource.sendAuthPacket(); //包括发送认证包和获取列表和切换函数
tcp.startHeartbeat(interval: const Duration(seconds: 4)); //启动心跳
appCubit.setAuth(user);
emit(AuthAuthenticated(user));

View File

@@ -1,6 +1,7 @@
import 'package:dio/dio.dart';
import 'package:flutter/widgets.dart';
import 'package:maibu_satabot_v2/core/consts/http_api_consts.dart';
import 'package:maibu_satabot_v2/core/network/tcp/tcp_client.dart';
import 'package:maibu_satabot_v2/features/devices/data/datasources/device_http_datasource.dart';
import 'package:maibu_satabot_v2/features/devices/domain/entities/device_entity.dart';

View File

@@ -1,10 +1,10 @@
import 'dart:async';
import 'dart:convert';
import 'package:flutter/cupertino.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:maibu_satabot_v2/features/devices/domain/entities/running_status_entity.dart';
import 'package:maibu_satabot_v2/features/devices/domain/entities/gps_entity.dart';
import 'package:maibu_satabot_v2/core/network/protocol_decoder.dart'; // 确保能访问 RawPacket
import 'package:maibu_satabot_v2/core/network/protocol_decoder.dart';
import '../../../../core/network/net_message_dispatcher.dart';
import 'device_status_event.dart';
@@ -13,41 +13,70 @@ import 'device_status_state.dart';
class DeviceStatusBloc extends Bloc<DeviceStatusEvent, DeviceStatusState> {
final NetMessageDispatcher _dispatcher;
// 持有订阅引用,仅在 close 时取消
StreamSubscription? _stringSub;
StreamSubscription? _jsonSub;
// 🔥 新增:标记是否已初始化订阅,防止重复订阅
bool _isSubscribed = false;
DeviceStatusBloc(this._dispatcher) : super(DeviceStatusInitial()) {
// 订阅 TCP 数据流
_dispatcher.onStringMessage().listen((jsonString) {
debugPrint('📩 Bloc 收到 0x02 数据');
add(DeviceStatusLoaded(jsonString));
});
// 1. 初始建立订阅(终身有效,除非 Bloc 关闭)
_setupStreamListeners();
_dispatcher.onJsonMessage(0x12).listen((jsonData) {
add(PushMessageReceived(jsonData));
});
//
// 2. 注册事件处理
on<DeviceStatusReset>(_handleReset);
on<DeviceStatusLoaded>(_handleDeviceStatusLoaded);
on<PushMessageReceived>(_handlePushMessageReceived);
// 🔥 新增:模拟测试数据(调试用)
// _testMockData();
}
void _testMockData() {
// 用户提供的真实指令数据(字节数组)
final mockBytes = [
171, 170, 2, 48, 46, 48, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 46, 48, 48, 44, 48, 46, 48, 48, 44, 48, 46, 48, 48, 44, 48, 46, 48, 48, 44, 50, 55, 46, 52, 55, 44, 49, 56, 48, 46, 48, 48, 44, 48, 46, 48, 48, 44, 48, 46, 48, 48, 44, 52, 49, 44, 52, 44, 49, 44, 51, 50, 46, 48, 52, 49, 50, 57, 54, 56, 54, 52, 48, 48, 57, 57, 57, 44, 49, 50, 48, 46, 56, 48, 56, 57, 57, 51, 48, 56, 49, 49, 53, 56, 53, 48, 44, 50, 48, 45, 49, 45, 49, 49, 45, 51, 49, 32, 48, 56, 58, 48, 48, 58, 48, 48, 44, 48, 44, 51, 44, 54, 54, 44, 57, 53, 50, 55, 46, 48, 48, 48, 48, 44, 48, 0, 0, 0, 170, 171
];
// 解码为字符串(去掉帧头 3 字节和帧尾 5 字节)
final payloadBytes = mockBytes.sublist(3, mockBytes.length - 5);
final mockData = String.fromCharCodes(payloadBytes);
// 🔥 核心修复:订阅逻辑只执行一次,不再随意 cancel/relisten
void _setupStreamListeners() {
if (_isSubscribed) {
return; // 如果已经订阅过,直接返回,避免重复操作
}
debugPrint('🧪 模拟测试数据:$mockData');
debugPrint('🔗 [DeviceStatusBloc] 初始化 TCP 数据流订阅(终身有效)');
// 发送模拟事件
add(DeviceStatusLoaded(mockData));
// 订阅字符串流 (0x02)
_stringSub = _dispatcher.onStringMessage().listen(
(jsonString) {
debugPrint('Bloc层已经!!收到 0x02 数据事件,长度:${jsonString.length}');
if (!isClosed) {
add(DeviceStatusLoaded(jsonString));
}
},
onDone: () => debugPrint('⚠️ [Bloc] 0x02 流已结束 (onDone) - 这通常意味着底层 TCP 彻底关闭'),
onError: (e) => debugPrint('❌ [Bloc] 0x02 流发生错误:$e'),
);
// 订阅 JSON 流 (0x12)
_jsonSub = _dispatcher.onJsonMessage(0x12).listen(
(jsonData) {
debugPrint('📩 [Bloc] 收到 JSON 数据事件:$jsonData');
if (!isClosed) {
add(PushMessageReceived(jsonData));
}
},
onDone: () => debugPrint('⚠️ [Bloc] JSON 流已结束 (onDone)'),
onError: (e) => debugPrint('❌ [Bloc] JSON 流发生错误:$e'),
);
_isSubscribed = true;
debugPrint('✅ [DeviceStatusBloc] 订阅建立完成,将持续监听数据流');
}
// 🔥 修改:使用 Emitter 发送状态
// 🔥 核心修复:重置时仅清空状态,绝对不再触碰订阅关系
Future<void> _handleReset(DeviceStatusReset event, Emitter<DeviceStatusState> emit) async {
debugPrint('🔄 收到重置事件:仅清空状态,保持订阅活跃(不重连)');
// 只 emit 初始状态,让 UI 清除旧设备的数据(如速度归零、轨迹清除)
emit(DeviceStatusInitial());
// ❌ 严禁在此处调用 _setupStreamListeners() 或 cancel 订阅
// 因为 TCP 重连期间数据流可能一直在推送,cancel 会导致关键首包丢失
}
Future<void> _handleDeviceStatusLoaded(
DeviceStatusLoaded event,
Emitter<DeviceStatusState> emit,
@@ -55,22 +84,21 @@ class DeviceStatusBloc extends Bloc<DeviceStatusEvent, DeviceStatusState> {
try {
debugPrint('🔍 开始解析数据:${event.jsonString}');
final fields = event.jsonString.trim().split(',');
debugPrint('🔍 字段数量:${fields.length}');
if (fields.length < 18) {
debugPrint('⚠️ 字段不足:${fields.length}');
emit(DeviceStatusError('字段不足,期望 ≥18,实际:${fields.length}'));
return;
}
final status = RunningStatusEntity.fromFields(fields);
debugPrint('设备运行时候状态:$status');
final gps = GPSEntity(status.latitude, status.longitude);
debugPrint('GPS: ${gps.latitude}, ${gps.longitude}');
emit(DeviceStatusUpdated(status, gps)); // 🔥 使用 emit 发送状态
} catch (e) {
debugPrint('❌ 解析异常:$e');
emit(DeviceStatusError('解析设备运行时候状态失败:$e'));
debugPrint('✅ 解析成功,更新状态:Lat=${gps.latitude}, Lng=${gps.longitude}');
emit(DeviceStatusUpdated(status, gps));
} catch (e, stack) {
debugPrint('❌ 解析异常:$e\n$stack');
emit(DeviceStatusError('解析失败:$e'));
}
}
@@ -82,6 +110,7 @@ class DeviceStatusBloc extends Bloc<DeviceStatusEvent, DeviceStatusState> {
final eventStr = event.jsonData['event'] ?? '';
final deviceId = event.jsonData['deviceId'] ?? '未知';
debugPrint('收到推送事件:$eventStr, 设备:$deviceId');
// 这里可以根据需要 emit 新状态
} catch (e) {
emit(DeviceStatusError('解析推送消息失败:$e'));
}
@@ -89,7 +118,15 @@ class DeviceStatusBloc extends Bloc<DeviceStatusEvent, DeviceStatusState> {
@override
Future<void> close() {
return super.close();
// 只有在 Bloc 彻底销毁时才取消订阅
//debugPrint('🚫 [DeviceStatusBloc] 正在关闭,取消所有订阅 ');
// debugPrint('🚫 [DeviceStatusBloc] 正在关闭,取消所有订阅');
//debugPrint('🔥 [DeviceStatusBloc] 销毁堆栈跟踪:\n${StackTrace.current}');
// _stringSub?.cancel();
// _jsonSub?.cancel();
//return super.close();
return Future.value();
}
}

View File

@@ -22,3 +22,8 @@ class PushMessageReceived extends DeviceStatusEvent {
List<Object?> get props => [jsonData];
}
class DeviceStatusReset extends DeviceStatusEvent {
@override
// TODO: implement props
List<Object?> get props => throw UnimplementedError();
}

View File

@@ -11,6 +11,8 @@ import 'package:maibu_satabot_v2/features/devices/domain/usecases/select_work_re
import 'package:maibu_satabot_v2/features/devices/domain/usecases/unbind_device_usecase.dart';
import 'package:maibu_satabot_v2/features/devices/domain/usecases/update_devicename_usecase.dart';
import '../../../../core/consts/tcp_consts.dart';
import '../../../../core/network/tcp/tcp_client.dart';
import '../../data/models/device_add_path_point_model.dart';
import '../../data/models/device_work_area_param_model.dart';
import '../../domain/usecases/bind_device_usecase.dart';
@@ -18,6 +20,8 @@ import '../../domain/usecases/delete_work_record_usecase.dart';
import '../../domain/usecases/get_device_location_usecase.dart';
import '../../domain/usecases/get_work_record_usecase.dart';
import '../../domain/usecases/route_planning_usecase.dart';
import 'device_status_bloc.dart';
import 'device_status_event.dart';
import 'devices_state.dart';
class DevicesCubit extends Cubit<DevicesState> {
@@ -33,7 +37,8 @@ class DevicesCubit extends Cubit<DevicesState> {
final SaveWorkRecordUseCase _saveWorkRecordUseCase;
final GeneratePathUseCase _generatePathUseCase;
final RoutePlanningUseCase _routePlanningUseCase; //
final DeviceStatusBloc _deviceStatusBloc; // 🔥 新增字段
final TcpClient _tcpClient;
DevicesCubit(
this.repository,
this._getUserDeviceUseCase,
@@ -47,6 +52,9 @@ class DevicesCubit extends Cubit<DevicesState> {
this._generatePathUseCase,
this._routePlanningUseCase,
this._bindDeviceUseCase,
this._deviceStatusBloc, this._tcpClient,
) : super(const DevicesState());
Future<void> unbindDevice(String deviceId, String deviceName) async {
@@ -192,10 +200,41 @@ class DevicesCubit extends Cubit<DevicesState> {
final result = await repository.switchDevice("app", device.deviceName);
result.fold((l) {
emit(state.copyWith(isLoading: false, errorMessage: l.message));
selectDevice(device);
}, (r) => emit(state.copyWith(isLoading: false, selectedDevice: device)));
result.fold(
// 失败处理
(l) {
emit(state.copyWith(isLoading: false, errorMessage: l.message));
selectDevice(device);
},
// 成功处理:改为 async 函数块
(r) async {
// 3. 最后更新 UI 状态
try {
// 🔥 关键修复 1:先强制断开旧连接!
// 这一步会销毁旧 Socket,清除旧 Listener,防止旧数据继续推送
if (_tcpClient.isConnected) {
debugPrint('🛑 检测到已连接,先断开旧 TCP 连接...');
_tcpClient.disconnects(forSwitch: true);
// 稍微等待一下,确保底层 Socket 资源释放 (可选,但推荐)
await Future.delayed(const Duration(milliseconds: 100));
}
// 🔥 关键修复 2:发起新连接
// connect 方法内部会自动调用 _sendAuthPacket -> 获取设备列表 -> 自动订阅当前选中的设备
debugPrint('🔌 开始重新新连接 TCP,将自动订阅新设备:${device.deviceName}');
await _tcpClient.connectBySwitch(host: TCPConsts.TCP_IP, port: TCPConsts.TCP_PORT, deviceName: device.deviceName);
// 🔥 关键修复 3:重置设备状态 Bloc,清除旧设备图表数据
_deviceStatusBloc.add(DeviceStatusReset());
debugPrint('✅ 新设备切换流程完成');
} catch (e) {
debugPrint('⚠️ 设备切换成功,但 TCP 重连或状态重置失败:$e');
// 即使 TCP 失败,也更新 UI 选中状态,让用户知道切换了,只是没数据
}
emit(state.copyWith(isLoading: false, selectedDevice: device));
},
);
}
/// 绑定设备

View File

@@ -878,7 +878,19 @@ class _RunningStatusPageState extends State<RunningStatusPage> {
SliverToBoxAdapter(
child: BlocBuilder<DeviceStatusBloc, DeviceStatusState>(
builder: (context, state) {
debugPrint('🎨 [UI-Build] BlocBuilder 重建!当前状态类型:${state.runtimeType}');
// 检测到设备切换(状态重置)时,清空图表历史数据
if (state is DeviceStatusInitial) {
debugPrint('🧹 [UI] 检测到 Initial 状态,准备重置图表数据');
WidgetsBinding.instance.addPostFrameCallback((_) {
_resetChartData();
});
}
if (state is DeviceStatusUpdated) {
debugPrint('📈 [UI] 检测到 Updated 状态,准备追加图表数据');
_appendChartData(state);
}
return Container(margin: const EdgeInsets.all(8), child: _isCardView ? _buildCardContentView(state) : _buildChartContentView(state));
@@ -889,6 +901,8 @@ class _RunningStatusPageState extends State<RunningStatusPage> {
),
);
}
void _resetChartData() {}
}
// ====================== 带动画的圆形仪表盘 Widget ======================

View File

@@ -47,7 +47,7 @@ class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
// 在 runApp 之前调用 appStarted,确保 GoRouter 初始化时能获取到正确的初始状态
sl<AuthCubit>().appStarted();
final deviceStatusBloc = sl<DeviceStatusBloc>();
return MultiBlocProvider(
providers: [
// 核心修正:在这里提供 AuthCubit
@@ -66,7 +66,9 @@ class MyApp extends StatelessWidget {
return devicesCubit;
},
),
BlocProvider<DeviceStatusBloc>(create: (_) => sl<DeviceStatusBloc>()),
BlocProvider<DeviceStatusBloc>.value(
value: deviceStatusBloc,
),
// 其他 Cubit...
],
child: MaterialApp.router(title: 'Maibu Satabot', theme: AppTheme.lightTheme, routerConfig: sl<GoRouter>()),