Browse Source

feat(navigation): 重构导航卡片组件并优化交互体验

- 将高度参数改为可选,默认350.h
- 重新设计卡片布局,增加图片区域占比
- 优化导航方式选择器的视觉样式
- 添加导航状态覆盖层和错误处理
- 改进目的地信息展示方式
- 增强开始导航按钮的视觉效果
- 添加导航类型图标映射功能
- 修正注释和变量命名
weicu
lxm 1 year ago
parent
commit
6a4fc51442
  1. 4
      lib/data/services/navigation_manager.dart
  2. 426
      lib/modules/agent/views/navigation_card.dart

4
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:deep_voice/core/utils/logger.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
//音乐播放管理 //导航播放管理
class NavigationManager extends GetxService { class NavigationManager extends GetxService {
static NavigationManager get to => Get.find<NavigationManager>(); static NavigationManager get to => Get.find<NavigationManager>();
// 音乐服务实例 // 导航服务实例
final AmapService _amapervice = AmapService(); final AmapService _amapervice = AmapService();
/// 初始化 /// 初始化

426
lib/modules/agent/views/navigation_card.dart

@ -31,7 +31,15 @@ String formatDuration(String secondsStr) {
return parts.join(''); return parts.join('');
} }
//导航卡片 /// 导航卡片组件
///
/// 各元素含义分析:
/// - [start]: 起点坐标 (经纬度格式)
/// - [end]: 终点坐标 (经纬度格式)
/// - [destination]: 目的地名称显示
/// - [modes]: 导航方式列表,包含驾车、步行、公交等选项
/// - [navigation]: 当前导航状态信息(可选)
/// - [height]: 卡片高度
Widget navigationCard( Widget navigationCard(
BuildContext context, { BuildContext context, {
required String start, required String start,
@ -39,72 +47,168 @@ Widget navigationCard(
required String destination, required String destination,
required List<Map<String, dynamic>> modes, required List<Map<String, dynamic>> modes,
Map<String, dynamic>? navigation, Map<String, dynamic>? navigation,
double height = 300, double? height, // 改为可选参数,允许自适应高度
}) { }) {
int currentIndex = 0; int currentIndex = 0;
final controller = Get.find<AgentController>(); // 获取控制器 final controller = Get.find<AgentController>();
final isDarkMode = Theme.of(context).brightness == Brightness.dark; final isDarkMode = Theme.of(context).brightness == Brightness.dark;
return Container( return Container(
height: height.h, height: height?.h ?? 350.h, // 如果没有指定高度,使用350作为默认高度
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16.r), borderRadius: BorderRadius.circular(16.r),
color: isDarkMode ? const Color(0xFF1E1E1E) : Colors.white, color: isDarkMode ? const Color(0xFF1E1E1E) : Colors.white,
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
blurRadius: 8, blurRadius: 12,
color: Colors.black.withValues(alpha: isDarkMode ? 0.3 : 0.12), color: Colors.black.withValues(alpha: isDarkMode ? 0.4 : 0.08),
offset: const Offset(0, 4), offset: const Offset(0, 2),
), ),
], ],
), ),
child: StatefulBuilder( child: StatefulBuilder(
builder: (context, setInnerState) { builder: (context, setInnerState) {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// 路线图片 // 顶部:路线预览图片区域
ClipRRect( Expanded(
borderRadius: flex: 4, // 增加图片区域的flex值,让图片占更多空间
BorderRadius.vertical(top: Radius.circular(16.r)), child: ClipRRect(
child: AnimatedSwitcher( borderRadius: BorderRadius.vertical(top: Radius.circular(16.r)),
duration: const Duration(milliseconds: 300), child: Stack(
child: Image.network( children: [
modes[currentIndex]["image"] as String, // 路线图片
key: ValueKey<String>(modes[currentIndex]["ntype"] as String), AnimatedSwitcher(
height: 160.h, duration: const Duration(milliseconds: 300),
width: double.infinity, child: Image.network(
fit: BoxFit.cover, modes[currentIndex]["image"] as String,
key: ValueKey<String>(
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( Container(
padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 4.w), padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 16.w),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: List.generate(modes.length, (index) { children: List.generate(modes.length, (index) {
final mode = modes[index];
final isSelected = index == currentIndex; final isSelected = index == currentIndex;
return GestureDetector( final isEnabled = mode['enable'] as bool;
onTap: modes[index]['enable'] as bool
? () { return Expanded(
setInnerState(() => currentIndex = index); child: GestureDetector(
} onTap: isEnabled
: null, ? () {
child: SizedBox( setInnerState(() => currentIndex = index);
width: 50.w, }
child: Text( : null,
(modes[index]["ntype"] as String).tr, child: Container(
textAlign: TextAlign.center, margin: EdgeInsets.symmetric(horizontal: 4.w),
style: TextStyle( padding: EdgeInsets.symmetric(
color: modes[index]['enable'] vertical: 8.h, horizontal: 12.w),
? isSelected decoration: BoxDecoration(
? Theme.of(context).primaryColor color: isSelected
: isDarkMode ? Colors.white.withValues(alpha: 0.9) : Colors.black87 ? Theme.of(context).primaryColor.withOpacity(0.1)
: Colors.grey, : Colors.transparent,
fontSize: 12.sp, borderRadius: BorderRadius.circular(8.r),
fontWeight: FontWeight.w500, 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( Container(
padding: EdgeInsets.symmetric(horizontal: 8.w), padding: EdgeInsets.fromLTRB(20.w, 8.h, 20.w, 16.h),
child: Row( child: Stack(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Column( // 目的地和距离时间信息
crossAxisAlignment: CrossAxisAlignment.start, Padding(
children: [ padding: EdgeInsets.only(right: 70.w), // 为右下角按钮留出空间
Text(destination, child: Column(
style: TextStyle( crossAxisAlignment: CrossAxisAlignment.start,
fontSize: 12.sp, children: [
color: isDarkMode ? Colors.white.withValues(alpha: 0.9) : Colors.black87)), // 路径显示:当前位置 → 目的地
Text( Row(
'${formatDistance(modes[currentIndex]["distance"])}-约${formatDuration(modes[currentIndex]["duration"])}', children: [
style: TextStyle( // 起点图标
fontSize: 15.sp, Icon(
color: Theme.of(context).primaryColor)), Icons.my_location,
if (navigation != null) size: 16.sp,
Text( color: Theme.of(context).primaryColor,
"导航中... ${navigation["distanceText"]}进入${navigation["nextRoad"]}", ),
style: TextStyle( 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, 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, Positioned(
radius: 18.r, right: 0,
child: IconButton( bottom: 0,
icon: Icon(Icons.turn_left, child: Container(
color: Colors.white, size: 20.sp), decoration: BoxDecoration(
onPressed: () { gradient: LinearGradient(
controller.openNavigation(start, end, ""); 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;
}
}

Loading…
Cancel
Save