You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
472 lines
20 KiB
472 lines
20 KiB
import 'package:flutter/material.dart';
|
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
|
import 'package:get/get.dart';
|
|
import '../controllers/agent_controller.dart';
|
|
|
|
String formatDistance(String distanceStr) {
|
|
final distance = double.tryParse(distanceStr);
|
|
if (distance == null) return distanceStr; // 如果无法解析,则返回原始字符串
|
|
|
|
if (distance >= 1000) {
|
|
final km = distance / 1000;
|
|
return '${km.toStringAsFixed(1)} KM';
|
|
} else {
|
|
return '${distance.toStringAsFixed(0)} M';
|
|
}
|
|
}
|
|
|
|
String formatDuration(String secondsStr) {
|
|
int totalSeconds = int.tryParse(secondsStr) ?? 0;
|
|
Duration duration = Duration(seconds: totalSeconds);
|
|
|
|
int hours = duration.inHours;
|
|
int minutes = duration.inMinutes.remainder(60);
|
|
int seconds = duration.inSeconds.remainder(60);
|
|
|
|
List<String> parts = [];
|
|
if (hours > 0) parts.add('$hours${'hours'.tr}'); // 小时
|
|
if (minutes > 0) parts.add('$minutes${'minutes'.tr}'); // 分钟
|
|
//不需要秒
|
|
//if (seconds > 0 || parts.isEmpty) parts.add('$seconds${'seconds'.tr}'); // 秒
|
|
|
|
return parts.join('');
|
|
}
|
|
|
|
/// 导航卡片组件
|
|
///
|
|
/// 各元素含义分析:
|
|
/// - [start]: 起点坐标 (经纬度格式)
|
|
/// - [end]: 终点坐标 (经纬度格式)
|
|
/// - [destination]: 目的地名称显示
|
|
/// - [modes]: 导航方式列表,包含驾车、步行、公交等选项
|
|
/// - [navigation]: 当前导航状态信息(可选)
|
|
/// - [height]: 卡片高度
|
|
Widget navigationCard(
|
|
BuildContext context, {
|
|
required String start,
|
|
required String end,
|
|
required String endlatitude,
|
|
required String endlongitude,
|
|
required String destination,
|
|
required List<Map<String, dynamic>> modes,
|
|
Map<String, dynamic>? navigation,
|
|
double? height, // 改为可选参数,允许自适应高度
|
|
}) {
|
|
int currentIndex = 0;
|
|
final controller = Get.find<AgentController>();
|
|
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
|
|
|
|
return Container(
|
|
height: height?.h ?? 350.h, // 如果没有指定高度,使用350作为默认高度
|
|
decoration: BoxDecoration(
|
|
borderRadius: BorderRadius.circular(16.r),
|
|
color: isDarkMode ? const Color(0xFF1E1E1E) : Colors.white,
|
|
boxShadow: [
|
|
BoxShadow(
|
|
blurRadius: 12,
|
|
color: Colors.black.withValues(alpha: isDarkMode ? 0.4 : 0.08),
|
|
offset: const Offset(0, 2),
|
|
),
|
|
],
|
|
),
|
|
child: StatefulBuilder(
|
|
builder: (context, setInnerState) {
|
|
return Column(
|
|
children: [
|
|
// 顶部:路线预览图片区域
|
|
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<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(
|
|
'navigating'.tr, // 导航中
|
|
style: TextStyle(
|
|
color: Colors.white,
|
|
fontSize: 12.sp,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
|
|
// 中部:导航方式选择器
|
|
Container(
|
|
padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 16.w),
|
|
child: Row(
|
|
children: List.generate(modes.length, (index) {
|
|
final mode = modes[index];
|
|
final isSelected = index == currentIndex;
|
|
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,
|
|
// ),
|
|
// ),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}),
|
|
),
|
|
),
|
|
|
|
// 底部:目的地信息和操作按钮
|
|
Container(
|
|
padding: EdgeInsets.fromLTRB(20.w, 8.h, 20.w, 16.h),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// 第一层:起点 → 目的地信息(独占整个宽度)
|
|
Row(
|
|
crossAxisAlignment: CrossAxisAlignment.center, // 确保垂直居中对齐
|
|
children: [
|
|
// 起点图标 - 统一图标容器宽度,确保对齐
|
|
SizedBox(
|
|
width: 16.sp, // 固定图标容器宽度
|
|
child: Icon(
|
|
Icons.my_location,
|
|
size: 15.sp, // 统一图标大小
|
|
color: Theme.of(context).primaryColor,
|
|
),
|
|
),
|
|
SizedBox(width: 6.w), // 统一间距
|
|
// 当前位置文本
|
|
Text(
|
|
'currentLocation'.tr, // 当前位置
|
|
style: TextStyle(
|
|
fontSize: 14.sp,
|
|
fontWeight: FontWeight.w500,
|
|
color: isDarkMode ? Colors.white70 : Colors.black54,
|
|
height: 1.3, // 设置行高,提升多行文本可读性
|
|
),
|
|
softWrap: true, // 启用软换行
|
|
overflow: TextOverflow.visible, // 确保文本完全显示
|
|
),
|
|
SizedBox(width: 8.w),
|
|
// 箭头图标
|
|
Icon(
|
|
Icons.arrow_forward,
|
|
size: 15.sp, // 统一图标大小
|
|
color: isDarkMode ? Colors.white54 : Colors.black45,
|
|
),
|
|
SizedBox(width: 8.w),
|
|
// 目的地图标
|
|
Icon(
|
|
Icons.location_on,
|
|
size: 15.sp, // 统一图标大小
|
|
color: Colors.red,
|
|
),
|
|
SizedBox(width: 6.w), // 统一间距
|
|
// 目的地名称 - 使用Expanded确保自动换行
|
|
Expanded(
|
|
child: Text(
|
|
destination,
|
|
style: TextStyle(
|
|
fontSize: 16.sp,
|
|
fontWeight: FontWeight.w600,
|
|
color: isDarkMode ? Colors.white : Colors.black87,
|
|
height: 1.2, // 设置行高,提升多行文本可读性
|
|
),
|
|
softWrap: true, // 启用软换行
|
|
overflow: TextOverflow.visible, // 确保文本完全显示
|
|
),
|
|
),
|
|
],
|
|
),
|
|
|
|
SizedBox(height: 4.h), // 第一层和第二层之间的间距
|
|
|
|
// 第二层:距离时间信息(左侧)+ 导航按钮(右侧)
|
|
Row(
|
|
crossAxisAlignment: CrossAxisAlignment.end, // 底部对齐
|
|
children: [
|
|
// 左侧:距离和时间信息
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// 距离信息
|
|
Row(
|
|
crossAxisAlignment:
|
|
CrossAxisAlignment.center, // 确保垂直居中对齐
|
|
children: [
|
|
// 距离图标 - 统一图标容器宽度,确保与第一层对齐
|
|
SizedBox(
|
|
width: 16.sp, // 与第一层图标容器宽度一致
|
|
child: Icon(
|
|
Icons.straighten,
|
|
size: 15.sp, // 统一图标大小
|
|
color: Theme.of(context).primaryColor,
|
|
),
|
|
),
|
|
SizedBox(width: 6.w), // 与第一层间距一致
|
|
Flexible(
|
|
child: Text(
|
|
formatDistance(
|
|
modes[currentIndex]["distance"]),
|
|
style: TextStyle(
|
|
fontSize: 14.sp,
|
|
color: Theme.of(context).primaryColor,
|
|
fontWeight: FontWeight.w500,
|
|
height: 1.3, // 设置行高,提升多行文本可读性
|
|
),
|
|
softWrap: true, // 启用软换行
|
|
overflow: TextOverflow.visible, // 确保文本完全显示
|
|
),
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 4.h),
|
|
// 时间信息
|
|
Row(
|
|
crossAxisAlignment:
|
|
CrossAxisAlignment.center, // 确保垂直居中对齐
|
|
children: [
|
|
// 时间图标 - 统一图标容器宽度,确保与第一层对齐
|
|
SizedBox(
|
|
width: 16.sp, // 与第一层图标容器宽度一致
|
|
child: Icon(
|
|
Icons.access_time,
|
|
size: 15.sp, // 统一图标大小
|
|
color: Theme.of(context).primaryColor,
|
|
),
|
|
),
|
|
SizedBox(width: 6.w), // 与第一层间距一致
|
|
Flexible(
|
|
child: Text(
|
|
'${'approximate'.tr}${formatDuration(modes[currentIndex]["duration"])}', // 约
|
|
style: TextStyle(
|
|
fontSize: 14.sp,
|
|
color: Theme.of(context).primaryColor,
|
|
fontWeight: FontWeight.w500,
|
|
height: 1.3, // 设置行高,提升多行文本可读性
|
|
),
|
|
softWrap: true, // 启用软换行
|
|
overflow: TextOverflow.visible, // 确保文本完全显示
|
|
),
|
|
),
|
|
],
|
|
),
|
|
// 导航状态信息
|
|
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"]}${'enterRoad'.tr}${navigation["nextRoad"]}", // 进入
|
|
style: TextStyle(
|
|
fontSize: 12.sp,
|
|
color: Theme.of(context).primaryColor,
|
|
fontWeight: FontWeight.w500,
|
|
height: 1.3, // 设置行高,提升多行文本可读性
|
|
),
|
|
softWrap: true, // 启用软换行
|
|
overflow: TextOverflow.visible, // 确保文本完全显示
|
|
),
|
|
),
|
|
],
|
|
],
|
|
),
|
|
),
|
|
|
|
SizedBox(width: 16.w), // 左右内容之间的间距
|
|
|
|
// 右侧:开始导航按钮
|
|
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(
|
|
true,
|
|
start,
|
|
destination,
|
|
endlatitude,
|
|
endlongitude,
|
|
modes[currentIndex]["ntype"] as String);
|
|
},
|
|
child: Container(
|
|
padding: EdgeInsets.symmetric(
|
|
horizontal: 16.w,
|
|
vertical: 12.h,
|
|
),
|
|
child: Icon(
|
|
Icons.navigation,
|
|
color: Colors.white,
|
|
size: 20.sp,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
/// 根据导航类型获取对应图标
|
|
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;
|
|
}
|
|
}
|
|
|