Files
flutterApp/lib/features/home/presentation/widgets/work_params_card.dart

323 lines
12 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:fpdart/fpdart.dart' hide State;
import 'package:maibu_satabot_v2/features/devices/domain/entities/device_entity.dart';
import 'package:maibu_satabot_v2/features/devices/domain/usecases/device_work_hostrirty_usecase.dart';
import 'package:maibu_satabot_v2/features/devices/domain/entities/device_run_hostrity_entity.dart';
import 'package:maibu_satabot_v2/features/devices/domain/errors/device_failure.dart';
import 'package:maibu_satabot_v2/features/devices/presentation/bloc/device_status_bloc.dart';
import 'package:maibu_satabot_v2/features/devices/presentation/bloc/device_status_state.dart';
import '../../../../core/di/injection.dart';
import '../../../devices/presentation/bloc/devices_cubit.dart';
import 'package:intl/intl.dart';
class WorkParamsCard extends StatefulWidget {
const WorkParamsCard({super.key});
@override
State<WorkParamsCard> createState() => _WorkParamsCardState();
}
class _WorkParamsCardState extends State<WorkParamsCard> {
List<DeviceRunStatisticsEntity>? _statistics;
String? _errorMessage;
bool _isLoading = false;
// 计时相关
DateTime? _firstTimestamp; // 第一次时间(开始时间)
int _totalWorkMinutes = 0; // 总作业分钟
@override
Widget build(BuildContext context) {
final deviceState = context.watch<DevicesCubit>().state;
final currentDevice = deviceState.selectedDevice;
final statusState = context.watch<DeviceStatusBloc>().state;
double? distance;
double? workArea;
if (statusState is DeviceStatusUpdated) {
workArea = double.tryParse(statusState.status.workingArea ?? '');
distance = workArea != null ? workArea / 0.7 : null;
// ====================== 计算作业时长(核心) ======================
_calculateWorkingTime(statusState.status.timestamp);
}
return Container(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(24),
boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.02), blurRadius: 10, offset: const Offset(0, 4))],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Container(
width: 4,
height: 16,
decoration: BoxDecoration(color: Colors.blueAccent, borderRadius: BorderRadius.circular(2)),
),
const SizedBox(width: 8),
const Text(
'作业参数',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Colors.black),
),
],
),
GestureDetector(
onTap: () {
if (currentDevice != null) {
_handleInfoTap(currentDevice);
}
},
child: Row(
children: [
Icon(Icons.info, size: 14, color: Colors.grey[400]),
const SizedBox(width: 4),
Text('查看详情', style: TextStyle(fontSize: 12, color: Colors.grey[400])),
],
),
),
],
),
const SizedBox(height: 24),
IntrinsicHeight(
child: Row(
children: [
_buildParamItem('作业面积', '${workArea?.toStringAsFixed(1) ?? '0'}', '㎡'),
_buildDivider(),
_buildParamItem('作业里程', '${distance?.toStringAsFixed(1) ?? '0'}', 'm'),
_buildDivider(),
// 显示实时计算的作业时长
_buildParamItem('作业时长', '${_totalWorkMinutes}', 'min'),
],
),
),
],
),
);
}
// ====================== 核心:根据时间戳计算时长 ======================
// 修复时间解析:适配 20-1-11-31 08:00:00 格式
void _calculateWorkingTime(String timestampStr) {
if (timestampStr.isEmpty) return;
try {
// 格式:年-月-日-星期 时:分:秒
// 我们只取 年-月-日 时:分:秒 来解析
List<String> parts = timestampStr.split(' ');
if (parts.length != 2) return;
String datePart = parts[0]; // 20-1-11-31
String timePart = parts[1]; // 08:00:00
List<String> dateSegments = datePart.split('-');
if (dateSegments.length < 3) return;
// 只取 年-月-日,丢弃星期
String cleanDate = '${dateSegments[0]}-${dateSegments[1]}-${dateSegments[2]}';
DateTime currentTime = DateFormat("yy-MM-dd HH:mm:ss").parse('$cleanDate $timePart');
// 记录第一次时间
if (_firstTimestamp == null) {
setState(() {
_firstTimestamp = currentTime;
});
}
// 计算分钟
final diff = currentTime.difference(_firstTimestamp!);
setState(() {
_totalWorkMinutes = diff.inMinutes;
});
} catch (e) {
debugPrint("时间解析失败: $e");
}
}
Future<void> _handleInfoTap(DeviceEntity currentDevice) async {
setState(() {
_isLoading = true;
_errorMessage = null;
});
try {
final useCase = DeviceWorkHostrirty(sl());
final deviceId = currentDevice.deviceName.toString() ?? '';
final params = BindDeviceParams(deviceId, 'your_device_alias');
final result = await useCase(params);
result.fold(
(failure) {
setState(() {
_errorMessage = failure.message;
_isLoading = false;
});
},
(data) {
setState(() {
_statistics = data;
_isLoading = false;
});
_showDetailsDialog(context, _statistics!);
},
);
} catch (e) {
setState(() {
_errorMessage = '请求失败: $e';
_isLoading = false;
});
}
}
void _showDetailsDialog(BuildContext context, List<DeviceRunStatisticsEntity> data) {
showModalBottomSheet(
context: context,
isScrollControlled: true,
useRootNavigator: true,
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(20))),
builder: (context) {
return Container(
height: MediaQuery.of(context).size.height * 0.75,
padding: const EdgeInsets.all(20),
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Center(child: Container(width: 40, height: 4)),
const Text(
'历史记录详情',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold, color: Colors.black87),
),
IconButton(
onPressed: () => Navigator.pop(context),
icon: const Icon(Icons.close, color: Colors.grey, size: 24),
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
),
],
),
const SizedBox(height: 10),
Expanded(
child: data.isEmpty
? const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.description_outlined, size: 60, color: Colors.grey),
SizedBox(height: 16),
Text('暂无历史记录', style: TextStyle(fontSize: 16, color: Colors.grey)),
],
),
)
: SingleChildScrollView(
child: Column(
children: data.map((item) {
return Container(
margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.blue.withOpacity(0.05),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.blue.withOpacity(0.2), width: 1),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildDetailRow(Icons.calendar_today, '开始时间', item.startTime),
_buildDetailRow(Icons.calendar_today_outlined, '结束时间', item.endTime),
_buildDetailRow(Icons.square_foot, '工作面积', '${item.workArea} ㎡'),
if (item.distance != null && item.distance! > 0)
_buildDetailRow(Icons.directions_car, '工作距离', '${item.distance!.toStringAsFixed(2)} m'),
_buildDetailRow(Icons.timer, '花费时间', '${item.time} min'),
],
),
);
}).toList(),
),
),
),
],
),
);
},
);
}
Widget _buildDetailRow(IconData icon, String label, String value) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Row(
children: [
Icon(icon, size: 16, color: Colors.blue),
const SizedBox(width: 8),
Text(
'$label: ',
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: Colors.black87),
),
Expanded(
child: Text(value, style: const TextStyle(fontSize: 14, color: Colors.black54)),
),
],
),
);
}
Widget _buildParamItem(String label, String value, String unit) {
return Expanded(
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
label == '作业面积'
? Icons.aspect_ratio
: label == '作业里程'
? Icons.local_shipping_outlined
: Icons.access_time,
size: 14,
color: Colors.black38,
),
const SizedBox(width: 4),
Text(label, style: const TextStyle(fontSize: 12, color: Colors.black38)),
],
),
const SizedBox(height: 12),
RichText(
text: TextSpan(
children: [
TextSpan(
text: value,
style: const TextStyle(fontSize: 28, fontWeight: FontWeight.w700, color: Colors.black, fontFamily: 'Inter'),
),
TextSpan(
text: ' $unit',
style: const TextStyle(fontSize: 12, fontWeight: FontWeight.normal, color: Colors.black54),
),
],
),
),
],
),
);
}
Widget _buildDivider() {
return VerticalDivider(color: Colors.black.withOpacity(0.05), thickness: 1, indent: 10, endIndent: 10);
}
}