From 0a3a062c04a44f9e5facbced767014b6fdfb1fd2 Mon Sep 17 00:00:00 2001 From: mmc <1556375442@qq.com> Date: Tue, 3 Mar 2026 10:28:50 +0800 Subject: [PATCH] =?UTF-8?q?toast=E5=BC=B9=E7=AA=97=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/components/toast.dart | 173 ++++++++++++++++++ .../widgets/map/testmap_pages.dart | 83 ++++++--- 2 files changed, 226 insertions(+), 30 deletions(-) create mode 100644 lib/components/toast.dart diff --git a/lib/components/toast.dart b/lib/components/toast.dart new file mode 100644 index 00000000..c73c74dd --- /dev/null +++ b/lib/components/toast.dart @@ -0,0 +1,173 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; + +// 自定义Toast工具类 - 气泡式提示 +class ToastUtils { + static OverlayEntry? _overlayEntry; + static bool _isShowing = false; + + /// 显示气泡提示 + /// [context] 上下文 + /// [message] 提示文本 + /// [backgroundColor] 背景色 + /// [duration] 显示时长 + /// [positionTop] 距离顶部的高度 + static void showToast( + BuildContext context, + String message, { + Color backgroundColor = const Color(0xCC333333), + Duration duration = const Duration(seconds: 1), + double positionTop = 80.0, + }) { + // 先关闭正在显示的提示 + if (_isShowing) { + _overlayEntry?.remove(); + _isShowing = false; + } + + // 创建OverlayEntry + _overlayEntry = OverlayEntry( + builder: (BuildContext context) => Positioned( + top: positionTop, + left: MediaQuery.of(context).size.width * 0.1, + right: MediaQuery.of(context).size.width * 0.1, + child: Material( + color: Colors.transparent, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), + decoration: BoxDecoration( + color: backgroundColor, + borderRadius: BorderRadius.circular(18), + boxShadow: [BoxShadow(color: Colors.black12, blurRadius: 10, offset: const Offset(0, 2))], + ), + child: Text( + message, + textAlign: TextAlign.center, + style: const TextStyle(color: Colors.white, fontSize: 14, height: 1.2), + ), + ), + ), + ), + ); + + // 显示提示 + SchedulerBinding.instance.addPostFrameCallback((_) { + Overlay.of(context).insert(_overlayEntry!); + _isShowing = true; + + // 定时关闭 + Future.delayed(duration, () { + if (_isShowing) { + _overlayEntry?.remove(); + _isShowing = false; + } + }); + }); + } + + /// 显示成功提示(绿色背景 + 85%不透明度) + static void showSuccess(BuildContext context, String message) { + showToast( + context, + message, + // 绿色:0xFF00C853 → 调整为 0xD900C853(D9=85%不透明度) + backgroundColor: const Color(0xD900C853), + duration: const Duration(seconds: 1), + ); + } + + /// 显示错误提示(红色背景 + 85%不透明度) + static void showError(BuildContext context, String message) { + showToast( + context, + message, + // 红色:0xFFFF5252 → 调整为 0xD9FF5252(D9=85%不透明度) + backgroundColor: const Color(0xD9FF5252), + duration: const Duration(seconds: 1), + ); + } + + /// 显示普通提示(灰色背景 + 80%不透明度) + static void showInfo(BuildContext context, String message) { + showToast( + context, + message, + // 灰色:0xCC333333 → 调整为 0xCC333333(保持80%,也可改为 0xB3333333=70%) + backgroundColor: const Color(0xCC333333), + duration: const Duration(seconds: 1), + ); + } + + /// 显示警告提示(黄色背景 + 85%不透明度) + static void showWarn(BuildContext context, String message) { + showToast( + context, + message, + // 黄色:0xFFFFC107 → 调整为 0xD9FFC107(D9=85%不透明度) + backgroundColor: const Color(0xD9FFC107), + duration: const Duration(seconds: 1), + ); + } + + /// 显示加载提示(蓝色背景 + 85%不透明度) + static void showLoading(BuildContext context, String message) { + showToast( + context, + message, + // 蓝色:0xFF0688D8 → 调整为 0xD90688D8(D9=85%不透明度) + backgroundColor: const Color(0xD90688D8), + duration: const Duration(seconds: 1), + ); + } + + // 扩展:支持自定义文字颜色 + static void showToastWithTextColor( + BuildContext context, + String message, { + Color backgroundColor = const Color(0xCC333333), + Color textColor = Colors.white, + Duration duration = const Duration(seconds: 2), + double positionTop = 80.0, + }) { + if (_isShowing) { + _overlayEntry?.remove(); + _isShowing = false; + } + + _overlayEntry = OverlayEntry( + builder: (BuildContext context) => Positioned( + top: positionTop, + left: MediaQuery.of(context).size.width * 0.1, + right: MediaQuery.of(context).size.width * 0.1, + child: Material( + color: Colors.transparent, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), + decoration: BoxDecoration( + color: backgroundColor, + borderRadius: BorderRadius.circular(24), + boxShadow: [BoxShadow(color: Colors.black12, blurRadius: 10, offset: const Offset(0, 2))], + ), + child: Text( + message, + textAlign: TextAlign.center, + style: TextStyle(color: textColor, fontSize: 14, height: 1.2), + ), + ), + ), + ), + ); + + SchedulerBinding.instance.addPostFrameCallback((_) { + Overlay.of(context).insert(_overlayEntry!); + _isShowing = true; + + Future.delayed(duration, () { + if (_isShowing) { + _overlayEntry?.remove(); + _isShowing = false; + } + }); + }); + } +} diff --git a/lib/features/home/presentation/widgets/map/testmap_pages.dart b/lib/features/home/presentation/widgets/map/testmap_pages.dart index 93c07427..60140b47 100644 --- a/lib/features/home/presentation/widgets/map/testmap_pages.dart +++ b/lib/features/home/presentation/widgets/map/testmap_pages.dart @@ -12,6 +12,7 @@ import 'package:geolocator/geolocator.dart'; import 'package:http/http.dart' as http; import 'package:latlong2/latlong.dart'; import 'package:maibu_satabot_v2/components/confrim_dialog.dart'; +import 'package:maibu_satabot_v2/components/toast.dart'; import 'package:maibu_satabot_v2/core/app/app_user_cubit.dart'; import 'package:maibu_satabot_v2/core/di/injection.dart'; import 'package:maibu_satabot_v2/core/network/net_message_dispatcher.dart'; @@ -260,7 +261,8 @@ class _MapPageEnterpriseState extends State { } else { // 截图失败时,允许空图片保存(可根据业务调整为强制失败) _savePlotData(plotName, null); - ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('地块保存成功,但地图截图生成失败!'), backgroundColor: Colors.amber)); + ToastUtils.showWarn(context, '地块保存成功,但地图截图生成失败!'); + //ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('地块保存成功,但地图截图生成失败!'), backgroundColor: Colors.amber)); } } }, @@ -323,7 +325,7 @@ class _MapPageEnterpriseState extends State { // 1. 获取用户ID final userId = context.read().state.user?.userId ?? ""; if (userId.isEmpty) { - ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('用户ID为空,无法保存'), backgroundColor: Colors.red)); + ToastUtils.showError(context, '用户ID为空,无法保存!'); return; } @@ -389,15 +391,16 @@ class _MapPageEnterpriseState extends State { // 7. 发送请求 try { - ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('正在保存地块...'), backgroundColor: Colors.blue)); + ToastUtils.showLoading(context, '正在保存地块「$plotName」...'); + //ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('正在保存地块...'), backgroundColor: Colors.blue)); final http.StreamedResponse response = await request.send(); final String responseBody = await response.stream.bytesToString(); - debugPrint('保存地块响应状态: ${response.statusCode}'); debugPrint('保存地块响应体: $responseBody'); if (response.statusCode == 200) { - ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('地块「$plotName」保存成功!'), backgroundColor: Colors.green)); + ToastUtils.showSuccess(context, '地块「$plotName」保存成功!'); + //ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('地块「$plotName」保存成功!'), backgroundColor: Colors.green)); // 保存成功 setState(() { _markedPoints.clear(); // 清空打点 @@ -412,7 +415,8 @@ class _MapPageEnterpriseState extends State { } } catch (e) { debugPrint('保存失败: $e'); - ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('保存失败: ${e.toString()}'), backgroundColor: Colors.red)); + ToastUtils.showError(context, '保存失败: ${e.toString()}'); + //ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('保存失败: ${e.toString()}'), backgroundColor: Colors.red)); } } @@ -448,9 +452,11 @@ class _MapPageEnterpriseState extends State { if (_isWorkAreaCompleted && _markedPoints.length >= 3) { _generatePath(showTips: false); } - ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('已撤回作业区域最后一个点,剩余${_markedPoints.length}个点'))); + ToastUtils.showInfo(context, '已撤回作业区域最后一个点,剩余${_markedPoints.length}个点'); + //ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('已撤回作业区域最后一个点,剩余${_markedPoints.length}个点'))); } else { - ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('暂无作业区域点可撤回'))); + ToastUtils.showInfo(context, '暂无作业区域点可撤回'); + //ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('暂无作业区域点可撤回'))); } return; } @@ -463,8 +469,9 @@ class _MapPageEnterpriseState extends State { if (_currentObstaclePoints.isEmpty) { _isObstacleEditing = false; } + ToastUtils.showInfo(context, '已撤回当前空洞最后一个点,剩余${_currentObstaclePoints.length}个点'); - ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('已撤回当前空洞最后1个点,剩余${_currentObstaclePoints.length}个点'))); + //ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('已撤回当前空洞最后1个点,剩余${_currentObstaclePoints.length}个点'))); return; // 撤完点就结束,不执行后面的逻辑 } @@ -475,12 +482,15 @@ class _MapPageEnterpriseState extends State { if (_isWorkAreaCompleted) { _generatePath(showTips: false); } - ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('已撤回上一个完整空洞,剩余${_obstacleHoles.length}个空洞'))); + ToastUtils.showInfo(context, '已撤回上一个完整空洞,剩余${_obstacleHoles.length}个空洞'); + + //ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('已撤回上一个完整空洞,剩余${_obstacleHoles.length}个空洞'))); return; } // 第三步:无任何可撤内容 - ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('暂无空洞可撤回'))); + ToastUtils.showInfo(context, '暂无空洞可撤回'); + //ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('暂无空洞可撤回'))); } }); } @@ -495,7 +505,9 @@ class _MapPageEnterpriseState extends State { // 1. 作业区域模式:清空作业点 + 重置路径 if (_currentAreaMode == AreaMode.work) { if (_markedPoints.isEmpty) { - ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('暂无作业区域点可删除'))); + ToastUtils.showInfo(context, '暂无作业区域点可删除'); + + //ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('暂无作业区域点可删除'))); return; } _markedPoints.clear(); @@ -504,12 +516,14 @@ class _MapPageEnterpriseState extends State { _isWorkAreaCompleted = false; _saveBoxOpen = false; typedPathList.clear(); - ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('已清空所有作业区域点和路径'))); + ToastUtils.showSuccess(context, '已清空所有作业区域点和路径'); + + //ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('已清空所有作业区域点和路径'))); } // 2. 空洞模式:清空所有空洞 + 恢复UI + 重新生成路径 else if (_currentAreaMode == AreaMode.obstacle) { if (_obstacleHoles.isEmpty && _currentObstaclePoints.isEmpty) { - ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('暂无空洞可删除'))); + ToastUtils.showInfo(context, '暂无空洞可删除'); return; } _obstacleHoles.clear(); @@ -519,7 +533,7 @@ class _MapPageEnterpriseState extends State { if (_isWorkAreaCompleted) { _generatePath(showTips: false); } - ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('已清空所有空洞'))); + ToastUtils.showSuccess(context, '已清空所有空洞'); } }); } @@ -639,15 +653,17 @@ class _MapPageEnterpriseState extends State { // 刷新成功提示 if (mounted) { - ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('页面已刷新完成'), backgroundColor: Colors.green, duration: Duration(seconds: 2))); + ToastUtils.showSuccess(context, '页面已刷新完成'); + //ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('页面已刷新完成'), backgroundColor: Colors.green, duration: Duration(seconds: 2))); } } catch (e) { // 错误处理 debugPrint('刷新失败:$e'); if (mounted) { - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('刷新失败:${e.toString().substring(0, 50)}'), backgroundColor: Colors.red, duration: Duration(seconds: 3))); + 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))); } } finally { // 标记刷新结束 @@ -682,7 +698,7 @@ class _MapPageEnterpriseState extends State { // ========== 障碍物模式:完成当前障碍物绘制 ========== void _completeObstacle() { if (_currentObstaclePoints.length < 3) { - ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('障碍物区域至少需要3个打点!'), backgroundColor: Colors.red)); + ToastUtils.showError(context, '障碍物区域至少需要3个打点!'); return; } @@ -693,7 +709,8 @@ class _MapPageEnterpriseState extends State { }); debugPrint('完成障碍物绘制,当前障碍物组数:${_obstacleHoles.length}'); - ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('已添加第${_obstacleHoles.length}个障碍物区域'), backgroundColor: Colors.green)); + + ToastUtils.showSuccess(context, '已添加第${_obstacleHoles.length}个障碍物区域'); } //void _undoObstaclePoint() { @@ -723,7 +740,7 @@ class _MapPageEnterpriseState extends State { // ========== 核心方法:打点逻辑 ========== void _addMarkedPoint() { if (_currentAreaMode == AreaMode.work && _isWorkAreaCompleted) { - ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('作业区域已完成,无法继续添加打点!'), backgroundColor: Colors.red)); + ToastUtils.showError(context, '作业区域已完成,无法继续添加打点!'); return; } setState(() { @@ -1072,7 +1089,8 @@ class _MapPageEnterpriseState extends State { debugPrint('开始作业:${_selectedPlot!.plotName},路径数据:$gcjPathPoints'); // 可选:显示作业提示 - ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('作业已开始'), backgroundColor: Colors.green)); + ToastUtils.showSuccess(context, '作业已开始'); + //ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('作业已开始'), backgroundColor: Colors.green)); } /// 暂停作业 @@ -1094,7 +1112,8 @@ class _MapPageEnterpriseState extends State { // 模拟停止逻辑(替换为你的实际停止代码) debugPrint('停止作业:${_selectedPlot!.plotName}'); // 可选:显示停止提示 - ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('作业已停止'), backgroundColor: Colors.redAccent)); + ToastUtils.showError(context, '作业已停止'); + //ScaffoldMessenger.of(co ntext).showSnackBar(const SnackBar(content: Text('作业已停止'), backgroundColor: Colors.redAccent)); } /// 继续作业 @@ -1395,19 +1414,21 @@ class _MapPageEnterpriseState extends State { // 1. 校验必要条件 if (_currentAreaMode == AreaMode.work && _markedPoints.isEmpty) { if (showTips) { - ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('请先添加作业区域打点!'), backgroundColor: Colors.red)); + ToastUtils.showError(context, '请先添加作业区域打点!'); + //ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('请先添加作业区域打点!'), backgroundColor: Colors.red)); } return; } if (_currentAreaMode == AreaMode.work && _markedPoints.length < 3) { if (showTips) { - ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('作业区域至少需要3个打点才能生成路径!'), backgroundColor: Colors.red)); + ToastUtils.showError(context, '作业区域至少需要3个打点才能生成路径!'); } return; } if (_currentWorkMode == null) { if (showTips) { - ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('请先选择作业模式!'), backgroundColor: Colors.red)); + ToastUtils.showError(context, '请先选择作业模式!'); + //ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('请先选择作业模式!'), backgroundColor: Colors.red)); } return; } @@ -1473,11 +1494,12 @@ class _MapPageEnterpriseState extends State { if (cubitState.errorMessage != null) { if (showTips) { - ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('路径生成失败:${cubitState.errorMessage}'), backgroundColor: Colors.red)); + ToastUtils.showError(context, '路径生成失败:${cubitState.errorMessage}'); + //ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('路径生成失败:${cubitState.errorMessage}'), backgroundColor: Colors.red)); } } else if (cubitState.generatedPath != null) { if (showTips) { - ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('路径生成成功!'), backgroundColor: Colors.green)); + ToastUtils.showSuccess(context, '路径生成成功!'); } debugPrint('生成的路径数据:${cubitState.generatedPath}'); @@ -1506,7 +1528,8 @@ class _MapPageEnterpriseState extends State { } catch (e) { debugPrint('路径生成异常:$e'); if (showTips) { - ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('路径生成异常:${e.toString().substring(0, 50)}'), backgroundColor: Colors.red)); + ToastUtils.showError(context, '路径生成异常:${e.toString().substring(0, 50)}'); + //ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('路径生成异常:${e.toString().substring(0, 50)}'), backgroundColor: Colors.red)); } } }