Merge remote-tracking branch 'origin/feature/my' into feature/my

This commit is contained in:
2026-03-25 15:24:18 +08:00
5 changed files with 367 additions and 281 deletions

View File

@@ -1,6 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
enum ToastType { success, error, warn, info, loading }
// 自定义Toast工具类 - 气泡式提示
class ToastUtils {
static OverlayEntry? _overlayEntry;

View File

@@ -96,20 +96,21 @@ class _LoginPageState extends State<LoginPage> {
width: double.infinity,
child: BlocBuilder<LoginCubit, LoginState>(
builder: (context, state) {
bool isLoading = state is LoginLoading; // 假设你有 Loading 状态
// 关键:判断是否正在 Loading
bool isLoading = state is LoginLoading;
return CCPrimaryButton(
onPressed: () {
if (!_isAgreed) {
// 如果没有勾选协议,弹出提示
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('请先阅读并同意用户协议')));
return;
}
if (state is! LoginLoading) {
_handleLogin();
}
},
text: state is LoginLoading ? '登录中...' : '登 录',
// 核心修复:正在加载时 → onPressed 为 null,按钮禁用
onPressed: isLoading
? null
: () {
if (!_isAgreed) {
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('请先阅读并同意用户协议')));
return;
}
_handleLogin();
},
text: isLoading ? '登录中...' : '登 录',
);
},
),

View File

