Files
flutterApp/lib/features/devices/presentation/bloc/devices_cubit.dart
Songzex 7f81cb3d7e 完成优化登录的优化
完成切换和切回APP数据的及时响应
2026-03-09 08:38:29 +08:00

390 lines
16 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'dart:collection';
import 'package:flutter/rendering.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:get_it/get_it.dart';
import 'package:maibu_satabot_v2/features/devices/domain/entities/device_entity.dart';
import 'package:maibu_satabot_v2/features/devices/domain/repositories/device_repository.dart';
import 'package:maibu_satabot_v2/features/devices/domain/usecases/generate_path_usecase.dart';
import 'package:maibu_satabot_v2/features/devices/domain/usecases/get_user_device_usecase.dart';
import 'package:maibu_satabot_v2/features/devices/domain/usecases/save_work_record_usecase.dart';
import 'package:maibu_satabot_v2/features/devices/domain/usecases/select_work_record_usecase.dart';
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';
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 '../../services/path_planning_service.dart';
import 'device_status_bloc.dart';
import 'device_status_event.dart';
import 'devices_state.dart';
class DevicesCubit extends Cubit<DevicesState> {
final GetUserDeviceUseCase _getUserDeviceUseCase;
final GetDeviceLocationUseCase _getDeviceLocationUseCase;
final DeviceRepository repository;
final GetWorkRecordUseCase _getWorkRecordUseCase;
final DeleteWorkRecordUseCase _deleteWorkRecordUseCase;
final BindDeviceUseCase _bindDeviceUseCase;
final UnbindDeviceUseCase _unbindDeviceUseCase;
final UpdateDevicenameUsecase _updateDevicename;
final SelectWorkRecordUseCase _selectWorkRecordUseCase;
final SaveWorkRecordUseCase _saveWorkRecordUseCase;
final GeneratePathUseCase _generatePathUseCase;
final RoutePlanningUseCase _routePlanningUseCase; //
final DeviceStatusBloc _deviceStatusBloc; // 🔥 新增字段
final TcpClient _tcpClient;
// 🔥 关键:注入全局服务
final PathPlanningService _pathPlanningService;
DevicesCubit(
this.repository,
this._getUserDeviceUseCase,
this._getDeviceLocationUseCase,
this._getWorkRecordUseCase,
this._deleteWorkRecordUseCase,
this._unbindDeviceUseCase,
this._updateDevicename,
this._selectWorkRecordUseCase,
this._saveWorkRecordUseCase,
this._generatePathUseCase,
this._routePlanningUseCase,
this._bindDeviceUseCase,
this._deviceStatusBloc,
this._tcpClient, this._pathPlanningService,
) : super(const DevicesState());
Future<void> unbindDevice(String deviceId, String deviceName) async {
emit(state.copyWith(isLoading: true, errorMessage: '', operationType: DeviceOperationType.unbind));
try {
final params = UnbindDeviceParams(deviceId, deviceName);
final result = await _unbindDeviceUseCase.call(params);
result.fold(
(failure) => emit(
state.copyWith(
isLoading: false,
errorMessage: failure.message ?? '解绑设备失败',
operationType: DeviceOperationType.none, // 操作结束重置
),
),
(successCode) {
// 核心修复:int 转 bool 条件判断
final isSuccess = successCode == 1; // 显式转为 bool
if (isSuccess) {
final updatedDevices = state.devices?.where((device) {
return device.deviceName != deviceId;
}).toList();
emit(
state.copyWith(
isLoading: false,
devices: updatedDevices,
selectedDevice: state.selectedDevice?.deviceName == deviceId ? null : state.selectedDevice,
errorMessage: '',
operationType: DeviceOperationType.none,
),
);
} else {
emit(state.copyWith(isLoading: false, errorMessage: '解绑失败:状态码 $successCode', operationType: DeviceOperationType.none));
}
},
);
} catch (e) {
emit(state.copyWith(isLoading: false, errorMessage: '解绑异常:${e.toString()}', operationType: DeviceOperationType.none));
}
}
Future<void> updateDeviceName(String deviceId, String deviceName) async {
emit(state.copyWith(isLoading: true, errorMessage: '', operationType: DeviceOperationType.updateName));
try {
final params = UpdateDevicenameParams(deviceId, deviceName);
final result = await _updateDevicename.call(params);
result.fold((failure) => emit(state.copyWith(isLoading: false, errorMessage: failure.message ?? '更新设备名称失败', operationType: DeviceOperationType.none)), (
successCode,
) {
// 核心修复:int 转 bool 条件判断
print('更新设备名称结果代码: $successCode'); // 调试输出结果代码
final isSuccess = successCode == 1; // 显式转为 bool
if (isSuccess) {
//final updatedDevices = state.devices?.map((device) {
// return device.deviceName == deviceId ? device.copyWith(deviceName: deviceName) : device;
//}).toList();
emit(
state.copyWith(
isLoading: false,
//devices: updatedDevices,
//selectedDevice: state.selectedDevice?.deviceName == deviceId ? state.selectedDevice?.copyWith(deviceName: deviceName) : state.selectedDevice,
errorMessage: '',
operationType: DeviceOperationType.none,
),
);
} else {
emit(state.copyWith(isLoading: false, errorMessage: '更新设备名称失败:状态码 $successCode', operationType: DeviceOperationType.none));
}
});
} catch (e) {
emit(state.copyWith(isLoading: false, errorMessage: '更新设备名称异常:${e.toString()}', operationType: DeviceOperationType.none));
}
}
// 获取所有设备列表
// 获取所有设备列表
Future<void> fetchAllDevices(String username) async {
emit(state.copyWith(isLoading: true));
try {
// 🔥 关键步骤1:记录刷新前的选中设备标识(用 deviceName 作为唯一标识)
final String? oldSelectedDeviceName = state.selectedDevice?.deviceName;
// 网络请求获取新列表
var resultEither = await _getUserDeviceUseCase.call(GetUserDeviceParams(username));
resultEither.fold((failure) => emit(state.copyWith(isLoading: false, errorMessage: failure.message)), (deviceList) {
// 🔥 关键步骤2:匹配新列表中对应的旧选中设备
DeviceEntity? newSelectedDevice;
if (oldSelectedDeviceName != null && deviceList.isNotEmpty) {
// 在新列表中查找和旧选中设备名称一致的设备
newSelectedDevice = deviceList.firstWhere(
(device) => device.deviceName == oldSelectedDeviceName,
// 如果找不到(如设备已解绑),返回 null
orElse: () => deviceList.first, // 兜底:选中第一个
);
} else {
// 无旧选中设备,默认选中第一个
newSelectedDevice = deviceList.isNotEmpty ? deviceList.first : null;
}
// 🔥 关键步骤3:更新状态,使用匹配后的选中设备
emit(
state.copyWith(
devices: deviceList,
selectedDevice: newSelectedDevice, // 保留旧选中设备
isLoading: false,
),
);
});
} catch (e) {
emit(state.copyWith(isLoading: false, errorMessage: e.toString()));
}
}
// 切换当前选中的设备
void selectDevice(DeviceEntity device) {
emit(state.copyWith(selectedDevice: device));
}
// 更新单个设备的状态(例如从 Tcp 收到实时电量更新)
void updateDeviceStatus(DeviceEntity updatedDevice) {
final newList = state.devices.map((d) {
return d.deviceName == updatedDevice.deviceName ? updatedDevice : d;
}).toList();
// 如果更新的是当前选中的设备,也要同步更新 selectedDevice
final newSelected = state.selectedDevice?.deviceName == updatedDevice.deviceName ? updatedDevice : state.selectedDevice;
emit(state.copyWith(devices: newList, selectedDevice: newSelected));
}
/// 切换设备
Future<void> switchDevice(DeviceEntity device) async {
// 保持现有列表,只改 loading
emit(state.copyWith(isLoading: true));
final result = await repository.switchDevice("app", device.deviceName);
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));
},
);
}
/// 绑定设备
Future<void> bindDevice(String deviceId, String deviceAlias) async {
emit(state.copyWith(isLoading: true, errorMessage: ''));
try {
final params = BindDeviceParams(deviceId, deviceAlias);
final result = await _bindDeviceUseCase.call(params);
result.fold(
// 失败处理
(failure) {
emit(state.copyWith(
isLoading: false,
errorMessage: failure.message ?? '绑定设备失败',
));
// 抛出异常,携带后端返回的错误消息(如“设备不存在”)
throw Exception(failure.message ?? '绑定设备失败');
},
// 成功处理(返回 int 状态码)
(successCode) {
if (successCode == 1) {
emit(state.copyWith(isLoading: false, errorMessage: ''));
// 绑定成功后刷新设备列表
//fetchAllDevices(state.?.username ?? '');
} else {
emit(state.copyWith(isLoading: false, errorMessage: '绑定失败:状态码 $successCode'));
}
},
);
} catch (e) {
// emit(state.copyWith(
// isLoading: false,
// errorMessage: '绑定异常:${e.toString()}',
// ));
// 🔥 重新抛出,传递给 UI
rethrow;
}
}
/// 获取设备位置
Future<void> getDeviceLocation(DeviceEntity device) async {
emit(state.copyWith(isLoading: true));
final result = await _getDeviceLocationUseCase.call(device.deviceName);
result.fold(
(failure) => emit(state.copyWith(isLoading: false, errorMessage: failure.message)),
(location) => emit(state.copyWith(isLoading: false, deviceLatitude: location.latitude, deviceLongitude: location.longitude)),
);
}
///获取记录
Future<void> loadWorkRecords(String userId) async {
emit(state.copyWith(isLoading: true));
final result = await _getWorkRecordUseCase(userId);
result.fold(
(failure) => emit(state.copyWith(isLoading: false, errorMessage: failure.message)),
(records) => emit(state.copyWith(isLoading: false, workRecords: records)),
);
}
/// 删除工作记录
Future<void> deleteWorkRecord(String workName) async {
emit(state.copyWith(isLoading: true));
final result = await _deleteWorkRecordUseCase(workName);
result.fold(
(failure) => emit(state.copyWith(isLoading: false, errorMessage: failure.message)),
(_) => emit(state.copyWith(isLoading: false, workRecords: state.workRecords?.where((record) => record != workName).toList())),
);
}
/// 加载指定作业名的路径数据
Future<void> loadSelectedPath(String workName) async {
emit(state.copyWith(isLoading: true));
try {
final result = await _selectWorkRecordUseCase.call(workName);
result.fold(
(failure) => emit(state.copyWith(isLoading: false, errorMessage: failure.message)),
(data) => emit(state.copyWith(isLoading: false, pathData: data)),
);
} catch (e) {
emit(state.copyWith(isLoading: false, errorMessage: e.toString()));
}
}
/// 保存路径数据
Future<void> saveWorkRecord(String workName, String userId, String jsonData) async {
emit(state.copyWith(isLoading: true));
final result = await _saveWorkRecordUseCase.call(workName: workName, userId: userId, jsonData: jsonData);
result.fold((failure) => emit(state.copyWith(isLoading: false, errorMessage: failure.message)), (data) => emit(state.copyWith(isLoading: false)));
}
/// generatePath
Future<void> generatePath({
required ReferencePoint reference,
required int heading,
required OuterBoundary outer,
required Map<String, HoleBoundary> holes,
required int workType,
}) async {
//holes.asMap().forEach((index, hole) {
// final pointsStr = hole.position.map((p) => "(${p.lat.toStringAsFixed(6)}, ${p.lon.toStringAsFixed(6)})").join(', ');
// debugPrint('第${index + 1}组holes:$pointsStr');
//});
emit(state.copyWith(isLoading: true));
final result = await _generatePathUseCase.execute(reference: reference, heading: heading, outer: outer, holes: holes, workType: workType);
result.fold((failure) => emit(state.copyWith(errorMessage: failure.message)), (pathData) => emit(state.copyWith(generatedPath: pathData)));
}
// 开始路径规划
Future<void> startRoutePlanning(Queue<DeviceAddPathPointModel> locationQueue) async {
// 清空全局 Service 中的队列
// _routePlanningUseCase.clearLocationQueue();
_pathPlanningService.updateQueue(locationQueue as List<DeviceAddPathPointModel>);
// 开始路径规划
emit(state.copyWith(isLoading: true));
//传入全局的Service 中路径规划队列
Queue<DeviceAddPathPointModel> queue = _pathPlanningService.getQueue() ;
final result = await _routePlanningUseCase.startRoutePlanning(queue);
result.fold(
(failure) => emit(state.copyWith(isLoading: false, errorMessage: failure.message ?? '路径规划启动失败')),
(_) => emit(state.copyWith(isLoading: false, errorMessage: '')),
);
}
// 暂停
Future<void> pauseRoutePlanning() async {
emit(state.copyWith(isLoading: true));
final result = await _routePlanningUseCase.pauseRPWork();
result.fold((failure) => emit(state.copyWith(isLoading: false, errorMessage: failure.message ?? '暂停失败')), (_) => emit(state.copyWith(isLoading: false)));
}
// 恢复
Future<void> resumeRoutePlanning() async {
emit(state.copyWith(isLoading: true));
final result = await _routePlanningUseCase.resumeRPWork();
result.fold((failure) => emit(state.copyWith(isLoading: false, errorMessage: failure.message ?? '恢复失败')), (_) => emit(state.copyWith(isLoading: false)));
}
// 停止
Future<void> stopRoutePlanning() async {
emit(state.copyWith(isLoading: true));
final result = await _routePlanningUseCase.stopRoutePlanning();
result.fold((failure) => emit(state.copyWith(isLoading: false, errorMessage: failure.message ?? '停止失败')), (_) => emit(state.copyWith(isLoading: false)));
}
}