From 6a4fc514424de21b0a7491ae6f940a7ffeee14bc Mon Sep 17 00:00:00 2001 From: lxm <1274844347@qq.com> Date: Mon, 7 Jul 2025 20:13:38 +0800 Subject: [PATCH] =?UTF-8?q?feat(navigation):=20=E9=87=8D=E6=9E=84=E5=AF=BC?= =?UTF-8?q?=E8=88=AA=E5=8D=A1=E7=89=87=E7=BB=84=E4=BB=B6=E5=B9=B6=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E4=BA=A4=E4=BA=92=E4=BD=93=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将高度参数改为可选,默认350.h - 重新设计卡片布局,增加图片区域占比 - 优化导航方式选择器的视觉样式 - 添加导航状态覆盖层和错误处理 - 改进目的地信息展示方式 - 增强开始导航按钮的视觉效果 - 添加导航类型图标映射功能 - 修正注释和变量命名 --- lib/data/services/navigation_manager.dart | 4 +- lib/modules/agent/views/navigation_card.dart | 426 +++++++++++++++---- 2 files changed, 352 insertions(+), 78 deletions(-) diff --git a/lib/data/services/navigation_manager.dart b/lib/data/services/navigation_manager.dart index 5604838c7..e7d7187b2 100644 --- a/lib/data/services/navigation_manager.dart +++ b/lib/data/services/navigation_manager.dart @@ -2,11 +2,11 @@ import 'package:amap_service/amap_service.dart'; import 'package:deep_voice/core/utils/logger.dart'; import 'package:get/get.dart'; -//音乐播放管理 +//导航播放管理 class NavigationManager extends GetxService { static NavigationManager get to => Get.find(); - // 音乐服务实例 + // 导航服务实例 final AmapService _amapervice = AmapService(); /// 初始化 diff --git a/lib/modules/agent/views/navigation_card.dart b/lib/modules/agent/views/navigation_card.dart index 2f79869c8..e5769785c 100644 --- a/lib/modules/agent/views/navigation_card.dart +++ b/lib/modules/agent/views/navigation_card.dart @@ -31,7 +31,15 @@ String formatDuration(String secondsStr) { return parts.join(''); } -//导航卡片 +/// 导航卡片组件 +/// +/// 各元素含义分析: +/// - [start]: 起点坐标 (经纬度格式) +/// - [end]: 终点坐标 (经纬度格式) +/// - [destination]: 目的地名称显示 +/// - [modes]: 导航方式列表,包含驾车、步行、公交等选项 +/// - [navigation]: 当前导航状态信息(可选) +/// - [height]: 卡片高度 Widget navigationCard( BuildContext context, { required String start, @@ -39,72 +47,168 @@ Widget navigationCard( required String destination, required List> modes, Map? navigation, - double height = 300, + double? height, // 改为可选参数,允许自适应高度 }) { int currentIndex = 0; - final controller = Get.find(); // 获取控制器 + final controller = Get.find(); final isDarkMode = Theme.of(context).brightness == Brightness.dark; return Container( - height: height.h, + height: height?.h ?? 350.h, // 如果没有指定高度,使用350作为默认高度 decoration: BoxDecoration( borderRadius: BorderRadius.circular(16.r), color: isDarkMode ? const Color(0xFF1E1E1E) : Colors.white, boxShadow: [ BoxShadow( - blurRadius: 8, - color: Colors.black.withValues(alpha: isDarkMode ? 0.3 : 0.12), - offset: const Offset(0, 4), + blurRadius: 12, + color: Colors.black.withValues(alpha: isDarkMode ? 0.4 : 0.08), + offset: const Offset(0, 2), ), ], ), child: StatefulBuilder( builder: (context, setInnerState) { return Column( - crossAxisAlignment: CrossAxisAlignment.start, children: [ - // 路线图片 - ClipRRect( - borderRadius: - BorderRadius.vertical(top: Radius.circular(16.r)), - child: AnimatedSwitcher( - duration: const Duration(milliseconds: 300), - child: Image.network( - modes[currentIndex]["image"] as String, - key: ValueKey(modes[currentIndex]["ntype"] as String), - height: 160.h, - width: double.infinity, - fit: BoxFit.cover, + // 顶部:路线预览图片区域 + Expanded( + flex: 4, // 增加图片区域的flex值,让图片占更多空间 + child: ClipRRect( + borderRadius: BorderRadius.vertical(top: Radius.circular(16.r)), + child: Stack( + children: [ + // 路线图片 + AnimatedSwitcher( + duration: const Duration(milliseconds: 300), + child: Image.network( + modes[currentIndex]["image"] as String, + key: ValueKey( + modes[currentIndex]["ntype"] as String), + height: double.infinity, + width: double.infinity, + fit: BoxFit.cover, + errorBuilder: (context, error, stackTrace) { + return Container( + color: isDarkMode + ? Colors.grey[800] + : Colors.grey[200], + child: Icon( + Icons.map_outlined, + size: 48.sp, + color: isDarkMode + ? Colors.grey[600] + : Colors.grey[400], + ), + ); + }, + ), + ), + // 导航状态覆盖层 + if (navigation != null) + Positioned( + top: 12.h, + left: 12.w, + child: Container( + padding: EdgeInsets.symmetric( + horizontal: 8.w, vertical: 4.h), + decoration: BoxDecoration( + color: + Theme.of(context).primaryColor.withOpacity(0.9), + borderRadius: BorderRadius.circular(12.r), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.navigation, + color: Colors.white, + size: 14.sp, + ), + SizedBox(width: 4.w), + Text( + '导航中', + style: TextStyle( + color: Colors.white, + fontSize: 12.sp, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + ), + ], ), ), ), - // 导航方式切换按钮 - Padding( - padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 4.w), + // 中部:导航方式选择器 + Container( + padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 16.w), child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: List.generate(modes.length, (index) { + final mode = modes[index]; final isSelected = index == currentIndex; - return GestureDetector( - onTap: modes[index]['enable'] as bool - ? () { - setInnerState(() => currentIndex = index); - } - : null, - child: SizedBox( - width: 50.w, - child: Text( - (modes[index]["ntype"] as String).tr, - textAlign: TextAlign.center, - style: TextStyle( - color: modes[index]['enable'] - ? isSelected - ? Theme.of(context).primaryColor - : isDarkMode ? Colors.white.withValues(alpha: 0.9) : Colors.black87 - : Colors.grey, - fontSize: 12.sp, - fontWeight: FontWeight.w500, + final isEnabled = mode['enable'] as bool; + + return Expanded( + child: GestureDetector( + onTap: isEnabled + ? () { + setInnerState(() => currentIndex = index); + } + : null, + child: Container( + margin: EdgeInsets.symmetric(horizontal: 4.w), + padding: EdgeInsets.symmetric( + vertical: 8.h, horizontal: 12.w), + decoration: BoxDecoration( + color: isSelected + ? Theme.of(context).primaryColor.withOpacity(0.1) + : Colors.transparent, + borderRadius: BorderRadius.circular(8.r), + border: Border.all( + color: isSelected + ? Theme.of(context).primaryColor + : (isDarkMode + ? Colors.grey[700]! + : Colors.grey[300]!), + width: isSelected ? 2 : 1, + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + _getNavigationIcon(mode["ntype"] as String), + color: isEnabled + ? (isSelected + ? Theme.of(context).primaryColor + : (isDarkMode + ? Colors.white70 + : Colors.black87)) + : Colors.grey, + size: 20.sp, + ), + SizedBox(height: 4.h), + Text( + (mode["ntype"] as String).tr, + textAlign: TextAlign.center, + style: TextStyle( + color: isEnabled + ? (isSelected + ? Theme.of(context).primaryColor + : (isDarkMode + ? Colors.white70 + : Colors.black87)) + : Colors.grey, + fontSize: 11.sp, + fontWeight: isSelected + ? FontWeight.w600 + : FontWeight.w400, + ), + ), + ], ), ), ), @@ -113,43 +217,191 @@ Widget navigationCard( ), ), - // 距离和耗时 - Padding( - padding: EdgeInsets.symmetric(horizontal: 8.w), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + // 底部:目的地信息和操作按钮 + Container( + padding: EdgeInsets.fromLTRB(20.w, 8.h, 20.w, 16.h), + child: Stack( children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(destination, - style: TextStyle( - fontSize: 12.sp, - color: isDarkMode ? Colors.white.withValues(alpha: 0.9) : Colors.black87)), - Text( - '${formatDistance(modes[currentIndex]["distance"])}-约${formatDuration(modes[currentIndex]["duration"])}', - style: TextStyle( - fontSize: 15.sp, - color: Theme.of(context).primaryColor)), - if (navigation != null) - Text( - "导航中... ${navigation["distanceText"]}进入${navigation["nextRoad"]}", - style: TextStyle( + // 目的地和距离时间信息 + Padding( + padding: EdgeInsets.only(right: 70.w), // 为右下角按钮留出空间 + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // 路径显示:当前位置 → 目的地 + Row( + children: [ + // 起点图标 + Icon( + Icons.my_location, + size: 16.sp, + color: Theme.of(context).primaryColor, + ), + SizedBox(width: 6.w), + // 当前位置文本 + Text( + '当前位置', + style: TextStyle( + fontSize: 14.sp, + fontWeight: FontWeight.w500, + color: isDarkMode + ? Colors.white70 + : Colors.black54, + ), + ), + SizedBox(width: 8.w), + // 箭头图标 + Icon( + Icons.arrow_forward, + size: 16.sp, + color: isDarkMode + ? Colors.white54 + : Colors.black45, + ), + SizedBox(width: 8.w), + // 目的地图标 + Icon( + Icons.location_on, + size: 16.sp, + color: Colors.red, + ), + SizedBox(width: 6.w), + // 目的地名称 + Expanded( + child: Text( + destination, + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.w600, + color: isDarkMode + ? Colors.white + : Colors.black87, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + SizedBox(height: 4.h), + // 距离和时间信息 + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // 距离信息 + Row( + children: [ + Icon( + Icons.straighten, + size: 14.sp, + color: Theme.of(context).primaryColor, + ), + SizedBox(width: 4.w), + Text( + formatDistance( + modes[currentIndex]["distance"]), + style: TextStyle( + fontSize: 14.sp, + color: Theme.of(context).primaryColor, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + SizedBox(height: 4.h), + // 时间信息 + Row( + children: [ + Icon( + Icons.access_time, + size: 14.sp, + color: Theme.of(context).primaryColor, + ), + SizedBox(width: 4.w), + Text( + '约${formatDuration(modes[currentIndex]["duration"])}', + style: TextStyle( + fontSize: 14.sp, + color: Theme.of(context).primaryColor, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ], + ), + // 导航状态信息 + if (navigation != null) ...[ + SizedBox(height: 6.h), + Container( + padding: EdgeInsets.symmetric( + horizontal: 8.w, vertical: 4.h), + decoration: BoxDecoration( + color: Theme.of(context) + .primaryColor + .withOpacity(0.1), + borderRadius: BorderRadius.circular(6.r), + ), + child: Text( + "${navigation["distanceText"]}进入${navigation["nextRoad"]}", + style: TextStyle( fontSize: 12.sp, - color: isDarkMode ? Colors.grey[400] : Colors.grey[600])) - ], + color: Theme.of(context).primaryColor, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ], + ), ), - CircleAvatar( - backgroundColor: Theme.of(context).primaryColor, - radius: 18.r, - child: IconButton( - icon: Icon(Icons.turn_left, - color: Colors.white, size: 20.sp), - onPressed: () { - controller.openNavigation(start, end, ""); - }, + // 开始导航按钮 - 绝对定位到右下角 + Positioned( + right: 0, + bottom: 0, + child: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Theme.of(context).primaryColor, + Theme.of(context).primaryColor.withOpacity(0.8), + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: BorderRadius.circular(12.r), + boxShadow: [ + BoxShadow( + color: Theme.of(context) + .primaryColor + .withOpacity(0.3), + blurRadius: 8, + offset: const Offset(0, 2), + ), + ], + ), + child: Material( + color: Colors.transparent, + child: InkWell( + borderRadius: BorderRadius.circular(12.r), + onTap: () { + controller.openNavigation(start, end, ""); + }, + child: Container( + padding: EdgeInsets.symmetric( + horizontal: 16.w, + vertical: 12.h, + ), + child: Icon( + Icons.navigation, + color: Colors.white, + size: 20.sp, + ), + ), + ), + ), ), - ) + ), ], ), ), @@ -159,3 +411,25 @@ Widget navigationCard( ), ); } + +/// 根据导航类型获取对应图标 +IconData _getNavigationIcon(String ntype) { + switch (ntype.toLowerCase()) { + case 'driving': + case '驾车': + return Icons.directions_car; + case 'walking': + case '步行': + return Icons.directions_walk; + case 'transit': + case 'tsransit': // 修正拼写错误 + case '公交': + return Icons.directions_transit; + case 'bicycling': // 修正为正确的英文值 + case 'cycling': + case '骑行': + return Icons.directions_bike; + default: + return Icons.navigation; + } +}