@@ -64,6 +64,8 @@ const String kSavedWorkStatus = 'saved_work_status'; // 作业状态(idle/work
const String kSavedIsStartWork = 'saved_is_start_work'; // 是否开始作业
const String kSavedIsStopWork = 'saved_is_stop_work'; // 是否点击过停止
const String kSavedTPMode = "saved_tp_mode";
const String kSavedCurrentRobotMode = "kSavedCurrentRobotMode";
const String kSavedIsPanelOpen = "kSavedIsPanelOpen";
// 保持 PlotData 类不变
class PlotData {
@@ -97,6 +99,8 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
bool isreceiveFirstCompletePoint = false;
Offset _videoPopupPos = const Offset(0, 100); // 弹窗初始位置
final GlobalKey _mapRepaintKey = GlobalKey();
// 用于存储当前页面的 Toast OverlayEntry
List<OverlayEntry> _toastEntries = [];
final MapController _mapController = MapController();
// 初始化轨迹管理器(泛型指定为LatLng)
@@ -237,7 +241,23 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
await LocationUtils.clearCache(LocationUtils.kSavedMarkedPoints);
await LocationUtils.clearCache(LocationUtils.kSavedObstacleHoles);
await LocationUtils.clearCache(LocationUtils.kSavedIsWorkAreaCompleted);
debugPrint('本地存储的绘制数据已清空');
final prefs = await SharedPreferences.getInstance();
await prefs.remove(kSavedWorkStatus);
await prefs.remove(kSavedTPMode);
await prefs.remove(kSavedCurrentRobotMode); // 机器人模式
await prefs.remove("kSavedTypedPathList"); // 生成的路径
await prefs.remove(kSavedIsStartWork);
await prefs.remove(kSavedIsStopWork);
await prefs.remove(kSavedGcjPathPoints); // 路径点
await prefs.remove(kSavedGcjOuterPoints); // 外边框
await prefs.remove(kSavedMarkedPoints); // 打点
await prefs.remove(kSavedObstacleHoles); // 障碍物
await prefs.remove(kSavedIsWorkAreaCompleted);
await prefs.remove(kSavedIsWorkPanelOpen);
await prefs.remove(kSavedWorkMode);
await prefs.remove(kSavedStartWorkList);
await prefs.remove(kSavedSelectedPlot);
await prefs.remove(kSavedIsPanelOpen);
} catch (e) {
debugPrint('清空本地数据失败:$e');
}
@@ -256,12 +276,30 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
_traceManager.setMode(TPMode.values.firstWhere((e) => e.toString() == modeStr));
}
final robotModeStr = prefs.getString(kSavedCurrentRobotMode);
if (robotModeStr != null) {
_currentRobotMode = RobotMode.values.firstWhere((e) => e.toString() == robotModeStr, orElse: () => RobotMode.point);
}
// 读取 生成完成的路径 typedPathList
final typedPathString = prefs.getString("kSavedTypedPathList");
if (typedPathString != null) {
final List<dynamic> typedPathJson = jsonDecode(typedPathString);
setState(() {
typedPathList = typedPathJson.map((p) {
return work_area_model.DeviceAddPathPointModel(latitude: p['lat'], longitude: p['lng']);
}).toList();
});
}
// 2. 恢复是否开始作业
isStartWork = prefs.getBool(kSavedIsStartWork) ?? false;
// 3. 恢复是否点击过停止
isStopWork = prefs.getBool(kSavedIsStopWork) ?? false;
_isPanelOpen = prefs.getBool(kSavedIsPanelOpen) ?? false;
// 加载路径点
final pathJson = prefs.getString(kSavedGcjPathPoints);
if (pathJson != null) {
@@ -280,7 +318,7 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
final markedJson = prefs.getString(kSavedMarkedPoints);
if (markedJson != null) {
final List<dynamic> markedList = jsonDecode(markedJson);
_markedPoints = markedList.map((item) => LatLng(double.parse(item['lat'].toString()), double.parse(item['lng'].toString()))).toList().toList();
_markedPoints = markedList.map((item) => LatLng(double.parse(item['lat'].toString()), double.parse(item['lng'].toString()))).toList();
}
// 加载障碍物数据
@@ -298,11 +336,6 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
// 1. 恢复作业面板状态
_isWorkPanelOpen = prefs.getBool(kSavedIsWorkPanelOpen) ?? false;
// if (isStartWork && !isStopWork) {
// _isWorkPanelOpen = true;
// } else {
// _isWorkPanelOpen = false;
// }
// 2. 恢复作业模式
workMode = prefs.getString(kSavedWorkMode) ?? "弓字模式";
@@ -325,6 +358,19 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
);
}
// ==================== ✅ 核心修复 ====================
// 1. 从缓存恢复后,强制让 _markedPoints 同步给外边框(解决不显示)
if (_markedPoints.isNotEmpty && gcjOuterPoints.isEmpty) {
gcjOuterPoints = List.from(_markedPoints);
}
// 2. 关键:同步机器人模式坐标(保存时边框不丢失)
if (_currentRobotMode == RobotMode.point) {
_robotModeWgsPoints = _markedPoints.map((p) {
return gcj02ToWgs84(p.latitude, p.longitude);
}).toList();
}
if (mounted) {
setState(() {});
// 移动地图到绘制内容中心
@@ -363,9 +409,14 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
}).toList();
prefs.setString(kSavedObstacleHoles, jsonEncode(obstacleList));
final typedPath = typedPathList.map((point) => {'lat': point.latitude, 'lng': point.longitude}).toList();
prefs.setString("kSavedTypedPathList", jsonEncode(typedPath));
// 保存作业区域完成状态
prefs.setBool(kSavedIsWorkAreaCompleted, _isWorkAreaCompleted);
prefs.setBool(kSavedIsPanelOpen, _isPanelOpen);
// 1. 保存作业面板显示状态
prefs.setBool(kSavedIsWorkPanelOpen, _isWorkPanelOpen);
@@ -384,6 +435,8 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
// 保存当前模式到本地缓存
prefs.setString(kSavedTPMode, _traceManager.getMode().toString());
prefs.setString(kSavedCurrentRobotMode, _currentRobotMode.toString());
// 4. 保存选中地块
if (_selectedPlot != null) {
final plotMap = {
@@ -469,12 +522,14 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
if (isStartWork && !isStopWork) {
if (headingStatus == 0) {
if (!_showHeadingWarn) {
setState(() {
_showHeadingWarn = true;
});
if (mounted) {
setState(() {
_showHeadingWarn = true;
});
}
}
} else {
if (_showHeadingWarn) {
if (_showHeadingWarn && mounted) {
setState(() {
_showHeadingWarn = false;
});
@@ -486,13 +541,13 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
// ==============================================
String mode = controlMode?.toString() ?? "";
if (mode != "3") {
if (!_showControlModeWarn) {
if (!_showControlModeWarn && mounted) {
setState(() {
_showControlModeWarn = true;
});
}
} else {
if (_showControlModeWarn) {
if (_showControlModeWarn && mounted) {
setState(() {
_showControlModeWarn = false;
});
@@ -500,7 +555,7 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
}
} else {
// 未作业 → 自动隐藏所有警告
if (_showHeadingWarn || _showControlModeWarn) {
if ((_showHeadingWarn || _showControlModeWarn) && mounted) {
setState(() {
_showHeadingWarn = false;
_showControlModeWarn = false;
@@ -560,7 +615,9 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
} else {
// 截图失败时,允许空图片保存(可根据业务调整为强制失败)
_savePlotData(plotName, null);
ToastUtils.showWarn(context, '地块保存成功,但地图截图生成失败!');
_showPageToast(message: "地块保存成功,但地图截图生成失败!", type: ToastType.warn);
//ToastUtils.showWarn(context, '地块保存成功,但地图截图生成失败!');
//ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('地块保存成功,但地图截图生成失败!'), backgroundColor: Colors.amber));
}
}
@@ -621,7 +678,9 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
// 1. 获取用户ID
final userId = context.read<AppUserCubit>().state.user?.userId ?? "";
if (userId.isEmpty) {
ToastUtils.showError(context, '用户ID为空,无法保存!');
_showPageToast(message: "用户ID为空,无法保存!", type: ToastType.error);
//ToastUtils.showError(context, '用户ID为空,无法保存!');
return;
}
debugPrint("===打印生成的路径开始 ");
@@ -633,6 +692,15 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
final Map<String, dynamic> savePath = {
//'img': imgBase64 ?? '', // 截图的 Base64 字符串
'name': plotName, // 地块名称
'outer':
// 核心修改:根据机器人模式选择不同的坐标源
(_currentRobotMode == RobotMode.robot ? _robotModeWgsPoints : gcjOuterPoints).map((point) {
final wgs84 = _currentRobotMode == RobotMode.robot
? point // Robot模式直接使用原始WGS84坐标
: gcj02ToWgs84(point.latitude, point.longitude); // Point模式转换
return {'lng': wgs84.longitude, 'lat': wgs84.latitude};
}).toList(),
'path': _currentWorkMode == WorkMode.custom
? [] // 自定义模式 path 为空数组
: typedPathList
@@ -645,18 +713,9 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
)
.toList(),
'outer':
// 核心修改:根据机器人模式选择不同的坐标源
(_currentRobotMode == RobotMode.robot ? _robotModeWgsPoints : gcjOuterPoints).map((point) {
final wgs84 = _currentRobotMode == RobotMode.robot
? point // Robot模式直接使用原始WGS84坐标
: gcj02ToWgs84(point.latitude, point.longitude); // Point模式转换
return {'lng': wgs84.longitude, 'lat': wgs84.latitude};
}).toList(),
'planModel': _currentWorkMode == WorkMode.bow ? 0 : 2, // 作业模式值
};
debugPrint("$savePath savePath");
// 3. 构造 workRecord (包裹一层)
final Map<String, dynamic> workRecord = {
@@ -690,11 +749,11 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
),
);
//_debugPrintMultipartRequest(request);
// 7. 发送请求
try {
ToastUtils.showLoading(context, '正在保存地块「$plotName」...');
_showPageToast(message: "正在保存地块「$plotName」...", type: ToastType.loading);
//ToastUtils.showLoading(context, '正在保存地块「$plotName」...');
//ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('正在保存地块...'), backgroundColor: Colors.blue));
final http.StreamedResponse response = await request.send();
@@ -702,7 +761,9 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
debugPrint('保存地块响应体: $responseBody');
if (response.statusCode == 200) {
ToastUtils.showSuccess(context, '地块「$plotName」保存成功!');
_showPageToast(message: "地块「$plotName」保存成功!", type: ToastType.success);
//ToastUtils.showSuccess(context, '地块「$plotName」保存成功!');
_clearLocalData();
//ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('地块「$plotName」保存成功!'), backgroundColor: Colors.green));
// 保存成功
@@ -711,6 +772,7 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
gcjPathPoints.clear(); // 清空路径
gcjOuterPoints.clear(); // 清空外边界
_robotModeWgsPoints.clear();
_obstacleHoles.clear();
_isPanelOpen = false; // 关闭操作面板
_isWorkAreaCompleted = false; // 重置作业区域完成状态
@@ -720,7 +782,9 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
}
} catch (e) {
debugPrint('保存失败: $e');
ToastUtils.showError(context, '保存失败: ${e.toString()}');
_showPageToast(message: "保存失败: ${e.toString()}", type: ToastType.error);
//ToastUtils.showError(context, '保存失败: ${e.toString()}');
//ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('保存失败: ${e.toString()}'), backgroundColor: Colors.red));
}
}
@@ -739,17 +803,21 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
// 1. 作业区域模式
if (_currentAreaMode == AreaMode.work) {
if (_markedPoints.isNotEmpty) {
_robotModeWgsPoints.removeLast();
_markedPoints.removeLast();
// 单独判断,单独删除
if (_robotModeWgsPoints.isNotEmpty) {
_robotModeWgsPoints.removeLast();
}
if (_markedPoints.isNotEmpty) {
_markedPoints.removeLast();
}
// 作业点撤回后,若路径已生成,重新生成路径(保持同步)
if (_isWorkAreaCompleted && _markedPoints.length >= 3) {
_generatePath(showTips: false);
}
ToastUtils.showInfo(context, '已撤回作业区域最后一个点,剩余${_markedPoints.length}个点');
//ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('已撤回作业区域最后一个点,剩余${_markedPoints.length}个点')));
_showPageToast(message: "已撤回作业区域最后一个点,剩余${_markedPoints.length}个点", type: ToastType.info);
} else {
ToastUtils.showInfo(context, '暂无作业区域点可撤回');
//ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('暂无作业区域点可撤回')));
_showPageToast(message: "暂无作业区域点可撤回", type: ToastType.info);
}
return;
}
@@ -757,15 +825,19 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
// 2. 空洞模式(完成后优先撤回已完成的空洞)
if (_currentAreaMode == AreaMode.obstacle) {
if (_currentObstaclePoints.isNotEmpty) {
_currentObstaclePoints.removeLast();
_robotModeObsWgsPoints.removeLast();
// 单独判断,单独删除
if (_currentObstaclePoints.isNotEmpty) {
_currentObstaclePoints.removeLast();
}
if (_robotModeObsWgsPoints.isNotEmpty) {
_robotModeObsWgsPoints.removeLast();
}
if (_currentObstaclePoints.isEmpty) {
_isObstacleEditing = false;
}
ToastUtils.showInfo(context, '已撤回当前空洞最后一个点,剩余${_currentObstaclePoints.length}个点');
//ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('已撤回当前空洞最后1个点,剩余${_currentObstaclePoints.length}个点')));
return; // 撤完点就结束,不执行后面的逻辑
_showPageToast(message: "已撤回当前空洞最后一个点,剩余${_currentObstaclePoints.length}个点", type: ToastType.info);
return;
}
// 第二步:当前空洞无点可撤 → 再撤回「上一个完整空洞」
@@ -775,15 +847,12 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
if (_isWorkAreaCompleted) {
_generatePath(showTips: false);
}
ToastUtils.showInfo(context, '已撤回上一个完整空洞,剩余${_obstacleHoles.length}个空洞');
//ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('已撤回上一个完整空洞,剩余${_obstacleHoles.length}个空洞')));
_showPageToast(message: "已撤回上一个完整空洞,剩余${_obstacleHoles.length}个空洞", type: ToastType.info);
return;
}
// 第三步:无任何可撤内容
ToastUtils.showInfo(context, '暂无空洞可撤回');
//ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('暂无空洞可撤回')));
_showPageToast(message: "暂无空洞可撤回", type: ToastType.info);
}
});
_saveDataToLocal();
@@ -795,7 +864,9 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
// 1. 作业区域模式:清空作业点 + 重置路径
if (_currentAreaMode == AreaMode.work) {
if (_markedPoints.isEmpty) {
ToastUtils.showInfo(context, '暂无作业区域点可删除');
_showPageToast(message: "暂无作业区域点可删除", type: ToastType.info);
//ToastUtils.showInfo(context, '暂无作业区域点可删除');
//ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('暂无作业区域点可删除')));
return;
@@ -804,17 +875,21 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
gcjPathPoints.clear();
gcjOuterPoints.clear();
_robotModeWgsPoints.clear();
_obstacleHoles.clear();
_isWorkAreaCompleted = false;
_saveBoxOpen = false;
typedPathList.clear();
ToastUtils.showSuccess(context, '已清空所有作业区域点和路径');
_showPageToast(message: "已清空所有作业区域点和路径", type: ToastType.success);
//ToastUtils.showSuccess(context, '已清空所有作业区域点和路径');
//ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('已清空所有作业区域点和路径')));
}
// 2. 空洞模式:清空所有空洞 + 恢复UI + 重新生成路径
else if (_currentAreaMode == AreaMode.obstacle) {
if (_obstacleHoles.isEmpty && _currentObstaclePoints.isEmpty) {
ToastUtils.showInfo(context, '暂无障碍物可删除');
_showPageToast(message: "暂无障碍物可删除", type: ToastType.info);
//ToastUtils.showInfo(context, '暂无障碍物可删除');
return;
}
_obstacleHoles.clear();
@@ -825,7 +900,8 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
if (_isWorkAreaCompleted) {
_generatePath(showTips: false);
}
ToastUtils.showSuccess(context, '已清空所有障碍物');
_showPageToast(message: "已清空所有障碍物", type: ToastType.success);
//ToastUtils.showSuccess(context, '已清空所有障碍物');
}
});
_saveDataToLocal();
@@ -844,100 +920,82 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
// ========== 新增:页面刷新初始化方法 ==========
Future<void> _handleRefresh() async {
debugPrint("点击刷新");
// 防止重复刷新
_cancelAllToast(); // 👇 加这行
if (_isRefreshing) return;
// 🔥 修复1:保存当前有效的地图中心和缩放级别(而非定位坐标)
final savedZoom = _mapController.camera?.zoom ?? 17.0;
setState(() {
_isRefreshing = true; // 标记开始刷新
_isRefreshing = true;
});
debugPrint("点击刷新111");
try {
if (_currentLatLng != null && _isValidLatLng(_currentLatLng!.latitude, _currentLatLng!.longitude)) {
_mapController.move(_currentLatLng!, 18);
debugPrint("点击刷新333");
}
debugPrint("点击刷新2222");
// 2. 重置所有页面状态(恢复初始值)
setState(() {
_traceManager.setMode(TPMode.LOCATION);
isreceiveFirstCompletePoint = false;
// 2. 清空打点和轨迹数据
// 清空所有绘制数据
_markedPoints.clear();
gcjPathPoints.clear();
_robotModeObsWgsPoints.clear();
gcjOuterPoints.clear();
_robotModeWgsPoints.clear();
_obstacleHoles.clear();
// 3. 重置作业区域状态
// 重置作业区域状态
_isWorkAreaCompleted = false;
_saveBoxOpen = false;
_showControlModeWarn = false;
_showHeadingWarn = false;
// 4. 重置面板状态
_isPanelOpen = false;
_directionBoxOpen = false;
_isListBoxOpen = false;
_isWorkPanelOpen = false;
// ==============================================
// 🔥 核心修复:强制关闭所有弹窗/面板/警告
// ==============================================
_showControlModeWarn = false; // 关闭远程模式警告
_showHeadingWarn = false; // 关闭航向警告
_isPanelOpen = false; // 关闭底部操作面板
_directionBoxOpen = false; // 关闭航线方向面板
_isListBoxOpen = false; // 关闭地块列表面板
_isWorkPanelOpen = false; // 关闭作业面板
_isVideoDialogOpen = false; // 关闭视频弹窗
_isObstacleEditing = false; // 退出障碍物编辑
_selectedPlot = null; // 清空选中地块
// 5. 重置障碍物数据
_obstacleHoles.clear();
_currentObstaclePoints.clear();
_isObstacleEditing = false;
// 6. 重置选中状态
_selectedPlot = null;
// 7. 重置作业模式
// 重置模式
_currentRobotMode = RobotMode.robot;
_currentAreaMode = AreaMode.work;
_currentWorkMode = WorkMode.bow;
// 8. 重置作业参数
// 重置参数
_workDistance = 0.0;
_angle = 0.0;
// 9. 重置地图与定位状态(关键修复)
// 重置定位
_currentLatLng = null;
_currentWgsLatLng = null;
_mapCenter = _getDefaultValidCenter(); // 仅用兜底坐标初始化地图中心
_mapCenter = _getDefaultValidCenter();
// 10. 重置作业状态
// 重置作业状态
isDjMapShow = false;
_workStatus = WorkStatus.idle;
isStartWork = false;
isStopWork = false;
});
_clearLocalData();
// 4. 重置轨迹管理器
_clearLocalData();
_traceManager.reset();
// 5. 重新加载作业记录(保持数据最新)
// final userId = context.read<AppUserCubit>().state.user?.userId ?? "";
// if (userId.isNotEmpty) {
// await context.read<DevicesCubit>().loadWorkRecords(userId);
// }
// 刷新成功提示
if (mounted) {
ToastUtils.showSuccess(context, '页面已刷新完成');
//ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('页面已刷新完成'), backgroundColor: Colors.green, duration: Duration(seconds: 2)));
_showPageToast(message: "页面已刷新完成", type: ToastType.success);
//ToastUtils.showSuccess(context, '页面已刷新完成');
}
} catch (e) {
// 错误处理
if (mounted) {
ToastUtils.showError(context, '刷新失败:${e.toString().substring(0, 50)}');
//ScaffoldMessenger.of(
// context,
//).showSnackBar(SnackBar(content: Text('刷新失败:${e.toString().substring(0, 50)}'), backgroundColor: Colors.red, duration: Duration(seconds: 3)));
_showPageToast(message: "刷新失败:${e.toString().substring(0, 50)}", type: ToastType.error);
//ToastUtils.showError(context, '刷新失败:${e.toString().substring(0, 50)}');
}
} finally {
// 标记刷新结束
if (mounted) {
setState(() {
_isRefreshing = false;
@@ -955,7 +1013,8 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
// ========== 障碍物模式:完成当前障碍物绘制 ==========
void _completeObstacle() {
if (_currentObstaclePoints.length < 3) {
ToastUtils.showError(context, '障碍物区域至少需要3个打点!');
_showPageToast(message: "障碍物区域至少需要3个打点!", type: ToastType.error);
//ToastUtils.showError(context, '障碍物区域至少需要3个打点!');
return;
}
@@ -972,15 +1031,16 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
_isObstacleEditing = false; // 退出编辑状态
});
_saveDataToLocal();
ToastUtils.showSuccess(context, '已添加第${_obstacleHoles.length}个障碍物区域');
_showPageToast(message: "已添加第${_obstacleHoles.length}个障碍物区域", type: ToastType.success);
//ToastUtils.showSuccess(context, '已添加第${_obstacleHoles.length}个障碍物区域');
}
// ========== 核心方法:打点逻辑 ==========
void _addMarkedPoint() {
_traceManager.reset();
if (_currentAreaMode == AreaMode.work && _isWorkAreaCompleted) {
ToastUtils.showError(context, '作业区域已完成,无法继续添加打点!');
_showPageToast(message: "作业区域已完成,无法继续添加打点!", type: ToastType.error);
//ToastUtils.showError(context, '作业区域已完成,无法继续添加打点!');
return;
}
setState(() {
@@ -989,6 +1049,10 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
_markedPoints.add(_mapCenter);
debugPrint('新增作业区域打点:第${_markedPoints.length}个点,经纬度:${_mapCenter.latitude}, ${_mapCenter.longitude}');
} else {
if (_markedPoints.isEmpty) {
_showPageToast(message: "请先完成作业区域打点!", type: ToastType.info);
return;
}
// 障碍物模式:添加到当前障碍物打点
_currentObstaclePoints.add(_mapCenter);
_isObstacleEditing = true; // 标记进入障碍物编辑状态
@@ -996,7 +1060,8 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
}
} else {
if (_currentLatLng == null || !_isValidLatLng(_currentLatLng!.latitude, _currentLatLng!.longitude)) {
ToastUtils.showError(context, '设备坐标无效,无法添加打点!');
_showPageToast(message: "设备坐标无效,无法添加打点!", type: ToastType.error);
//ToastUtils.showError(context, '设备坐标无效,无法添加打点!');
return;
}
@@ -1006,6 +1071,10 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
_robotModeWgsPoints.add(_currentWgsLatLng!);
debugPrint('Robot模式新增作业打点:第${_markedPoints.length}个点,经纬度:${_currentLatLng!.latitude}, ${_currentLatLng!.longitude}');
} else {
if (_markedPoints.isEmpty || _robotModeWgsPoints.isEmpty) {
_showPageToast(message: "请先完成作业区域打点!", type: ToastType.info);
return;
}
// 3. Robot模式-障碍物打点
_currentObstaclePoints.add(_currentLatLng!);
_robotModeObsWgsPoints.add(_currentWgsLatLng!);
@@ -1030,6 +1099,7 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
_mapController.dispose();
_pathPlanningSub?.cancel(); // 🔥 取消订阅
_saveDataToLocal(); // 🔥 页面销毁时保存最终状态
_cancelAllToast(); // 退出页面 → 关闭所有本页面的Toast
super.dispose();
}
@@ -1140,6 +1210,7 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
List<dynamic> outerList = [];
final rawOuter = parsedJson['outer'];
debugPrint("$rawOuter rawOuterrawOuterrawOuter");
if (rawOuter is String) {
try {
outerList = jsonDecode(rawOuter) as List;
@@ -1275,8 +1346,6 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
tracePoint = _traceManager.getTracePoint();
gctracePoint = batchWgs84ToGcj02(tracePoint!);
debugPrint("$gctracePoint gctracePoint");
//_currentLatLng = gcjPoint; // ✅ 状态变量在setState内更新
if (_isValidLatLng(gcjPoint.latitude, gcjPoint.longitude)) {
_currentLatLng = gcjPoint;
@@ -1504,6 +1573,28 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
),
],
),
if (gctracePoint!.isNotEmpty)
PolylineLayer(
polylines: [Polyline(points: gctracePoint!, color: Color.fromARGB(255, 6, 187, 232), strokeWidth: 3, isDotted: false)],
),
/// 当前定位 Marker
if (_currentLatLng != null && __isValidLatLng(_currentLatLng))
MarkerLayer(
markers: [
Marker(
point: _currentLatLng!,
width: 40,
height: 40,
child: CustomPaint(
size: const Size(40, 40),
painter: HeadingMarkerPainter(headingAngle: _headingAngle),
),
),
],
),
if (!_isWorkAreaCompleted)
/// 历史打点的绿色标记
MarkerLayer(
@@ -1524,7 +1615,7 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
width: 16,
height: 16,
decoration: const BoxDecoration(
color: Color(0xFF00C853), // 绿色主题色
color: Color.fromARGB(255, 10, 218, 55), // 绿色主题色
shape: BoxShape.circle,
boxShadow: [BoxShadow(color: Colors.black12, blurRadius: 2)],
),
@@ -1540,26 +1631,6 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
);
}).toList(),
),
if (gctracePoint!.isNotEmpty)
PolylineLayer(
polylines: [Polyline(points: gctracePoint!, color: Color(0xFF00C853), strokeWidth: 3, isDotted: false)],
),
/// 当前定位 Marker
if (_currentLatLng != null && __isValidLatLng(_currentLatLng))
MarkerLayer(
markers: [
Marker(
point: _currentLatLng!,
width: 40,
height: 40,
child: CustomPaint(
size: const Size(40, 40),
painter: HeadingMarkerPainter(headingAngle: _headingAngle),
),
),
],
),
],
);
}
@@ -1569,7 +1640,8 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
_traceManager.reset();
if (startWorkList.isEmpty) {
ToastUtils.showInfo(context, '作业列表为空,请重新选择路径');
_showPageToast(message: "作业列表为空,请重新选择路径", type: ToastType.info);
//ToastUtils.showInfo(context, '作业列表为空,请重新选择路径');
return;
}
_traceManager.setMode(TPMode.NAVIGATION);
@@ -1601,7 +1673,8 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
///context.read<DevicesCubit>().updateAppState(AppState.routePlanning);
// 可选:显示作业提示
ToastUtils.showSuccess(context, '作业已开始');
_showPageToast(message: "作业已开始", type: ToastType.success);
//ToastUtils.showSuccess(context, '作业已开始');
_saveDataToLocal();
//ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('作业已开始'), backgroundColor: Colors.green));
}
@@ -1618,21 +1691,23 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
}
void _stopWork() async {
setState(() {
isStartWork = false; // 🔥 关键:停止作业标志
_workStatus = WorkStatus.idle;
_showHeadingWarn = false;
_showControlModeWarn = false;
isStopWork = true; //已经点击过停止
isreceiveFirstCompletePoint = false;
});
if (mounted) {
setState(() {
isStartWork = false; // 🔥 关键:停止作业标志
_workStatus = WorkStatus.idle;
_showHeadingWarn = false;
_showControlModeWarn = false;
isStopWork = true; //已经点击过停止
isreceiveFirstCompletePoint = false;
});
}
//context.read<DevicesCubit>().updateAppState(AppState.none);
await context.read<DevicesCubit>().stopRoutePlanning();
_traceManager.reset();
_saveDataToLocal(); // 🔥 保存暂停状态
ToastUtils.showError(context, '作业已停止');
_showPageToast(message: "作业已停止", type: ToastType.error);
_traceManager.setMode(TPMode.LOCATION);
}
/// 继续作业
@@ -1975,68 +2050,57 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
if (_currentWorkMode == WorkMode.custom) {
setState(() {
_saveBoxOpen = true; // 显示保存按钮
_isWorkAreaCompleted = true; // 标记为完成,允许保存
gcjPathPoints = List.from(_markedPoints); // outer直接使用打点坐标
_saveBoxOpen = true;
_isWorkAreaCompleted = true;
gcjPathPoints = List.from(_markedPoints);
gcjOuterPoints = List.from(_markedPoints);
typedPathList = []; // 路径列表置空
typedPathList = [];
});
return;
}
// 1. 校验必要条件
if (_currentAreaMode == AreaMode.work && _markedPoints.isEmpty) {
if (showTips) {
ToastUtils.showError(context, '请先添加作业区域打点!');
_showPageToast(message: '请先添加作业区域打点!', type: ToastType.error);
}
return;
}
if (_currentAreaMode == AreaMode.work && _markedPoints.length < 3) {
if (showTips) {
ToastUtils.showError(context, '作业区域至少需要3个打点才能生成路径!');
_showPageToast(message: '作业区域至少需要3个打点才能生成路径!', type: ToastType.error);
}
return;
}
if (_currentWorkMode == null) {
if (showTips) {
ToastUtils.showError(context, '请先选择作业模式!');
_showPageToast(message: '请先选择作业模式!', type: ToastType.error);
}
return;
}
try {
// 2. 转换页面数据为 generatePath 所需的参数格式
// 2.1 参考点(区分Robot/Point模式)
LatLng firstPointWgs; // 最终传给接口的WGS84格式参考点
LatLng firstPointWgs;
if (_currentRobotMode == RobotMode.robot && _robotModeWgsPoints.isNotEmpty) {
// Robot模式:直接使用原始WGS84坐标(无需转换)
firstPointWgs = _robotModeWgsPoints.first;
} else if (_currentRobotMode == RobotMode.point && _markedPoints.isNotEmpty) {
// Point模式:先取十字准星的GCJ02坐标 → 转换为WGS84
LatLng firstPointGcj = _markedPoints.first;
firstPointWgs = gcj02ToWgs84(firstPointGcj.latitude, firstPointGcj.longitude);
} else {
if (showTips) {
ToastUtils.showError(context, '参考点坐标为空,请先添加作业区域打点!');
_showPageToast(message: '参考点坐标为空,请先添加作业区域打点!', type: ToastType.error);
}
return; // 终止方法执行,避免后续错误
return;
}
// 参考点使用转换后的WGS84坐标
final referencePoint = work_area_model.ReferencePoint(lat: firstPointWgs.latitude, lon: firstPointWgs.longitude);
// 2.2 航向角
final heading = _angle == -1 ? -1 : _angle.toInt();
// 2.3 外边界(作业区域)- 核心区分Robot/Point模式
List<work_area_model.Position> outerPositions = [];
if (_currentRobotMode == RobotMode.robot && _robotModeWgsPoints.isNotEmpty) {
// Robot模式:直接使用原始WGS84坐标,跳过GCJ02→WGS84转换
outerPositions = _robotModeWgsPoints.map((latLng) {
return work_area_model.Position(lat: latLng.latitude, lon: latLng.longitude);
}).toList();
} else if (_currentRobotMode == RobotMode.point && _markedPoints.isNotEmpty) {
// Point模式:十字准星的GCJ02坐标 → 转换为WGS84
outerPositions = _markedPoints.map((latLng) {
final wgs84Point = gcj02ToWgs84(latLng.latitude, latLng.longitude);
return work_area_model.Position(lat: wgs84Point.latitude, lon: wgs84Point.longitude);
@@ -2050,17 +2114,14 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
}).toList();
final outerBoundary = work_area_model.OuterBoundary(position: outerPositions, sideWidth: _workDistance);
work_area_model.HoleBoundary? holeBoundary;
if (_obstacleHoles.isNotEmpty) {
// 1. 构造二维 Position 数组
List<List<work_area_model.Position>> positionList = [];
if (_currentRobotMode == RobotMode.robot && _robotModeObsWgsPoints.isNotEmpty) {
for (var holePoints in _obstacleWgsHoles) {
List<work_area_model.Position> innerPositionList = [];
innerPositionList = holePoints.map((latLng) {
return work_area_model.Position(lat: latLng.latitude, lon: latLng.longitude);
}).toList();
@@ -2069,8 +2130,6 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
} else if (_currentRobotMode == RobotMode.point && _obstacleHoles.isNotEmpty) {
for (var holePoints in _obstacleHoles) {
List<work_area_model.Position> innerPositionList = [];
// Point模式:GCJ02→WGS84转换
innerPositionList = holePoints.map((latLng) {
final wgs84Point = gcj02ToWgs84(latLng.latitude, latLng.longitude);
return work_area_model.Position(lat: wgs84Point.latitude, lon: wgs84Point.longitude);
@@ -2079,70 +2138,39 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
}
}
//for (var holePoints in _obstacleHoles) {
// List<work_area_model.Position> innerPositionList = [];
// if (_currentRobotMode == RobotMode.robot && _robotModeObsWgsPoints.isNotEmpty) {
// // Robot模式:使用障碍物原始WGS84坐标
// innerPositionList = holePoints.map((latLng) {
// return work_area_model.Position(lat: latLng.latitude, lon: latLng.longitude);
// }).toList();
// } else {
// // Point模式:GCJ02→WGS84转换
// innerPositionList = holePoints.map((latLng) {
// final wgs84Point = gcj02ToWgs84(latLng.latitude, latLng.longitude);
// return work_area_model.Position(lat: wgs84Point.latitude, lon: wgs84Point.longitude);
// }).toList();
// }
// positionList.add(innerPositionList);
//}
// 2. 创建 HoleBoundary 对象(无外层 Map)
holeBoundary = work_area_model.HoleBoundary(position: positionList, sideWidth: 0.0);
}
// 🔥 第二步:构造符合方法参数要求的 Map<String, HoleBoundary>
// 用一个临时 Map 来满足类型要求,后续在 Cubit 中取值即可
Map<String, work_area_model.HoleBoundary> holes = {};
if (holeBoundary != null) {
holes = {'tempKey': holeBoundary}; // 键名任意,仅为满足类型要求
holes = {'tempKey': holeBoundary};
}
final workType = _currentWorkMode == WorkMode.bow ? 0 : 2;
print('==================================================');
print('✅ generatePath 完整参数结构:');
print('reference: ${formatPrettyJson(referencePoint)}');
print('heading: $heading');
print('outer: ${formatPrettyJson(outerBoundary)}');
print('holes: ${formatPrettyJson(holes)}');
print('workType: $workType');
print('==================================================');
// 3. 调用 Cubit 的 generatePath 方法(传入holes参数)
await context.read<DevicesCubit>().generatePath(
reference: referencePoint,
heading: heading,
outer: outerBoundary,
holes: holes.isNotEmpty ? holes : {}, // 无障碍物时传空对象
holes: holes.isNotEmpty ? holes : {},
workType: workType,
);
// 4. 处理生成结果(原有逻辑不变)
final cubitState = context.read<DevicesCubit>().state;
if (cubitState.errorMessage != null) {
if (showTips) {
ToastUtils.showError(context, '路径生成失败:${cubitState.errorMessage}');
//ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('路径生成失败:${cubitState.errorMessage}'), backgroundColor: Colors.red));
_showPageToast(message: '路径生成失败:${cubitState.errorMessage}', type: ToastType.error);
}
} else if (cubitState.generatedPath != null) {
if (showTips) {
ToastUtils.showSuccess(context, '路径生成成功!');
_showPageToast(message: '路径生成成功!', type: ToastType.success);
}
if (cubitState.generatedPath is List) {
setState(() {
_isWorkAreaCompleted = true;
_saveBoxOpen = true;
_currentObstaclePoints.clear(); // 清空当前绘制的障碍物点
_robotModeObsWgsPoints.clear(); // 清空Robot模式下的障碍物WGS84坐标
_currentObstaclePoints.clear();
_robotModeObsWgsPoints.clear();
final pathList = cubitState.generatedPath as List;
typedPathList = pathList.whereType<work_area_model.DeviceAddPathPointModel>().toList();
gcjPathPoints = typedPathList.map((point) {
@@ -2151,8 +2179,6 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
final gcjPoint = wgs84ToGcj02(wgs84Lat, wgs84Lon);
return LatLng(gcjPoint.latitude, gcjPoint.longitude);
}).toList();
// 外边界保持原有逻辑
//gcjOuterPoints = List.from(_markedPoints);
});
_saveDataToLocal();
moveMapToPointsCenter(gcjPathPoints);
@@ -2161,11 +2187,67 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
} catch (e) {
debugPrint('路径生成异常:$e');
if (showTips) {
ToastUtils.showError(context, '路径生成异常:${e.toString().substring(0, 50)}');
_showPageToast(message: '路径生成异常:${e.toString().substring(0, 50)}', type: ToastType.error);
}
}
}
void _cancelAllToast() {
for (final entry in _toastEntries) {
if (entry.mounted) {
entry.remove();
}
}
_toastEntries.clear();
}
// 只在当前页面显示安全 Toast,退出自动消失
void _showPageToast({required String message, required ToastType type}) {
_cancelAllToast();
// 🔥 延迟执行 Overlay 操作,避开构建阶段
WidgetsBinding.instance.addPostFrameCallback((_) {
if (!mounted) return;
OverlayEntry entry = OverlayEntry(
builder: (context) => SafeArea(
child: Align(
alignment: Alignment.topCenter,
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 20, vertical: 30),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
decoration: BoxDecoration(color: _getToastColor(type), borderRadius: BorderRadius.circular(8)),
child: Text(message, style: const TextStyle(color: Colors.white, fontSize: 14)),
),
),
),
);
_toastEntries.add(entry);
Overlay.of(context)?.insert(entry);
// 2秒后自动关闭
Future.delayed(const Duration(seconds: 2), () {
if (entry.mounted) entry.remove();
_toastEntries.remove(entry);
});
});
}
Color _getToastColor(ToastType type) {
switch (type) {
case ToastType.success:
return Colors.green;
case ToastType.error:
return Colors.red;
case ToastType.warn:
return Colors.orange;
case ToastType.info:
return Colors.blue;
case ToastType.loading:
return Colors.black87;
}
}
@override
Widget build(BuildContext context) {
final screenHeight = MediaQuery.of(context).size.height;
@@ -2207,16 +2289,25 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
DeviceStatusUpdated? updatedState;
//有停止信号
if (isFinishWork) {
_workStatus = WorkStatus.idle;
ToastUtils.showError(context, '作业已停止');
// 👇 延迟 1 秒钟再执行 reset(不会阻塞UI,安全)
Future.delayed(const Duration(seconds: 1), () {
// 🔥 关键:用微任务延迟执行状态更新,避开构建阶段
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
_traceManager.reset();
setState(() {
_workStatus = WorkStatus.idle;
isStopWork = true;
isreceiveFirstCompletePoint = false;
});
_showPageToast(message: "作业已停止", type: ToastType.error);
// 延迟重置轨迹管理器
Future.delayed(const Duration(seconds: 1), () {
if (mounted) {
_traceManager.reset();
_traceManager.setMode(TPMode.LOCATION);
}
});
}
});
isStopWork = true;
isreceiveFirstCompletePoint = false;
}
if (state is DeviceStatusUpdated) {

View File

@@ -23,12 +23,11 @@ class UserInfoCard extends StatefulWidget {
class _UserInfoCardState extends State<UserInfoCard> {
late TextEditingController _nicknameController;
final GetIt _getIt = GetIt.I;
MyCubit? _myCubit; // 可空类型
MyCubit? _myCubit;
@override
void initState() {
super.initState();
// 优先从 BlocProvider 获取,获取失败则从 GetIt 兜底
try {
_myCubit = BlocProvider.of<MyCubit>(context);
} catch (e) {
@@ -48,9 +47,35 @@ class _UserInfoCardState extends State<UserInfoCard> {
super.dispose();
}
// 打开修改昵称的底部抽屉
// ✅ 登录失效提示弹窗 + 2秒后跳登录
void _showSessionExpiredDialog() {
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => WillPopScope(
onWillPop: () async => false,
child: AlertDialog(
title: const Text('提示'),
content: const Text('登录状态失效,请重新登录!'),
actions: [
Center(
child: TextButton(onPressed: () {}, child: const Text('确定')),
),
],
),
),
);
// 2秒后关闭弹窗并跳登录
Future.delayed(const Duration(seconds: 2), () {
if (mounted) {
Navigator.pop(context); // 关闭弹窗
context.go(RoutePaths.login);
}
});
}
void _openEditNicknameBottomSheet() {
// 修复1:先校验 _myCubit 是否为 null,避免空断言
final myCubit = _myCubit;
if (myCubit == null) {
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Cubit 初始化失败,无法修改昵称'), backgroundColor: Colors.red));
@@ -64,13 +89,14 @@ class _UserInfoCardState extends State<UserInfoCard> {
backgroundColor: Colors.transparent,
constraints: BoxConstraints(maxHeight: MediaQuery.of(context).size.height * 0.8),
builder: (context) => BlocProvider.value(
// 使用非空的局部变量,无需空断言
value: myCubit,
child: BlocBuilder<MyCubit, MyState>(
builder: (context, myState) {
final cubit = context.read<MyCubit>();
// ==================== ✅ 核心修复 ====================
if (myState.errorMessage == '登录状态失效,请重新登录') {
context.go(RoutePaths.login);
WidgetsBinding.instance.addPostFrameCallback((_) {
_showSessionExpiredDialog(); // 弹窗提示,不直接跳转
});
}
return Padding(
@@ -96,16 +122,12 @@ class _UserInfoCardState extends State<UserInfoCard> {
TextField(
controller: _nicknameController,
style: const TextStyle(fontSize: 16),
decoration: InputDecoration(
border: const OutlineInputBorder(),
contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
),
decoration: const InputDecoration(border: OutlineInputBorder(), contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 10)),
autofocus: true,
enabled: !myState.isLoading,
),
const SizedBox(height: 20),
// 核心:根据 MyState 显示加载/按钮/错误提示
if (myState.isLoading)
const Center(
child: Column(
@@ -119,14 +141,11 @@ class _UserInfoCardState extends State<UserInfoCard> {
else
Column(
children: [
// 修复2:使用 ?. 和 ?? 避免空断言,安全判断错误信息
if (myState.errorMessage?.isNotEmpty ?? false)
Padding(
padding: const EdgeInsets.only(bottom: 12),
child: Text(myState.errorMessage ?? "修改失败", style: const TextStyle(color: Colors.red, fontSize: 14)),
),
// 确定修改按钮
SizedBox(
width: double.infinity,
child: ElevatedButton(
@@ -152,7 +171,6 @@ class _UserInfoCardState extends State<UserInfoCard> {
);
}
// 提交新昵称的业务逻辑
Future<void> _submitNewNickname(BuildContext context) async {
final newNickname = _nicknameController.text.trim();
if (newNickname.isEmpty) {
@@ -169,25 +187,19 @@ class _UserInfoCardState extends State<UserInfoCard> {
return;
}
// 移除重复的加载弹窗(由 BlocBuilder 显示加载状态)
await myCubit.updateName(newNickname);
// 成功后由 BlocBuilder 监听状态并处理弹窗关闭和提示
if (context.mounted && myCubit.state.errorMessage!.isEmpty ?? true) {
if (context.mounted && (myCubit.state.errorMessage?.isEmpty ?? true)) {
setState(() {
_nicknameController.text = newNickname;
});
Navigator.pop(context);
// 触发 AuthCubit 重新获取用户信息
}
}
// 扫码绑定设备逻辑(保留)
// 扫码绑定设备逻辑(修复版)
Future<void> _handleScanAndBind(BuildContext context) async {
bool isScanned = false;
// 1. 扫码逻辑 (保持不变)
final String? deviceCode = await Navigator.push<String>(
context,
MaterialPageRoute(
@@ -196,10 +208,7 @@ class _UserInfoCardState extends State<UserInfoCard> {
appBar: AppBar(
title: const Text('扫码绑定设备'),
backgroundColor: Colors.black54,
leading: IconButton(
icon: const Icon(Icons.close),
onPressed: () => Navigator.pop(context),
),
leading: IconButton(icon: const Icon(Icons.close), onPressed: () => Navigator.pop(context)),
),
body: MobileScanner(
onDetect: (BarcodeCapture capture) {
@@ -226,7 +235,6 @@ class _UserInfoCardState extends State<UserInfoCard> {
return;
}
// 2. 确认对话框 (保持不变)
final bool? confirmBind = await showDialog<bool>(
context: context,
barrierDismissible: false,
@@ -235,19 +243,10 @@ class _UserInfoCardState extends State<UserInfoCard> {
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('设备信息:'),
const SizedBox(height: 8),
Text('设备编码:$deviceCode'),
const Text('设备状态:未绑定'),
const Text('设备类型:智能设备'),
],
children: [const Text('设备信息:'), const SizedBox(height: 8), Text('设备编码:$deviceCode'), const Text('设备状态:未绑定'), const Text('设备类型:智能设备')],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: const Text('取消'),
),
TextButton(onPressed: () => Navigator.pop(context, false), child: const Text('取消')),
ElevatedButton(
onPressed: () => Navigator.pop(context, true),
child: const Text('确定绑定'),
@@ -261,13 +260,12 @@ class _UserInfoCardState extends State<UserInfoCard> {
return;
}
// 3. 显示加载框:保存加载弹窗的 BuildContext
BuildContext? loadingContext; // 新增:保存加载弹窗的上下文
BuildContext? loadingContext;
showDialog(
context: context,
barrierDismissible: false,
builder: (context) {
loadingContext = context; // 保存这个 context
loadingContext = context;
return const Center(child: CircularProgressIndicator());
},
);
@@ -276,30 +274,22 @@ class _UserInfoCardState extends State<UserInfoCard> {
final devicesCubit = GetIt.I<DevicesCubit>();
await devicesCubit.bindDevice(deviceCode, '');
// ✅ 成功:用 loadingContext 关闭弹窗
if (loadingContext != null && Navigator.canPop(loadingContext!)) {
Navigator.pop(loadingContext!);
}
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('设备绑定成功!'), backgroundColor: Colors.green),
);
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('设备绑定成功!'), backgroundColor: Colors.green));
}
} catch (e) {
// ❌ 失败:用 loadingContext 关闭弹窗
if (loadingContext != null && Navigator.canPop(loadingContext!)) {
Navigator.pop(loadingContext!);
}
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('绑定失败:${e.toString()}'),
backgroundColor: Colors.red,
duration: const Duration(seconds: 4),
),
);
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text('绑定失败:${e.toString()}'), backgroundColor: Colors.red, duration: const Duration(seconds: 4)));
}
debugPrint('设备绑定失败详情:$e');
}
@@ -314,7 +304,7 @@ class _UserInfoCardState extends State<UserInfoCard> {
height: 48,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
image: DecorationImage(image: NetworkImage('https://pic4.zhimg.com/v2-c34c61a90095abb8713de9d1dca7ec7b_r.jpg'), fit: BoxFit.cover),
image: const DecorationImage(image: NetworkImage('https://pic4.zhimg.com/v2-c34c61a90095abb8713de9d1dca7ec7b_r.jpg'), fit: BoxFit.cover),
),
),
const SizedBox(width: 12),

View File

@@ -83,13 +83,15 @@ class _RemoteControlPageState extends State<RemoteControlPage> {
} else {
_videoStreamUrl = '';
}
final int originY = context.watch<RemoteControlCubit>().state.controlEntity.originY;
debugPrint("${originY},originY");
return WebRTCLocalPlayer(
// 这里的 URL 拼接根据你的后端规则
// streamUrl: "webrtc://${TCPConsts.TCP_IP}/live/livestream/${currentDevice.deviceName}?token=${userState.user!.token}",
streamUrl: _videoStreamUrl,
showLeftPip: state.showLeftPip, // 从 Cubit 状态中读取
showRightPip: state.showRightPip, // 从 Cubit 状态中读取
isFrontMain: context.watch<RemoteControlCubit>().state.controlEntity.originY>= 0.5,
isFrontMain: context.watch<RemoteControlCubit>().state.controlEntity.originY >= 0,
);
},
),