基本完成APP的所有页面中的语言切换支持的开发和设计
This commit is contained in:
@@ -56,6 +56,7 @@ import '../../features/remote_control/data/datasources/remote_tcp_datasource.dar
|
||||
import '../../features/remote_control/domain/usecase/remote_control_usecase.dart';
|
||||
import '../../features/remote_control/presentation/bloc/remote_control_cubit.dart';
|
||||
import '../app/app_user_cubit.dart';
|
||||
import '../localization/locale_cubit.dart';
|
||||
import '../network/dio_client.dart';
|
||||
import '../network/net_message_dispatcher.dart';
|
||||
import '../network/tcp/tcp_client.dart';
|
||||
@@ -183,6 +184,10 @@ Future<void> init() async {
|
||||
|
||||
/// 5. 状态管理 (Cubit/Bloc)
|
||||
sl.registerLazySingleton(() => AppUserCubit()); // AuthCubit 依赖它,必须先注册
|
||||
|
||||
// 🔥 语言管理 Cubit (单例)
|
||||
sl.registerLazySingleton(() => LocaleCubit());
|
||||
|
||||
sl.registerLazySingleton(() => GetDeviceLocationUseCase(sl()));
|
||||
sl.registerLazySingleton(() => DevicesCubit(sl(), sl(), sl(), sl(), sl(), sl(), sl(), sl(), sl(), sl(), sl(), sl(),sl(),sl(),sl()));
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:maibu_satabot_v2/core/app/app_user_cubit.dart';
|
||||
import 'package:maibu_satabot_v2/core/localization/app_localizations.dart';
|
||||
|
||||
import '../bloc/ai_cubit.dart';
|
||||
import '../bloc/ai_state.dart';
|
||||
@@ -53,7 +54,7 @@ class _AiViewState extends State<AiView> {
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('新能源光伏智能体'),
|
||||
title: Text(AppLocalizations.of(context).translate('ai.title')),
|
||||
actions: [
|
||||
PopupMenuButton<String>(
|
||||
onSelected: (value) {
|
||||
@@ -64,8 +65,8 @@ class _AiViewState extends State<AiView> {
|
||||
}
|
||||
},
|
||||
itemBuilder: (BuildContext context) => <PopupMenuEntry<String>>[
|
||||
const PopupMenuItem<String>(value: 'new_session', child: Text('开启新的会话')),
|
||||
const PopupMenuItem<String>(value: 'history', child: Text('历史会话')),
|
||||
PopupMenuItem<String>(value: 'new_session', child: Text(AppLocalizations.of(context).translate('ai.new_session'))),
|
||||
PopupMenuItem<String>(value: 'history', child: Text(AppLocalizations.of(context).translate('ai.history'))),
|
||||
],
|
||||
),
|
||||
],
|
||||
@@ -190,8 +191,8 @@ class _AiViewState extends State<AiView> {
|
||||
children: [
|
||||
Icon(Icons.lightbulb_outline, size: 20, color: Colors.green.shade700),
|
||||
const SizedBox(width: 8),
|
||||
const Text(
|
||||
"最终结论",
|
||||
Text(
|
||||
AppLocalizations.of(context).translate('ai.final_conclusion'),
|
||||
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16, color: Color(0xFF2D7D46)),
|
||||
),
|
||||
],
|
||||
@@ -259,7 +260,7 @@ class _AiViewState extends State<AiView> {
|
||||
Icon(icon, size: 20, color: iconColor),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
title,
|
||||
title == "深度思考" ? AppLocalizations.of(context).translate('ai.deep_thinking') : title,
|
||||
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16, color: iconColor),
|
||||
),
|
||||
if (showSpinner) ...[
|
||||
@@ -298,7 +299,9 @@ class _AiViewState extends State<AiView> {
|
||||
controller: _textController,
|
||||
enabled: state.status != AiStatus.loading,
|
||||
decoration: InputDecoration(
|
||||
hintText: state.status == AiStatus.loading ? 'AI正在思考中...' : '输入你的想法...',
|
||||
hintText: state.status == AiStatus.loading
|
||||
? AppLocalizations.of(context).translate('ai.thinking_hint')
|
||||
: AppLocalizations.of(context).translate('ai.input_hint'),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(30.0),
|
||||
borderSide: const BorderSide(width: 0, style: BorderStyle.none),
|
||||
@@ -372,7 +375,7 @@ class _AiViewState extends State<AiView> {
|
||||
children: <Widget>[
|
||||
ListTile(
|
||||
leading: const Icon(Icons.photo_library),
|
||||
title: const Text('相册'),
|
||||
title: Text(AppLocalizations.of(context).translate('ai.album')),
|
||||
onTap: () {
|
||||
Navigator.of(context).pop();
|
||||
context.read<AiCubit>().selectImage(ImageSource.gallery);
|
||||
@@ -380,7 +383,7 @@ class _AiViewState extends State<AiView> {
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.photo_camera),
|
||||
title: const Text('拍照'),
|
||||
title: Text(AppLocalizations.of(context).translate('ai.camera')),
|
||||
onTap: () {
|
||||
Navigator.of(context).pop();
|
||||
context.read<AiCubit>().selectImage(ImageSource.camera);
|
||||
@@ -408,7 +411,7 @@ class _AiViewState extends State<AiView> {
|
||||
context: context,
|
||||
builder: (BuildContext dialogContext) {
|
||||
return AlertDialog(
|
||||
title: const Text('历史记录'),
|
||||
title: Text(AppLocalizations.of(context).translate('ai.history')),
|
||||
content: SizedBox(
|
||||
width: double.maxFinite,
|
||||
child: BlocBuilder<AiCubit, AiState>(
|
||||
@@ -430,7 +433,7 @@ class _AiViewState extends State<AiView> {
|
||||
},
|
||||
),
|
||||
),
|
||||
actions: [TextButton(onPressed: () => Navigator.of(dialogContext).pop(), child: const Text('关闭'))],
|
||||
actions: [TextButton(onPressed: () => Navigator.of(dialogContext).pop(), child: Text(AppLocalizations.of(context).translate('common.close')))],
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:convert';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:maibu_satabot_v2/core/localization/app_localizations.dart';
|
||||
import 'package:maibu_satabot_v2/core/router/route_paths.dart';
|
||||
import 'package:webview_flutter/webview_flutter.dart';
|
||||
|
||||
@@ -68,7 +69,7 @@ class _DjPageState extends State<DjPage> {
|
||||
context.go(RoutePaths.home);
|
||||
},
|
||||
),
|
||||
title: const Text("路径规划", style: TextStyle(color: Colors.white)),
|
||||
title: Text(AppLocalizations.of(context).translate('route_planning.title'), style: const TextStyle(color: Colors.white)),
|
||||
backgroundColor: const Color(0xFF1677FF),
|
||||
foregroundColor: Colors.white,
|
||||
),
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:maibu_satabot_v2/core/app/app_user_cubit.dart';
|
||||
import 'package:maibu_satabot_v2/core/consts/tcp_consts.dart';
|
||||
import 'package:maibu_satabot_v2/core/localization/app_localizations.dart';
|
||||
import 'package:maibu_satabot_v2/core/network/tcp/tcp_client.dart';
|
||||
import 'package:maibu_satabot_v2/features/devices/presentation/bloc/devices_cubit.dart';
|
||||
import 'package:maibu_satabot_v2/features/devices/presentation/bloc/devices_state.dart'; // 👈 必须加
|
||||
@@ -94,7 +95,13 @@ class _HomePageState extends State<HomePage> {
|
||||
// 🔥 现在取的设备 = 100% 最新加载完成的
|
||||
final currentDevice = deviceState.selectedDevice;
|
||||
|
||||
final defaultDevice = DeviceEntity(deviceName: '暂未绑定设备', productId: 00000, productName: '', tenantId: 00000, tenantName: '');
|
||||
final defaultDevice = DeviceEntity(
|
||||
deviceName: AppLocalizations.of(context).translate('home.no_device_bound'),
|
||||
productId: 00000,
|
||||
productName: '',
|
||||
tenantId: 00000,
|
||||
tenantName: '',
|
||||
);
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: const Color(0xFFF7F7F7),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||
import 'package:maibu_satabot_v2/core/localization/app_localizations.dart';
|
||||
|
||||
class ManualPage extends StatefulWidget {
|
||||
const ManualPage({super.key});
|
||||
@@ -13,49 +14,69 @@ class _ManualPageState extends State<ManualPage> {
|
||||
bool showMenu = true;
|
||||
String currentType = '';
|
||||
|
||||
final List<String> menuTitles = const ['安全警示及使用注意事项', '机器使用说明', '保养与维修', '故障处置', '本商品搬运的要领', '关于三包与售后服务'];
|
||||
|
||||
final List<List<Map<String, dynamic>>> subMenus = [
|
||||
[
|
||||
{'title': '特别警告', 'type': 'alert'},
|
||||
{'title': '关于本产品的警告标签说明', 'type': 'alertinstruction'},
|
||||
{'title': '安全操作须知', 'type': 'safety'},
|
||||
{'title': '安全驾驶·安全作业须知', 'type': 'safetywork'},
|
||||
],
|
||||
[
|
||||
{'title': '发动前的准备', 'type': 'prepare'},
|
||||
{'title': '启动方法', 'type': 'startmethod'},
|
||||
{'title': '割草方法', 'type': 'work'},
|
||||
{'title': '停车方法', 'type': 'stop'},
|
||||
{'title': '遥控器使用', 'type': 'controluse'},
|
||||
],
|
||||
[
|
||||
{'title': '定期检查表', 'type': 'check1'},
|
||||
{'title': '行走部分及割草部分的检查', 'type': 'check2'},
|
||||
{'title': '加油、加水以及上润滑脂、润滑油一览表', 'type': 'supply'},
|
||||
{'title': '消耗零件(更换零件)一览表', 'type': 'check4'},
|
||||
{'title': '发动机', 'type': 'check5'},
|
||||
{'title': '行走装置', 'type': 'check6'},
|
||||
{'title': '使用后的维修', 'type': 'check7'},
|
||||
],
|
||||
[
|
||||
{'title': '问题诊断表', 'type': 'breakdown1'},
|
||||
],
|
||||
[
|
||||
{'title': '搬运要领', 'type': 'starpoint'},
|
||||
],
|
||||
[
|
||||
{'title': '关于三包', 'type': 'about1'},
|
||||
{'title': '关于售后服务', 'type': 'about2'},
|
||||
{'title': '关于零件的补给年限(期间)', 'type': 'supplyyear'},
|
||||
],
|
||||
];
|
||||
late List<String> menuTitles;
|
||||
late List<List<Map<String, dynamic>>> subMenus;
|
||||
|
||||
void _initLocalizedMenus() {
|
||||
final loc = AppLocalizations.of(context);
|
||||
menuTitles = [
|
||||
loc.translate('manual.menu_titles.0'),
|
||||
loc.translate('manual.menu_titles.1'),
|
||||
loc.translate('manual.menu_titles.2'),
|
||||
loc.translate('manual.menu_titles.3'),
|
||||
loc.translate('manual.menu_titles.4'),
|
||||
loc.translate('manual.menu_titles.5'),
|
||||
];
|
||||
|
||||
subMenus = [
|
||||
[
|
||||
{'title': loc.translate('manual.submenus.alert'), 'type': 'alert'},
|
||||
{'title': loc.translate('manual.submenus.alertinstruction'), 'type': 'alertinstruction'},
|
||||
{'title': loc.translate('manual.submenus.safety'), 'type': 'safety'},
|
||||
{'title': loc.translate('manual.submenus.safetywork'), 'type': 'safetywork'},
|
||||
],
|
||||
[
|
||||
{'title': loc.translate('manual.submenus.prepare'), 'type': 'prepare'},
|
||||
{'title': loc.translate('manual.submenus.startmethod'), 'type': 'startmethod'},
|
||||
{'title': loc.translate('manual.submenus.work'), 'type': 'work'},
|
||||
{'title': loc.translate('manual.submenus.stop'), 'type': 'stop'},
|
||||
{'title': loc.translate('manual.submenus.controluse'), 'type': 'controluse'},
|
||||
],
|
||||
[
|
||||
{'title': loc.translate('manual.submenus.check1'), 'type': 'check1'},
|
||||
{'title': loc.translate('manual.submenus.check2'), 'type': 'check2'},
|
||||
{'title': loc.translate('manual.submenus.supply'), 'type': 'supply'},
|
||||
{'title': loc.translate('manual.submenus.check4'), 'type': 'check4'},
|
||||
{'title': loc.translate('manual.submenus.check5'), 'type': 'check5'},
|
||||
{'title': loc.translate('manual.submenus.check6'), 'type': 'check6'},
|
||||
{'title': loc.translate('manual.submenus.check7'), 'type': 'check7'},
|
||||
],
|
||||
[
|
||||
{'title': loc.translate('manual.submenus.breakdown1'), 'type': 'breakdown1'},
|
||||
],
|
||||
[
|
||||
{'title': loc.translate('manual.submenus.starpoint'), 'type': 'starpoint'},
|
||||
],
|
||||
[
|
||||
{'title': loc.translate('manual.submenus.about1'), 'type': 'about1'},
|
||||
{'title': loc.translate('manual.submenus.about2'), 'type': 'about2'},
|
||||
{'title': loc.translate('manual.submenus.supplyyear'), 'type': 'supplyyear'},
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
late List<bool> expandedList;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// menuTitles 和 subMenus 在 build 时初始化
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
_initLocalizedMenus();
|
||||
expandedList = List.filled(menuTitles.length, false);
|
||||
}
|
||||
|
||||
@@ -83,6 +104,7 @@ class _ManualPageState extends State<ManualPage> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
_initLocalizedMenus(); // 确保每次build时更新语言
|
||||
return Scaffold(
|
||||
backgroundColor: const Color(0xFFF7F8FA),
|
||||
appBar: AppBar(
|
||||
@@ -90,7 +112,7 @@ class _ManualPageState extends State<ManualPage> {
|
||||
elevation: 1,
|
||||
centerTitle: true,
|
||||
title: Text(
|
||||
showMenu ? '使用说明' : _getCurrentTitle(),
|
||||
showMenu ? AppLocalizations.of(context).translate('manual.title') : _getCurrentTitle(),
|
||||
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: Color(0xFF2A3A55)),
|
||||
),
|
||||
leading: IconButton(
|
||||
@@ -188,10 +210,11 @@ class _ManualPageState extends State<ManualPage> {
|
||||
}
|
||||
|
||||
Widget _buildContent() {
|
||||
return SingleChildScrollView(padding: const EdgeInsets.all(16), physics: const BouncingScrollPhysics(), child: _getContentWidget());
|
||||
final loc = AppLocalizations.of(context);
|
||||
return SingleChildScrollView(padding: const EdgeInsets.all(16), physics: const BouncingScrollPhysics(), child: _getContentWidget(loc));
|
||||
}
|
||||
|
||||
Widget _getContentWidget() {
|
||||
Widget _getContentWidget(AppLocalizations loc) {
|
||||
if (currentType == 'supply') {
|
||||
return _buildSupplyTable();
|
||||
} else if (currentType == 'breakdown1') {
|
||||
@@ -211,23 +234,23 @@ class _ManualPageState extends State<ManualPage> {
|
||||
const SizedBox(height: 16),
|
||||
_buildDiagnosticTable1(),
|
||||
const SizedBox(height: 24),
|
||||
const Text(
|
||||
"发电机",
|
||||
style: TextStyle(fontSize: 17, fontWeight: FontWeight.bold, color: Color(0xFF2A3A55)),
|
||||
Text(
|
||||
loc.translate('manual.tables.generator'),
|
||||
style: const TextStyle(fontSize: 17, fontWeight: FontWeight.bold, color: Color(0xFF2A3A55)),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_buildDiagnosticTable2(),
|
||||
const SizedBox(height: 24),
|
||||
const Text(
|
||||
"行走装置",
|
||||
style: TextStyle(fontSize: 17, fontWeight: FontWeight.bold, color: Color(0xFF2A3A55)),
|
||||
Text(
|
||||
loc.translate('manual.tables.walking_device'),
|
||||
style: const TextStyle(fontSize: 17, fontWeight: FontWeight.bold, color: Color(0xFF2A3A55)),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_buildDiagnosticTable3(),
|
||||
const SizedBox(height: 24),
|
||||
const Text(
|
||||
"割草装置",
|
||||
style: TextStyle(fontSize: 17, fontWeight: FontWeight.bold, color: Color(0xFF2A3A55)),
|
||||
Text(
|
||||
loc.translate('manual.tables.mowing_device'),
|
||||
style: const TextStyle(fontSize: 17, fontWeight: FontWeight.bold, color: Color(0xFF2A3A55)),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_buildDiagnosticTable4(),
|
||||
@@ -239,7 +262,10 @@ class _ManualPageState extends State<ManualPage> {
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(12)),
|
||||
child: const Text('本商品单个零件的提供年限为停止生产后三年。', style: TextStyle(fontSize: 16, color: Color(0xFF333333))),
|
||||
child: Text(loc.translate('manual.submenus.supplyyear') == '关于零件的补给年限(期间)'
|
||||
? '本商品单个零件的提供年限为停止生产后三年。'
|
||||
: 'The supply period for individual parts of this product is three years after production cessation.',
|
||||
style: const TextStyle(fontSize: 16, color: Color(0xFF333333))),
|
||||
);
|
||||
} else if (markdownMap.containsKey(currentType)) {
|
||||
return Container(
|
||||
@@ -269,7 +295,7 @@ class _ManualPageState extends State<ManualPage> {
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(12)),
|
||||
child: const Text("暂无内容", style: TextStyle(fontSize: 16, color: Color(0xFF8A98AC))),
|
||||
child: Text(AppLocalizations.of(context).translate('manual.no_content'), style: const TextStyle(fontSize: 16, color: Color(0xFF8A98AC))),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -283,19 +309,20 @@ class _ManualPageState extends State<ManualPage> {
|
||||
}
|
||||
}
|
||||
}
|
||||
return '详情';
|
||||
return AppLocalizations.of(context).translate('manual.detail');
|
||||
}
|
||||
|
||||
Widget _buildSupplyTable() {
|
||||
final loc = AppLocalizations.of(context);
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(12)),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'加油、加水以及上润滑脂、润滑油一览表',
|
||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Color(0xFF2A3A55)),
|
||||
Text(
|
||||
loc.translate('manual.tables.supply_title'),
|
||||
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Color(0xFF2A3A55)),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
SingleChildScrollView(
|
||||
@@ -307,10 +334,10 @@ class _ManualPageState extends State<ManualPage> {
|
||||
TableRow(
|
||||
decoration: const BoxDecoration(color: Color(0xFFF7F8FA)),
|
||||
children: [
|
||||
TableCell(child: _tableCellText('项目', isHeader: true)),
|
||||
TableCell(child: _tableCellText('补给(交换)的时期', isHeader: true)),
|
||||
TableCell(child: _tableCellText('推荐品种', isHeader: true)),
|
||||
TableCell(child: _tableCellText('容量', isHeader: true)),
|
||||
TableCell(child: _tableCellText(loc.translate('manual.tables.item'), isHeader: true)),
|
||||
TableCell(child: _tableCellText(loc.translate('manual.tables.period'), isHeader: true)),
|
||||
TableCell(child: _tableCellText(loc.translate('manual.tables.recommend'), isHeader: true)),
|
||||
TableCell(child: _tableCellText(loc.translate('manual.tables.capacity'), isHeader: true)),
|
||||
],
|
||||
),
|
||||
_tableRowSimple(['燃料', '随时', '汽车用91号以上无铅汽油或乙醇含量不超过10%的无铅汽油(E10)', '10L']),
|
||||
@@ -443,9 +470,14 @@ class _ManualPageState extends State<ManualPage> {
|
||||
}
|
||||
|
||||
TableRow _tableHeader() {
|
||||
final loc = AppLocalizations.of(context);
|
||||
return TableRow(
|
||||
decoration: const BoxDecoration(color: Color(0xFFF7F8FA)),
|
||||
children: [_tableCellText("问题现象说明", isHeader: true), _tableCellText("原因", isHeader: true), _tableCellText("处理", isHeader: true)],
|
||||
children: [
|
||||
_tableCellText(loc.translate('manual.tables.problem'), isHeader: true),
|
||||
_tableCellText(loc.translate('manual.tables.reason'), isHeader: true),
|
||||
_tableCellText(loc.translate('manual.tables.solution'), isHeader: true)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'dart:math' as math;
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:maibu_satabot_v2/core/localization/app_localizations.dart';
|
||||
import 'package:maibu_satabot_v2/features/home/presentation/widgets/common/commonFn.dart';
|
||||
import 'package:syncfusion_flutter_gauges/gauges.dart';
|
||||
import 'package:flutter/animation.dart';
|
||||
@@ -135,14 +136,14 @@ class _RunningStatusPageState extends State<RunningStatusPage> with WidgetsBindi
|
||||
final currentDevice = deviceState.selectedDevice;
|
||||
|
||||
if (currentDevice != null) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("数据刷新成功!"), duration: Duration(seconds: 1)));
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(AppLocalizations.of(context).translate('running_status.data_refresh_success')), duration: const Duration(seconds: 1)));
|
||||
// 刷新后重置超时状态
|
||||
if (_isDataTimeout) {
|
||||
setState(() => _isDataTimeout = false);
|
||||
}
|
||||
_startDataTimeoutTimer();
|
||||
} else {
|
||||
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("暂无设备,无法刷新"), duration: Duration(seconds: 1)));
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(AppLocalizations.of(context).translate('running_status.no_device')), duration: const Duration(seconds: 1)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -273,7 +274,7 @@ class _RunningStatusPageState extends State<RunningStatusPage> with WidgetsBindi
|
||||
children: [
|
||||
Icon(Icons.inbox_outlined, color: Colors.grey, size: 48),
|
||||
SizedBox(height: 16),
|
||||
Text('暂无数据', style: TextStyle(color: Colors.grey, fontSize: 16)),
|
||||
Text(AppLocalizations.of(context).translate('running_status.no_data'), style: TextStyle(color: Colors.grey, fontSize: 16)),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -314,9 +315,9 @@ class _RunningStatusPageState extends State<RunningStatusPage> with WidgetsBindi
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
_legendItem(color: const Color(0xFF3F51B5), label: "左轮"),
|
||||
_legendItem(color: const Color(0xFF3F51B5), label: AppLocalizations.of(context).translate('running_status.left_wheel')),
|
||||
const SizedBox(width: 24),
|
||||
_legendItem(color: const Color(0xFF8BC34A), label: "右轮"),
|
||||
_legendItem(color: const Color(0xFF8BC34A), label: AppLocalizations.of(context).translate('running_status.right_wheel')),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -472,7 +473,7 @@ class _RunningStatusPageState extends State<RunningStatusPage> with WidgetsBindi
|
||||
child: Column(
|
||||
children: [
|
||||
_chartCard(
|
||||
title: "电压 (V)",
|
||||
title: AppLocalizations.of(context).translate('running_status.voltage') + " (V)",
|
||||
isGaugeMode: _voltageGaugeMode,
|
||||
onGaugeTap: () => setState(() => _voltageGaugeMode = true),
|
||||
onChartTap: () => setState(() => _voltageGaugeMode = false),
|
||||
@@ -481,7 +482,7 @@ class _RunningStatusPageState extends State<RunningStatusPage> with WidgetsBindi
|
||||
: SizedBox(
|
||||
height: 180,
|
||||
child: _styledLineChart(
|
||||
title: "电压",
|
||||
title: AppLocalizations.of(context).translate('running_status.voltage'),
|
||||
lines: [_lineData(_voltageHistory, const Color(0xFF4CAF50))],
|
||||
yAxisMax: 250,
|
||||
yAxisMin: 0,
|
||||
@@ -492,7 +493,7 @@ class _RunningStatusPageState extends State<RunningStatusPage> with WidgetsBindi
|
||||
_gap(),
|
||||
|
||||
_chartCard(
|
||||
title: "芯片温度 (°C)",
|
||||
title: AppLocalizations.of(context).translate('running_status.chip_temp') + " (°C)",
|
||||
isGaugeMode: _chipTempGaugeMode,
|
||||
onGaugeTap: () => setState(() => _chipTempGaugeMode = true),
|
||||
onChartTap: () => setState(() => _chipTempGaugeMode = false),
|
||||
@@ -501,7 +502,7 @@ class _RunningStatusPageState extends State<RunningStatusPage> with WidgetsBindi
|
||||
: SizedBox(
|
||||
height: 180,
|
||||
child: _styledLineChart(
|
||||
title: "芯片温度",
|
||||
title: AppLocalizations.of(context).translate('running_status.chip_temp'),
|
||||
lines: [_lineData(_chipTempHistory, const Color(0xFF4CAF50))],
|
||||
yAxisMax: 100,
|
||||
yAxisMin: 0,
|
||||
@@ -512,7 +513,7 @@ class _RunningStatusPageState extends State<RunningStatusPage> with WidgetsBindi
|
||||
_gap(),
|
||||
|
||||
_chartCard(
|
||||
title: "割刀速度 (rpm)",
|
||||
title: AppLocalizations.of(context).translate('running_status.knife_speed') + " (rpm)",
|
||||
isGaugeMode: _knifeSpeedGaugeMode,
|
||||
onGaugeTap: () => setState(() => _knifeSpeedGaugeMode = true),
|
||||
onChartTap: () => setState(() => _knifeSpeedGaugeMode = false),
|
||||
@@ -526,7 +527,7 @@ class _RunningStatusPageState extends State<RunningStatusPage> with WidgetsBindi
|
||||
: SizedBox(
|
||||
height: 180,
|
||||
child: _styledLineChart(
|
||||
title: "割刀速度",
|
||||
title: AppLocalizations.of(context).translate('running_status.knife_speed'),
|
||||
lines: [_lineData(_knifeHistory, const Color(0xFF4CAF50))],
|
||||
yAxisMax: 3000,
|
||||
yAxisMin: -3000,
|
||||
@@ -547,14 +548,14 @@ class _RunningStatusPageState extends State<RunningStatusPage> with WidgetsBindi
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"左右轮测量速度对比 (rpm)",
|
||||
AppLocalizations.of(context).translate('running_status.left_right_measure_speed') + " (rpm)",
|
||||
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Color(0xFF1677FF)),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
SizedBox(
|
||||
height: 180,
|
||||
child: _styledLineChart(
|
||||
title: "左右轮测量速度对比",
|
||||
title: AppLocalizations.of(context).translate('running_status.left_right_measure_speed'),
|
||||
lines: [_lineData(_leftMeasureHistory, const Color(0xFF3F51B5)), _lineData(_rightMeasureHistory, const Color(0xFF8BC34A))],
|
||||
yAxisMax: 3000,
|
||||
yAxisMin: -3000,
|
||||
@@ -577,14 +578,14 @@ class _RunningStatusPageState extends State<RunningStatusPage> with WidgetsBindi
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"左右轮目标速度对比 (rpm)",
|
||||
AppLocalizations.of(context).translate('running_status.left_right_target_speed') + " (rpm)",
|
||||
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Color(0xFF1677FF)),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
SizedBox(
|
||||
height: 180,
|
||||
child: _styledLineChart(
|
||||
title: "左右轮目标速度对比",
|
||||
title: AppLocalizations.of(context).translate('running_status.left_right_target_speed'),
|
||||
lines: [_lineData(_leftTargetHistory, const Color(0xFF3F51B5)), _lineData(_rightTargetHistory, const Color(0xFF8BC34A))],
|
||||
yAxisMax: 3000,
|
||||
yAxisMin: -3000,
|
||||
@@ -607,14 +608,14 @@ class _RunningStatusPageState extends State<RunningStatusPage> with WidgetsBindi
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"电流对比 (A)",
|
||||
AppLocalizations.of(context).translate('running_status.current_compare') + " (A)",
|
||||
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Color(0xFF1677FF)),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
SizedBox(
|
||||
height: 180,
|
||||
child: _styledLineChart(
|
||||
title: "电流对比",
|
||||
title: AppLocalizations.of(context).translate('running_status.current_compare'),
|
||||
lines: [_lineData(_leftCurrentHistory, const Color(0xFF3F51B5)), _lineData(_rightCurrentHistory, const Color(0xFF8BC34A))],
|
||||
yAxisMax: 100,
|
||||
yAxisMin: 0,
|
||||
@@ -637,14 +638,14 @@ class _RunningStatusPageState extends State<RunningStatusPage> with WidgetsBindi
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"电机温度对比 (°C)",
|
||||
AppLocalizations.of(context).translate('running_status.motor_temp_compare') + " (°C)",
|
||||
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Color(0xFF1677FF)),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
SizedBox(
|
||||
height: 180,
|
||||
child: _styledLineChart(
|
||||
title: "电机温度对比",
|
||||
title: AppLocalizations.of(context).translate('running_status.motor_temp_compare'),
|
||||
lines: [_lineData(_leftTempHistory, const Color(0xFF3F51B5)), _lineData(_rightTempHistory, const Color(0xFF8BC34A))],
|
||||
yAxisMax: 100,
|
||||
yAxisMin: 0,
|
||||
@@ -667,7 +668,7 @@ class _RunningStatusPageState extends State<RunningStatusPage> with WidgetsBindi
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"航向角 (°)",
|
||||
AppLocalizations.of(context).translate('running_status.heading_angle_chart') + " (°)",
|
||||
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Color(0xFF1677FF)),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
@@ -705,9 +706,9 @@ class _RunningStatusPageState extends State<RunningStatusPage> with WidgetsBindi
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 12),
|
||||
child: Text("电机参数", style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
child: Text(AppLocalizations.of(context).translate('running_status.motor_params'), style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
|
||||
),
|
||||
Table(
|
||||
border: TableBorder.all(color: const Color(0xFFE5E5E5)),
|
||||
@@ -715,32 +716,32 @@ class _RunningStatusPageState extends State<RunningStatusPage> with WidgetsBindi
|
||||
children: [
|
||||
TableRow(
|
||||
decoration: const BoxDecoration(color: Color(0xFFF5F5F5)),
|
||||
children: [const SizedBox(), _buildTableCell("左轮", isHeader: true), _buildTableCell("右轮", isHeader: true)],
|
||||
children: [const SizedBox(), _buildTableCell(AppLocalizations.of(context).translate('running_status.left_wheel'), isHeader: true), _buildTableCell(AppLocalizations.of(context).translate('running_status.right_wheel'), isHeader: true)],
|
||||
),
|
||||
TableRow(
|
||||
children: [
|
||||
_buildTableCell("目标速度\n(rpm)"),
|
||||
_buildTableCell(AppLocalizations.of(context).translate('running_status.target_speed') + "\n(rpm)"),
|
||||
_buildTableCell(status.leftTargetSpeed.toStringAsFixed(2)),
|
||||
_buildTableCell(status.rightTargetSpeed.toStringAsFixed(2)),
|
||||
],
|
||||
),
|
||||
TableRow(
|
||||
children: [
|
||||
_buildTableCell("测量速度\n(rpm)"),
|
||||
_buildTableCell(AppLocalizations.of(context).translate('running_status.measure_speed') + "\n(rpm)"),
|
||||
_buildTableCell(status.leftMeasureSpeed.toStringAsFixed(2)),
|
||||
_buildTableCell(status.rightMeasureSpeed.toStringAsFixed(2)),
|
||||
],
|
||||
),
|
||||
TableRow(
|
||||
children: [
|
||||
_buildTableCell("电流(A)"),
|
||||
_buildTableCell(AppLocalizations.of(context).translate('running_status.current') + "(A)"),
|
||||
_buildTableCell(status.leftCurrent.toStringAsFixed(2)),
|
||||
_buildTableCell(status.rightCurrent.toStringAsFixed(2)),
|
||||
],
|
||||
),
|
||||
TableRow(
|
||||
children: [
|
||||
_buildTableCell("电机温度(°C)"),
|
||||
_buildTableCell(AppLocalizations.of(context).translate('running_status.motor_temp') + "(°C)"),
|
||||
_buildTableCell(status.leftMotorTemp.toStringAsFixed(2)),
|
||||
_buildTableCell(status.rightMotorTemp.toStringAsFixed(2)),
|
||||
],
|
||||
@@ -756,9 +757,9 @@ class _RunningStatusPageState extends State<RunningStatusPage> with WidgetsBindi
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 12),
|
||||
child: Text("其他参数", style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
child: Text(AppLocalizations.of(context).translate('running_status.other_params'), style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
|
||||
),
|
||||
Table(
|
||||
border: TableBorder.all(color: const Color(0xFFE5E5E5)),
|
||||
@@ -766,17 +767,17 @@ class _RunningStatusPageState extends State<RunningStatusPage> with WidgetsBindi
|
||||
children: [
|
||||
TableRow(
|
||||
decoration: const BoxDecoration(color: Color(0xFFF5F5F5)),
|
||||
children: [_buildTableCell("名称", isHeader: true), _buildTableCell("数值", isHeader: true)],
|
||||
children: [_buildTableCell(AppLocalizations.of(context).translate('running_status.name'), isHeader: true), _buildTableCell(AppLocalizations.of(context).translate('running_status.value'), isHeader: true)],
|
||||
),
|
||||
TableRow(children: [_buildTableCell("俯仰角(°)"), _buildTableCell(status.pitch.toStringAsFixed(2))]),
|
||||
TableRow(children: [_buildTableCell("翻滚角(°)"), _buildTableCell(status.roll.toStringAsFixed(2))]),
|
||||
TableRow(children: [_buildTableCell("航向角(°)"), _buildTableCell(status.yaw.toStringAsFixed(2))]),
|
||||
TableRow(children: [_buildTableCell("电量(%)"), _buildTableCell(status.battery)]),
|
||||
TableRow(children: [_buildTableCell("芯片温度(°C)"), _buildTableCell(status.chipTemp.toStringAsFixed(2))]),
|
||||
TableRow(children: [_buildTableCell("割刀速度(rpm)"), _buildTableCell(status.knifeCuttingSpeed)]),
|
||||
TableRow(children: [_buildTableCell("控制模式"), _buildTableCell(LocationUtils.parseControlMode(int.parse(status.controlMode)))]),
|
||||
TableRow(children: [_buildTableCell("经度(°)"), _buildTableCell(status.longitude.toStringAsFixed(6))]),
|
||||
TableRow(children: [_buildTableCell("纬度(°)"), _buildTableCell(status.latitude.toStringAsFixed(6))]),
|
||||
TableRow(children: [_buildTableCell(AppLocalizations.of(context).translate('running_status.pitch_angle') + "(°)"), _buildTableCell(status.pitch.toStringAsFixed(2))]),
|
||||
TableRow(children: [_buildTableCell(AppLocalizations.of(context).translate('running_status.roll_angle') + "(°)"), _buildTableCell(status.roll.toStringAsFixed(2))]),
|
||||
TableRow(children: [_buildTableCell(AppLocalizations.of(context).translate('running_status.heading_angle') + "(°)"), _buildTableCell(status.yaw.toStringAsFixed(2))]),
|
||||
TableRow(children: [_buildTableCell(AppLocalizations.of(context).translate('running_status.battery') + "(%)"), _buildTableCell(status.battery)]),
|
||||
TableRow(children: [_buildTableCell(AppLocalizations.of(context).translate('running_status.chip_temp') + "(°C)"), _buildTableCell(status.chipTemp.toStringAsFixed(2))]),
|
||||
TableRow(children: [_buildTableCell(AppLocalizations.of(context).translate('running_status.knife_speed') + "(rpm)"), _buildTableCell(status.knifeCuttingSpeed)]),
|
||||
TableRow(children: [_buildTableCell(AppLocalizations.of(context).translate('running_status.control_mode')), _buildTableCell(AppLocalizations.of(context).translate(_getControlModeKey(int.parse(status.controlMode))))]),
|
||||
TableRow(children: [_buildTableCell(AppLocalizations.of(context).translate('running_status.longitude') + "(°)"), _buildTableCell(status.longitude.toStringAsFixed(6))]),
|
||||
TableRow(children: [_buildTableCell(AppLocalizations.of(context).translate('running_status.latitude') + "(°)"), _buildTableCell(status.latitude.toStringAsFixed(6))]),
|
||||
],
|
||||
),
|
||||
],
|
||||
@@ -823,6 +824,34 @@ class _RunningStatusPageState extends State<RunningStatusPage> with WidgetsBindi
|
||||
);
|
||||
}
|
||||
|
||||
// 辅助方法:根据定位质量值返回翻译键
|
||||
String _getLocationQualityKey(int qualValue) {
|
||||
switch (qualValue) {
|
||||
case 0:
|
||||
return 'running_status.invalid';
|
||||
case 1:
|
||||
return 'common.unknown'; // GPS单点定位 - 使用通用未知
|
||||
case 2:
|
||||
return 'common.unknown'; // DGPS - 使用通用未知
|
||||
case 4:
|
||||
return 'running_status.valid'; // RTK固定解 - 视为有效
|
||||
case 5:
|
||||
return 'running_status.valid'; // RTK浮点解 - 视为有效
|
||||
default:
|
||||
return 'common.unknown';
|
||||
}
|
||||
}
|
||||
|
||||
// 辅助方法:根据控制模式值返回翻译键
|
||||
String _getControlModeKey(int modeValue) {
|
||||
switch (modeValue) {
|
||||
case 3:
|
||||
return 'running_status.remote_control';
|
||||
default:
|
||||
return 'running_status.local_control';
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final deviceState = context.read<DevicesCubit>().state;
|
||||
@@ -851,14 +880,14 @@ class _RunningStatusPageState extends State<RunningStatusPage> with WidgetsBindi
|
||||
String headingStatus = _isDataTimeout ? "--" : "--";
|
||||
|
||||
if (!_isDataTimeout && state is DeviceStatusUpdated) {
|
||||
headingStatus = state.status.headingStatus == 0 ? '未初始化' : '已初始化';
|
||||
headingStatus = state.status.headingStatus == 0 ? AppLocalizations.of(context).translate('running_status.not_initialized') : AppLocalizations.of(context).translate('running_status.initialized');
|
||||
int qualValue = 0;
|
||||
try {
|
||||
qualValue = int.parse(state.status.qual.toString());
|
||||
} catch (e) {
|
||||
qualValue = 0;
|
||||
}
|
||||
qual = LocationUtils.parseLocationQuality(qualValue);
|
||||
qual = AppLocalizations.of(context).translate(_getLocationQualityKey(qualValue));
|
||||
satelliteCnt = state.status.satelliteCnt.toString();
|
||||
|
||||
// 收到新数据,重置超时计时器和状态
|
||||
@@ -881,12 +910,12 @@ class _RunningStatusPageState extends State<RunningStatusPage> with WidgetsBindi
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: Text("航向角状态:$headingStatus", style: const TextStyle(fontSize: 12), maxLines: 1, overflow: TextOverflow.ellipsis),
|
||||
child: Text(AppLocalizations.of(context).translate('running_status.heading_status') + ":$headingStatus", style: const TextStyle(fontSize: 12), maxLines: 1, overflow: TextOverflow.ellipsis),
|
||||
),
|
||||
Expanded(
|
||||
flex: 4,
|
||||
child: Text(
|
||||
"定位质量:$qual",
|
||||
AppLocalizations.of(context).translate('running_status.position_quality') + ":$qual",
|
||||
style: const TextStyle(fontSize: 12),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
@@ -896,7 +925,7 @@ class _RunningStatusPageState extends State<RunningStatusPage> with WidgetsBindi
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Text(
|
||||
"卫星数:$satelliteCnt",
|
||||
AppLocalizations.of(context).translate('running_status.satellite_count') + ":$satelliteCnt",
|
||||
style: const TextStyle(fontSize: 12),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
@@ -922,12 +951,12 @@ class _RunningStatusPageState extends State<RunningStatusPage> with WidgetsBindi
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () => setState(() => _isCardView = true),
|
||||
child: _buildTab("卡片", isActive: _isCardView),
|
||||
child: _buildTab(AppLocalizations.of(context).translate('running_status.card'), isActive: _isCardView),
|
||||
),
|
||||
const SizedBox(width: 24),
|
||||
GestureDetector(
|
||||
onTap: () => setState(() => _isCardView = false),
|
||||
child: _buildTab("图表", isActive: !_isCardView),
|
||||
child: _buildTab(AppLocalizations.of(context).translate('running_status.chart'), isActive: !_isCardView),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -988,9 +1017,9 @@ class _RunningStatusPageState extends State<RunningStatusPage> with WidgetsBindi
|
||||
backgroundColor: const Color(0xFF1677FF),
|
||||
elevation: 4, // 增加阴影,提升层次感
|
||||
centerTitle: true,
|
||||
title: const Text(
|
||||
"设备数据监控",
|
||||
style: TextStyle(
|
||||
title: Text(
|
||||
AppLocalizations.of(context).translate('running_status.title'),
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:maibu_satabot_v2/core/localization/app_localizations.dart';
|
||||
|
||||
class ProductPage extends StatefulWidget {
|
||||
const ProductPage({super.key});
|
||||
@@ -24,25 +25,36 @@ class _ProductPageState extends State<ProductPage> {
|
||||
'整机三色灯显示机器各种状态,包括油量预警、电量预警、直行保持、电控箱温度预警和安全倾角预警等;遥控实现整机操控,包括发动机一键启停、行走操控、速度操控、割盘升降和割刀转速等;遥控显示屏实时显示作业状态,包括作业时间、保养提醒、驱动器情况、作业模式、行驶模式、安全倾角检测、障碍物检测车灯状态、信号强度和遥控器电量等。',
|
||||
];
|
||||
|
||||
// 产品亮点
|
||||
final List<Map<String, String>> starPoints = const [
|
||||
{'title': '整机设计', 'content': '承载式车身、高效大功率行走系统、坦克履型、油电混动、防溜坡系统、主被动溜坡浮动式刀盘、空气辅助式提升机构、自适应割草装置'},
|
||||
{'title': '控制系统', 'content': '车规级控制和电气系统、工业级防干扰遥控系统、车速控制和检测系统、牵引力控制系统、手动和自动行驶操作模式、整机状态液晶显示。'},
|
||||
{'title': '动力系统', 'content': '自适应增程系统、本田顶级发动机、大功率交直流放电功能。'},
|
||||
{'title': '安全配置', 'content': '车身稳定系统、ABS刹车装置、电子防翻滚功能、倾斜保护功能、急速、坠落和翻滚停机功能、无操作自动关机。'},
|
||||
{'title': '智能功能', 'content': '直行保持功能、防碰撞功能、碰撞停机功能。'},
|
||||
{'title': '扩展功能', 'content': '路径规划、巡检探测、物联控制。'},
|
||||
];
|
||||
|
||||
// 应用场景
|
||||
final List<Map<String, String>> sceneList = const [
|
||||
{'img': 'assets/images/scene4.png', 'title': '果园林业'},
|
||||
{'img': 'assets/images/scene1.png', 'title': '光伏储能'},
|
||||
{'img': 'assets/images/scene2.png', 'title': '公路路政'},
|
||||
{'img': 'assets/images/scene3.png', 'title': '农业生产'},
|
||||
{'img': 'assets/images/scene5.png', 'title': '市政河堤'},
|
||||
{'img': 'assets/images/scene6.png', 'title': '机场场所'},
|
||||
];
|
||||
Widget _buildScene() {
|
||||
final loc = AppLocalizations.of(context);
|
||||
final sceneListLocalized = [
|
||||
{'img': 'assets/images/scene4.png', 'title': loc.translate('product.scenes.orchard')},
|
||||
{'img': 'assets/images/scene1.png', 'title': loc.translate('product.scenes.photovoltaic')},
|
||||
{'img': 'assets/images/scene2.png', 'title': loc.translate('product.scenes.highway')},
|
||||
{'img': 'assets/images/scene3.png', 'title': loc.translate('product.scenes.agriculture')},
|
||||
{'img': 'assets/images/scene5.png', 'title': loc.translate('product.scenes.municipal')},
|
||||
{'img': 'assets/images/scene6.png', 'title': loc.translate('product.scenes.airport')},
|
||||
];
|
||||
|
||||
return GridView.builder(
|
||||
padding: const EdgeInsets.all(16),
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2, crossAxisSpacing: 12, mainAxisSpacing: 12, childAspectRatio: 0.85),
|
||||
itemCount: sceneListLocalized.length,
|
||||
itemBuilder: (context, index) {
|
||||
final item = sceneListLocalized[index];
|
||||
return Container(
|
||||
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(12), boxShadow: [BoxShadow(color: Colors.black12, blurRadius: 4)]),
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(child: ClipRRect(borderRadius: const BorderRadius.vertical(top: Radius.circular(12)), child: Image.asset(item['img']!, fit: BoxFit.cover, width: double.infinity))),
|
||||
Padding(padding: const EdgeInsets.all(8), child: Text(item['title']!, textAlign: TextAlign.center, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500))),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// 产品参数 markdown
|
||||
final String productParamMd = '''
|
||||
@@ -107,11 +119,12 @@ class _ProductPageState extends State<ProductPage> {
|
||||
}
|
||||
|
||||
Widget _buildHeader() {
|
||||
String title = '产品介绍';
|
||||
if (currentPage == 1) title = '产品亮点';
|
||||
if (currentPage == 2) title = '功能介绍';
|
||||
if (currentPage == 3) title = '产品参数';
|
||||
if (currentPage == 4) title = '应用场景';
|
||||
final loc = AppLocalizations.of(context);
|
||||
String title = loc.translate('product.intro');
|
||||
if (currentPage == 1) title = loc.translate('product.highlights');
|
||||
if (currentPage == 2) title = loc.translate('product.functions');
|
||||
if (currentPage == 3) title = loc.translate('product.parameters');
|
||||
if (currentPage == 4) title = loc.translate('product.scenes');
|
||||
|
||||
return Container(
|
||||
padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top + 8, bottom: 12, left: 4, right: 4),
|
||||
@@ -161,13 +174,14 @@ class _ProductPageState extends State<ProductPage> {
|
||||
|
||||
// 主菜单(卡片样式)
|
||||
Widget _buildMainMenu() {
|
||||
final loc = AppLocalizations.of(context);
|
||||
return ListView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
children: [
|
||||
_menuItem('产品亮点', Icons.highlight_outlined, 1),
|
||||
_menuItem('功能介绍', Icons.widgets_outlined, 2),
|
||||
_menuItem('产品参数', Icons.table_chart_outlined, 3),
|
||||
_menuItem('应用场景', Icons.place_outlined, 4),
|
||||
_menuItem(loc.translate('product.highlights'), Icons.highlight_outlined, 1),
|
||||
_menuItem(loc.translate('product.functions'), Icons.widgets_outlined, 2),
|
||||
_menuItem(loc.translate('product.parameters'), Icons.table_chart_outlined, 3),
|
||||
_menuItem(loc.translate('product.scenes'), Icons.place_outlined, 4),
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -201,10 +215,20 @@ class _ProductPageState extends State<ProductPage> {
|
||||
|
||||
// 产品亮点(卡片+主题色)
|
||||
Widget _buildStarPoint() {
|
||||
final loc = AppLocalizations.of(context);
|
||||
final starPointsLocalized = [
|
||||
{'title': loc.translate('product.star_points.design.title'), 'content': loc.translate('product.star_points.design.content')},
|
||||
{'title': loc.translate('product.star_points.control.title'), 'content': loc.translate('product.star_points.control.content')},
|
||||
{'title': loc.translate('product.star_points.power.title'), 'content': loc.translate('product.star_points.power.content')},
|
||||
{'title': loc.translate('product.star_points.safety.title'), 'content': loc.translate('product.star_points.safety.content')},
|
||||
{'title': loc.translate('product.star_points.smart.title'), 'content': loc.translate('product.star_points.smart.content')},
|
||||
{'title': loc.translate('product.star_points.expand.title'), 'content': loc.translate('product.star_points.expand.content')},
|
||||
];
|
||||
|
||||
return ListView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
children: [
|
||||
...starPoints.map((item) {
|
||||
...starPointsLocalized.map((item) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: 10),
|
||||
padding: const EdgeInsets.all(16),
|
||||
@@ -421,34 +445,4 @@ class _ProductPageState extends State<ProductPage> {
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// 应用场景(卡片网格)
|
||||
Widget _buildScene() {
|
||||
return GridView.builder(
|
||||
padding: const EdgeInsets.all(12),
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2, mainAxisSpacing: 10, crossAxisSpacing: 10, childAspectRatio: 0.9),
|
||||
itemCount: sceneList.length,
|
||||
itemBuilder: (ctx, index) {
|
||||
final item = sceneList[index];
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: [BoxShadow(color: Colors.black12, blurRadius: 4)],
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Image.asset(item['img']!, height: 100, fit: BoxFit.cover),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Text(item['title']!, style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w500)),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:maibu_satabot_v2/core/localization/app_localizations.dart';
|
||||
|
||||
class RouteDirectionPanel extends StatefulWidget {
|
||||
// 接收外部传入的初始值
|
||||
@@ -8,6 +9,10 @@ class RouteDirectionPanel extends StatefulWidget {
|
||||
final ValueChanged<Map<String, dynamic>>? onValueChanged;
|
||||
// 取消/关闭面板回调(保留)
|
||||
final VoidCallback? onCancel;
|
||||
// 多语言支持
|
||||
final String? titleText;
|
||||
final String? optimalHeadingText;
|
||||
final String? routeDirectionText;
|
||||
|
||||
const RouteDirectionPanel({
|
||||
super.key,
|
||||
@@ -15,6 +20,9 @@ class RouteDirectionPanel extends StatefulWidget {
|
||||
this.initialDirection = 0.0, // 默认方向0度
|
||||
this.onValueChanged, // 实时值变更回调
|
||||
this.onCancel,
|
||||
this.titleText,
|
||||
this.optimalHeadingText,
|
||||
this.routeDirectionText,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -65,9 +73,9 @@ class _RouteDirectionPanelState extends State<RouteDirectionPanel> {
|
||||
},
|
||||
child: const Icon(Icons.arrow_back_ios, color: Colors.black87, size: 20),
|
||||
),
|
||||
const Text(
|
||||
'航线方向',
|
||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: Colors.black87),
|
||||
Text(
|
||||
widget.titleText ?? '航线方向',
|
||||
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: Colors.black87),
|
||||
),
|
||||
const SizedBox(width: 24), // 占位保持标题居中
|
||||
],
|
||||
@@ -78,7 +86,7 @@ class _RouteDirectionPanelState extends State<RouteDirectionPanel> {
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Text('最优航向', style: TextStyle(fontSize: 16, color: Colors.black87)),
|
||||
Text(widget.optimalHeadingText ?? '最优航向', style: const TextStyle(fontSize: 16, color: Colors.black87)),
|
||||
Switch(
|
||||
value: _optimalHeading,
|
||||
activeColor: const Color(0xFF00C853),
|
||||
@@ -101,7 +109,7 @@ class _RouteDirectionPanelState extends State<RouteDirectionPanel> {
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Text('航线方向', style: TextStyle(fontSize: 16, color: Colors.black87)),
|
||||
Text(widget.routeDirectionText ?? '航线方向', style: const TextStyle(fontSize: 16, color: Colors.black87)),
|
||||
Text(
|
||||
'${_direction.toStringAsFixed(0)}°',
|
||||
style: TextStyle(fontSize: 16, color: _optimalHeading ? Colors.grey : Colors.black87, fontWeight: FontWeight.w500),
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:maibu_satabot_v2/core/localization/app_localizations.dart';
|
||||
import 'package:maibu_satabot_v2/features/devices/domain/entities/device_entity.dart';
|
||||
import 'package:maibu_satabot_v2/features/home/presentation/widgets/common/commonFn.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
@@ -90,7 +91,7 @@ class ImmersionHeader extends StatelessWidget {
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// 3. 状态标签也紧跟其后
|
||||
_buildStatusTag(device.isOnline),
|
||||
_buildStatusTag(device.isOnline, context),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -121,7 +122,7 @@ class ImmersionHeader extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStatusTag(bool isOnline) {
|
||||
Widget _buildStatusTag(bool isOnline, BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
@@ -135,7 +136,11 @@ class ImmersionHeader extends StatelessWidget {
|
||||
children: [
|
||||
CircleAvatar(radius: 3, backgroundColor: isOnline ? Colors.green : Colors.grey),
|
||||
const SizedBox(width: 6),
|
||||
Text(isOnline ? "在线" : "离线", style: const TextStyle(fontSize: 12, color: Colors.black54)),
|
||||
Text(
|
||||
isOnline ? AppLocalizations.of(context).translate('home.device_online') : AppLocalizations.of(context).translate('home.device_offline'),
|
||||
style: const TextStyle(fontSize: 12, color: Colors.black54),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -199,7 +204,11 @@ class ImmersionHeader extends StatelessWidget {
|
||||
height: 5,
|
||||
decoration: BoxDecoration(color: Colors.grey[300], borderRadius: BorderRadius.circular(10)),
|
||||
),
|
||||
const Text("切换设备", style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
|
||||
Text(
|
||||
AppLocalizations.of(modalContext).translate('home.switch_device'),
|
||||
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Container(height: 0.5, margin: const EdgeInsets.symmetric(horizontal: 20), color: Colors.grey.withOpacity(0.1)),
|
||||
|
||||
@@ -216,7 +225,7 @@ class ImmersionHeader extends StatelessWidget {
|
||||
children: [
|
||||
const CircularProgressIndicator(color: Colors.blue),
|
||||
const SizedBox(height: 16),
|
||||
Text("加载中...", style: TextStyle(color: Colors.grey[600])),
|
||||
Text(AppLocalizations.of(context).translate('home.loading'), style: TextStyle(color: Colors.grey[600])),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -224,13 +233,13 @@ class ImmersionHeader extends StatelessWidget {
|
||||
|
||||
// 2. 列表展示
|
||||
if (state.devices.isEmpty) {
|
||||
return const Center(
|
||||
return 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)),
|
||||
const Icon(Icons.description_outlined, size: 60, color: Colors.grey),
|
||||
const SizedBox(height: 16),
|
||||
Text(AppLocalizations.of(context).translate('home.no_devices'), style: const TextStyle(fontSize: 16, color: Colors.grey)),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -288,7 +297,7 @@ class ImmersionHeader extends StatelessWidget {
|
||||
const SizedBox(width: 4),
|
||||
Flexible(
|
||||
child: Text(
|
||||
(device.deviceAlias?.trim() ?? '').isEmpty ? '未知设备' : device.deviceAlias!,
|
||||
(device.deviceAlias?.trim() ?? '').isEmpty ? AppLocalizations.of(context).translate('home.unknown_device') : device.deviceAlias!,
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
// 关键属性:处理文本溢出
|
||||
maxLines: 2, // 最多显示2行
|
||||
@@ -299,7 +308,11 @@ class ImmersionHeader extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
const Text("点击查看详情", style: TextStyle(color: Colors.grey, fontSize: 12)),
|
||||
Text(
|
||||
AppLocalizations.of(context).translate('home.click_to_view'),
|
||||
style: const TextStyle(color: Colors.grey, fontSize: 12),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -319,8 +332,8 @@ class ImmersionHeader extends StatelessWidget {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
},
|
||||
text: isSelected ? "使用中" : "切换",
|
||||
width: 80,
|
||||
text: isSelected ? AppLocalizations.of(context).translate('home.in_use') : AppLocalizations.of(context).translate('home.switch'),
|
||||
width: 90,
|
||||
height: 30,
|
||||
fontSize: 14,
|
||||
// 如果是当前设备,按钮颜色变灰
|
||||
@@ -333,8 +346,8 @@ class ImmersionHeader extends StatelessWidget {
|
||||
print("通过按钮进入详情,设备名称:$device");
|
||||
},
|
||||
|
||||
text: "详情",
|
||||
width: 80,
|
||||
text: AppLocalizations.of(context).translate('home.details'),
|
||||
width: 90,
|
||||
height: 30,
|
||||
fontSize: 14,
|
||||
backgroundColor: Colors.white,
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:flutter_map/flutter_map.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:maibu_satabot_v2/core/localization/app_localizations.dart';
|
||||
|
||||
import 'CoordTransform/coord_transform.dart';
|
||||
|
||||
@@ -47,7 +48,7 @@ class _AmapFlutterMapPageState extends State<AmapFlutterMapPage> {
|
||||
if (status.isDenied) {
|
||||
if (!_isDisposed) { // 防护:页面没销毁才弹提示
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('定位权限被拒绝,无法获取当前位置')),
|
||||
SnackBar(content: Text(AppLocalizations.of(context).translate('route_planning.location_permission_denied'))),
|
||||
);
|
||||
setState(() { _isLoading = false; });
|
||||
}
|
||||
@@ -58,9 +59,9 @@ class _AmapFlutterMapPageState extends State<AmapFlutterMapPage> {
|
||||
if (status.isPermanentlyDenied) {
|
||||
if (!_isDisposed) { // 防护:页面没销毁才弹提示
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('定位权限被永久拒绝,请去设置开启'),
|
||||
action: SnackBarAction(label: '去设置', onPressed: openAppSettings),
|
||||
SnackBar(
|
||||
content: Text(AppLocalizations.of(context).translate('route_planning.location_permanently_denied')),
|
||||
action: SnackBarAction(label: AppLocalizations.of(context).translate('route_planning.go_to_settings'), onPressed: openAppSettings),
|
||||
),
|
||||
);
|
||||
setState(() { _isLoading = false; });
|
||||
@@ -81,7 +82,7 @@ class _AmapFlutterMapPageState extends State<AmapFlutterMapPage> {
|
||||
if (!serviceEnabled) {
|
||||
if (!_isDisposed) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('定位服务未启用,请开启定位服务')),
|
||||
SnackBar(content: Text(AppLocalizations.of(context).translate('route_planning.location_service_disabled'))),
|
||||
);
|
||||
setState(() { _isLoading = false; });
|
||||
}
|
||||
@@ -118,9 +119,9 @@ class _AmapFlutterMapPageState extends State<AmapFlutterMapPage> {
|
||||
setState(() { _isLoading = false; });
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('定位失败: $e'),
|
||||
content: Text('${AppLocalizations.of(context).translate('route_planning.location_failed')}: $e'),
|
||||
action: SnackBarAction(
|
||||
label: '重试',
|
||||
label: AppLocalizations.of(context).translate('route_planning.retry'),
|
||||
onPressed: _checkAndGetLocation,
|
||||
),
|
||||
),
|
||||
@@ -134,12 +135,12 @@ class _AmapFlutterMapPageState extends State<AmapFlutterMapPage> {
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('路径规划'),
|
||||
title: Text(AppLocalizations.of(context).translate('route_planning.title')),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.my_location),
|
||||
onPressed: _checkAndGetLocation,
|
||||
tooltip: '定位到我的位置',
|
||||
tooltip: AppLocalizations.of(context).translate('route_planning.my_location'),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -18,6 +18,7 @@ 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/consts/tcp_consts.dart';
|
||||
import 'package:maibu_satabot_v2/core/di/injection.dart';
|
||||
import 'package:maibu_satabot_v2/core/localization/app_localizations.dart';
|
||||
import 'package:maibu_satabot_v2/core/network/net_message_dispatcher.dart';
|
||||
import 'package:maibu_satabot_v2/core/network/protocol_decoder.dart';
|
||||
import 'package:maibu_satabot_v2/core/router/route_paths.dart';
|
||||
@@ -167,9 +168,9 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
|
||||
|
||||
// 所有可用的场站列表(根据你的实际瓦片目录配置)
|
||||
final List<DJIStation> _stationList = [
|
||||
const DJIStation(name: 'liaoning', displayName: '一号场站'),
|
||||
const DJIStation(name: 'station_b', displayName: '二号场站'),
|
||||
const DJIStation(name: 'station_c', displayName: '三号场站'),
|
||||
const DJIStation(name: 'liaoning', displayName: 'station_1'),
|
||||
const DJIStation(name: 'station_b', displayName: 'station_2'),
|
||||
const DJIStation(name: 'station_c', displayName: 'station_3'),
|
||||
// 新增场站只需在这里添加,无需修改其他逻辑
|
||||
];
|
||||
|
||||
@@ -591,20 +592,21 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
|
||||
}
|
||||
|
||||
void _showSavePlotDialog() {
|
||||
final loc = AppLocalizations.of(context);
|
||||
showInputConfirmDialog(
|
||||
context: context,
|
||||
title: '保存地块', // 自定义标题
|
||||
hintText: '请输入地块名称(如:北地块、一号田)', // 自定义输入提示
|
||||
labelText: '地块名称', // 自定义输入框标签
|
||||
confirmText: '保存', // 确认按钮文字
|
||||
cancelText: '取消', // 取消按钮文字
|
||||
title: loc.translate('route_planning.save_plot'), // 自定义标题
|
||||
hintText: loc.translate('route_planning.plot_name_hint'), // 自定义输入提示
|
||||
labelText: loc.translate('route_planning.plot_name_label'), // 自定义输入框标签
|
||||
confirmText: loc.translate('route_planning.save'), // 确认按钮文字
|
||||
cancelText: loc.translate('route_planning.cancel'), // 取消按钮文字
|
||||
// 输入校验器(可选)
|
||||
inputValidator: (inputText) {
|
||||
if (inputText.isEmpty) {
|
||||
return '地块名称不能为空!';
|
||||
return loc.translate('route_planning.plot_name_empty');
|
||||
}
|
||||
if (inputText.length > 20) {
|
||||
return '地块名称不能超过20个字符!';
|
||||
return loc.translate('route_planning.plot_name_too_long');
|
||||
}
|
||||
return null; // 校验通过
|
||||
},
|
||||
@@ -629,7 +631,7 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
|
||||
} else {
|
||||
// 截图失败时,允许空图片保存(可根据业务调整为强制失败)
|
||||
_savePlotData(plotName, null);
|
||||
_showPageToast(message: "地块保存成功,但地图截图生成失败!", type: ToastType.warn);
|
||||
_showPageToast(message: loc.translate('route_planning.save_success') + "," + "但地图截图生成失败!", type: ToastType.warn);
|
||||
|
||||
//ToastUtils.showWarn(context, '地块保存成功,但地图截图生成失败!');
|
||||
//ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('地块保存成功,但地图截图生成失败!'), backgroundColor: Colors.amber));
|
||||
@@ -683,10 +685,11 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
|
||||
}
|
||||
|
||||
Future<void> _savePlotData(String plotName, String? imgBase64) async {
|
||||
final loc = AppLocalizations.of(context);
|
||||
// 1. 获取用户ID
|
||||
final userId = context.read<AppUserCubit>().state.user?.userId ?? "";
|
||||
if (userId.isEmpty) {
|
||||
_showPageToast(message: "用户ID为空,无法保存!", type: ToastType.error);
|
||||
_showPageToast(message: loc.translate('route_planning.user_id_empty'), type: ToastType.error);
|
||||
|
||||
//ToastUtils.showError(context, '用户ID为空,无法保存!');
|
||||
return;
|
||||
@@ -756,7 +759,7 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
|
||||
|
||||
// 7. 发送请求
|
||||
try {
|
||||
_showPageToast(message: "正在保存地块「$plotName」...", type: ToastType.loading);
|
||||
_showPageToast(message: loc.translate('route_planning.saving') + "「$plotName」...", type: ToastType.loading);
|
||||
|
||||
//ToastUtils.showLoading(context, '正在保存地块「$plotName」...');
|
||||
//ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('正在保存地块...'), backgroundColor: Colors.blue));
|
||||
@@ -765,7 +768,7 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
|
||||
final String responseBody = await response.stream.bytesToString();
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
_showPageToast(message: "地块「$plotName」保存成功!", type: ToastType.success);
|
||||
_showPageToast(message: loc.translate('route_planning.save_success') + "「$plotName」", type: ToastType.success);
|
||||
|
||||
//ToastUtils.showSuccess(context, '地块「$plotName」保存成功!');
|
||||
_clearLocalData();
|
||||
@@ -787,7 +790,7 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('保存失败: $e');
|
||||
_showPageToast(message: "保存失败: ${e.toString()}", type: ToastType.error);
|
||||
_showPageToast(message: loc.translate('route_planning.save_failed') + ": ${e.toString()}", type: ToastType.error);
|
||||
|
||||
//ToastUtils.showError(context, '保存失败: ${e.toString()}');
|
||||
//ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('保存失败: ${e.toString()}'), backgroundColor: Colors.red));
|
||||
@@ -822,7 +825,8 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
|
||||
}
|
||||
_showPageToast(message: "已撤回作业区域最后一个点,剩余${_markedPoints.length}个点", type: ToastType.info);
|
||||
} else {
|
||||
_showPageToast(message: "暂无作业区域点可撤回", type: ToastType.info);
|
||||
final loc = AppLocalizations.of(context);
|
||||
_showPageToast(message: loc.translate('route_planning.no_work_points'), type: ToastType.info);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -884,7 +888,8 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
|
||||
_isWorkAreaCompleted = false;
|
||||
_saveBoxOpen = false;
|
||||
typedPathList.clear();
|
||||
_showPageToast(message: "已清空所有作业区域点和路径", type: ToastType.success);
|
||||
final loc = AppLocalizations.of(context);
|
||||
_showPageToast(message: loc.translate('route_planning.clear_work_area'), type: ToastType.success);
|
||||
//ToastUtils.showSuccess(context, '已清空所有作业区域点和路径');
|
||||
|
||||
//ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('已清空所有作业区域点和路径')));
|
||||
@@ -892,7 +897,8 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
|
||||
// 2. 空洞模式:清空所有空洞 + 恢复UI + 重新生成路径
|
||||
else if (_currentAreaMode == AreaMode.obstacle) {
|
||||
if (_obstacleHoles.isEmpty && _currentObstaclePoints.isEmpty) {
|
||||
_showPageToast(message: "暂无障碍物可删除", type: ToastType.info);
|
||||
final loc = AppLocalizations.of(context);
|
||||
_showPageToast(message: loc.translate('route_planning.no_obstacles'), type: ToastType.info);
|
||||
|
||||
//ToastUtils.showInfo(context, '暂无障碍物可删除');
|
||||
return;
|
||||
@@ -905,7 +911,8 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
|
||||
if (_isWorkAreaCompleted) {
|
||||
_generatePath(showTips: false);
|
||||
}
|
||||
_showPageToast(message: "已清空所有障碍物", type: ToastType.success);
|
||||
final loc = AppLocalizations.of(context);
|
||||
_showPageToast(message: loc.translate('route_planning.clear_obstacles'), type: ToastType.success);
|
||||
//ToastUtils.showSuccess(context, '已清空所有障碍物');
|
||||
}
|
||||
});
|
||||
@@ -1032,11 +1039,11 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
|
||||
await _saveDataToLocal();
|
||||
|
||||
if (mounted) {
|
||||
_showPageToast(message: "已清除所有缓存,恢复初始状态", type: ToastType.success);
|
||||
_showPageToast(message: AppLocalizations.of(context).translate('route_planning.clear_cache_success'), type: ToastType.success);
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
_showPageToast(message: "刷新失败:${e.toString().substring(0, 50)}", type: ToastType.error);
|
||||
_showPageToast(message: "${AppLocalizations.of(context).translate('route_planning.refresh_failed')}:${e.toString().substring(0, 50)}", type: ToastType.error);
|
||||
}
|
||||
} finally {
|
||||
if (mounted) {
|
||||
@@ -1861,7 +1868,7 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text('作业模式:$workMode', style: const TextStyle(fontSize: 14, color: Colors.grey)),
|
||||
Text('${AppLocalizations.of(context).translate('route_planning.work_mode')}:$workMode', style: const TextStyle(fontSize: 14, color: Colors.grey)),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -1982,7 +1989,7 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
|
||||
),
|
||||
child: state.isLoading
|
||||
? const SizedBox(width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white))
|
||||
: const Text('开始作业', style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold)),
|
||||
: Text(AppLocalizations.of(context).translate('route_planning.start_work'), style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold)),
|
||||
),
|
||||
)
|
||||
: Row(
|
||||
@@ -2638,6 +2645,9 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
|
||||
child: RouteDirectionPanel(
|
||||
initialOptimalHeading: true,
|
||||
initialDirection: 0.0,
|
||||
titleText: AppLocalizations.of(context).translate('route_planning.route_direction'),
|
||||
optimalHeadingText: AppLocalizations.of(context).translate('route_planning.optimal_heading'),
|
||||
routeDirectionText: AppLocalizations.of(context).translate('route_planning.route_direction'),
|
||||
onValueChanged: (result) {
|
||||
_angle = result['optimalHeading'] == true ? -1 : result['direction'];
|
||||
|
||||
@@ -2687,7 +2697,7 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'地块列表(共${plotList.length}条)',
|
||||
AppLocalizations.of(context).translate('route_planning.plot_list_title').replaceAll('%d', plotList.length.toString()),
|
||||
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Colors.black87),
|
||||
),
|
||||
IconButton(
|
||||
@@ -2704,13 +2714,13 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
|
||||
const Divider(height: 1, color: Colors.grey),
|
||||
Expanded(
|
||||
child: plotList.isEmpty
|
||||
? const Center(
|
||||
? Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(Icons.inbox_outlined, color: Colors.grey, size: 48),
|
||||
SizedBox(height: 16),
|
||||
Text('暂无地块数据', style: TextStyle(color: Colors.grey, fontSize: 16)),
|
||||
const Icon(Icons.inbox_outlined, color: Colors.grey, size: 48),
|
||||
const SizedBox(height: 16),
|
||||
Text(AppLocalizations.of(context).translate('route_planning.no_plot_data'), style: const TextStyle(color: Colors.grey, fontSize: 16)),
|
||||
],
|
||||
),
|
||||
)
|
||||
@@ -2750,12 +2760,12 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
boxShadow: [BoxShadow(color: Colors.black12, blurRadius: 4, offset: const Offset(0, 2))],
|
||||
),
|
||||
child: const Row(
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.warning_amber_rounded, color: Colors.white, size: 20),
|
||||
SizedBox(width: 8),
|
||||
const Icon(Icons.warning_amber_rounded, color: Colors.white, size: 20),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text('航向角未初始化,无法开始作业', style: TextStyle(color: Colors.white, fontSize: 14)),
|
||||
child: Text(AppLocalizations.of(context).translate('route_planning.heading_not_init'), style: const TextStyle(color: Colors.white, fontSize: 14)),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -2775,12 +2785,12 @@ class _MapPageEnterpriseState extends State<MapPageEnterprise> {
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
boxShadow: [BoxShadow(color: Colors.black12, blurRadius: 4, offset: const Offset(0, 2))],
|
||||
),
|
||||
child: const Row(
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.error_rounded, color: Colors.white, size: 20),
|
||||
SizedBox(width: 8),
|
||||
const Icon(Icons.error_rounded, color: Colors.white, size: 20),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text('请切换到远程模式后再开始作业', style: TextStyle(color: Colors.white, fontSize: 14)),
|
||||
child: Text(AppLocalizations.of(context).translate('route_planning.switch_remote_mode'), style: const TextStyle(color: Colors.white, fontSize: 14)),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:maibu_satabot_v2/core/localization/app_localizations.dart';
|
||||
import 'package:maibu_satabot_v2/core/router/route_paths.dart';
|
||||
import 'package:maibu_satabot_v2/features/home/presentation/widgets/common/enum.dart';
|
||||
|
||||
@@ -63,14 +64,23 @@ class _VerticalFloatMenuState extends State<VerticalFloatMenu> {
|
||||
_selectedWorkModeIn = widget.selectedWorkModeIn;
|
||||
}
|
||||
|
||||
final List<Map<String, dynamic>> _menuItems = const [
|
||||
{"icon": Icons.crop_free, "name": "圈地"},
|
||||
{"icon": Icons.format_list_bulleted, "name": "列表"},
|
||||
{"icon": Icons.train, "name": "场站"},
|
||||
{"icon": Icons.navigation, "name": "定位"},
|
||||
{"icon": Icons.refresh, "name": "刷新"},
|
||||
{"icon": Icons.videocam, "name": "监控"},
|
||||
];
|
||||
final List<Map<String, dynamic>> _menuItems = [];
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
// 在依赖变化时重新初始化菜单(支持语言切换)
|
||||
final loc = AppLocalizations.of(context);
|
||||
_menuItems.clear();
|
||||
_menuItems.addAll([
|
||||
{"icon": Icons.crop_free, "name": loc.translate('route_planning.mark_land')},
|
||||
{"icon": Icons.format_list_bulleted, "name": loc.translate('route_planning.list')},
|
||||
{"icon": Icons.train, "name": loc.translate('route_planning.station')},
|
||||
{"icon": Icons.navigation, "name": loc.translate('route_planning.location')},
|
||||
{"icon": Icons.refresh, "name": loc.translate('route_planning.refresh')},
|
||||
{"icon": Icons.videocam, "name": loc.translate('route_planning.monitoring')},
|
||||
]);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -168,37 +178,47 @@ class _VerticalFloatMenuState extends State<VerticalFloatMenu> {
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Text("选择作业模式", style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
|
||||
const SizedBox(height: 20),
|
||||
_modeItem(
|
||||
title: "弓字模式",
|
||||
subtitle: "标准全覆盖路径规划",
|
||||
isSelected: selectedMode == WorkMode.bow,
|
||||
onTap: () {
|
||||
setState(() {
|
||||
selectedMode = WorkMode.bow;
|
||||
});
|
||||
Builder(
|
||||
builder: (dialogContext) {
|
||||
final loc = AppLocalizations.of(dialogContext);
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(loc.translate('route_planning.select_work_mode'), style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
|
||||
const SizedBox(height: 20),
|
||||
_modeItem(
|
||||
title: loc.translate('route_planning.bow_mode'),
|
||||
subtitle: loc.translate('route_planning.bow_mode_subtitle'),
|
||||
isSelected: selectedMode == WorkMode.bow,
|
||||
onTap: () {
|
||||
setState(() {
|
||||
selectedMode = WorkMode.bow;
|
||||
});
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
_modeItem(
|
||||
title: loc.translate('route_planning.custom_mode'),
|
||||
subtitle: loc.translate('route_planning.custom_mode_subtitle'),
|
||||
isSelected: selectedMode == WorkMode.custom,
|
||||
onTap: () {
|
||||
setState(() {
|
||||
selectedMode = WorkMode.custom;
|
||||
});
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton(onPressed: () => Navigator.pop(context), child: Text(loc.translate('common.cancel'))),
|
||||
ElevatedButton(onPressed: () => Navigator.pop(context, selectedMode), child: Text(loc.translate('common.confirm'))),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
_modeItem(
|
||||
title: "自定义模式",
|
||||
subtitle: "手动设定作业区域",
|
||||
isSelected: selectedMode == WorkMode.custom,
|
||||
onTap: () {
|
||||
setState(() {
|
||||
selectedMode = WorkMode.custom;
|
||||
});
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton(onPressed: () => Navigator.pop(context), child: const Text("取消")),
|
||||
ElevatedButton(onPressed: () => Navigator.pop(context, selectedMode), child: const Text("确定")),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:maibu_satabot_v2/core/localization/app_localizations.dart';
|
||||
import 'package:maibu_satabot_v2/core/router/route_paths.dart';
|
||||
|
||||
class QuickActionsGrid extends StatelessWidget {
|
||||
@@ -12,20 +13,23 @@ class QuickActionsGrid extends StatelessWidget {
|
||||
child: Row(
|
||||
children: [
|
||||
_buildActionItem(
|
||||
context: context,
|
||||
icon: Icons.videogame_asset_outlined,
|
||||
label: '远程遥控',
|
||||
labelKey: 'home.remote_control',
|
||||
iconColor: Colors.blue,
|
||||
onTap: () => context.push(RoutePaths.remoteControl),
|
||||
),
|
||||
_buildActionItem(
|
||||
context: context,
|
||||
icon: Icons.near_me_outlined,
|
||||
label: '路径规划',
|
||||
labelKey: 'home.route_plan',
|
||||
iconColor: Colors.purple,
|
||||
onTap: () => context.push(RoutePaths.routePlan),
|
||||
),
|
||||
_buildActionItem(
|
||||
context: context,
|
||||
icon: Icons.insights_rounded,
|
||||
label: '机器状态',
|
||||
labelKey: 'home.machine_status',
|
||||
iconColor: Colors.orange,
|
||||
onTap: () => context.push(RoutePaths.runningStatus),
|
||||
),
|
||||
@@ -36,8 +40,9 @@ class QuickActionsGrid extends StatelessWidget {
|
||||
|
||||
// 💡 改造后的构建函数,支持命名参数和点击事件
|
||||
Widget _buildActionItem({
|
||||
required BuildContext context,
|
||||
required IconData icon,
|
||||
required String label,
|
||||
required String labelKey,
|
||||
required Color iconColor,
|
||||
required VoidCallback onTap,
|
||||
}) {
|
||||
@@ -65,7 +70,7 @@ class QuickActionsGrid extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
label,
|
||||
AppLocalizations.of(context).translate(labelKey),
|
||||
style: const TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w500,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:maibu_satabot_v2/core/localization/app_localizations.dart';
|
||||
import 'package:maibu_satabot_v2/features/home/presentation/widgets/common/enum.dart';
|
||||
import 'package:maibu_satabot_v2/features/home/presentation/widgets/map/testmap_pages.dart';
|
||||
|
||||
@@ -95,8 +96,9 @@ class _BottomOperationPanelState extends State<BottomOperationPanel> {
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
final loc = AppLocalizations.of(context);
|
||||
setState(() {
|
||||
_currentTab = '地块'; // 更新选中状态
|
||||
_currentTab = loc.translate('route_planning.land'); // 更新选中状态
|
||||
});
|
||||
debugPrint('切换到地块标签');
|
||||
widget.onLandTap?.call();
|
||||
@@ -104,9 +106,9 @@ class _BottomOperationPanelState extends State<BottomOperationPanel> {
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
// 选中地块时背景为白色,否则透明
|
||||
decoration: BoxDecoration(color: _currentTab == '地块' ? Colors.white : Colors.transparent, borderRadius: BorderRadius.circular(8)),
|
||||
child: const Center(
|
||||
child: Text('地块', style: TextStyle(color: Colors.black87, fontSize: 16)),
|
||||
decoration: BoxDecoration(color: _currentTab == AppLocalizations.of(context).translate('route_planning.land') ? Colors.white : Colors.transparent, borderRadius: BorderRadius.circular(8)),
|
||||
child: Center(
|
||||
child: Text(AppLocalizations.of(context).translate('route_planning.land'), style: TextStyle(color: Colors.black87, fontSize: 16)),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -114,8 +116,9 @@ class _BottomOperationPanelState extends State<BottomOperationPanel> {
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
final loc = AppLocalizations.of(context);
|
||||
setState(() {
|
||||
_currentTab = '航线'; // 更新选中状态
|
||||
_currentTab = loc.translate('route_planning.route'); // 更新选中状态
|
||||
});
|
||||
debugPrint('切换到航线标签');
|
||||
widget.onRouteTap?.call();
|
||||
@@ -123,9 +126,9 @@ class _BottomOperationPanelState extends State<BottomOperationPanel> {
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
// 选中航线时背景为白色,否则透明
|
||||
decoration: BoxDecoration(color: _currentTab == '航线' ? Colors.white : Colors.transparent, borderRadius: BorderRadius.circular(8)),
|
||||
child: const Center(
|
||||
child: Text('航线', style: TextStyle(color: Colors.black87, fontSize: 16)),
|
||||
decoration: BoxDecoration(color: _currentTab == AppLocalizations.of(context).translate('route_planning.route') ? Colors.white : Colors.transparent, borderRadius: BorderRadius.circular(8)),
|
||||
child: Center(
|
||||
child: Text(AppLocalizations.of(context).translate('route_planning.route'), style: TextStyle(color: Colors.black87, fontSize: 16)),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -151,9 +154,9 @@ class _BottomOperationPanelState extends State<BottomOperationPanel> {
|
||||
children: [
|
||||
DropdownButton<RobotMode>(
|
||||
value: _robotMode,
|
||||
items: const [
|
||||
DropdownMenuItem(value: RobotMode.point, child: Text("十字准星")),
|
||||
DropdownMenuItem(value: RobotMode.robot, child: Text("机器人模式")),
|
||||
items: [
|
||||
DropdownMenuItem(value: RobotMode.point, child: Text(AppLocalizations.of(context).translate('route_planning.crosshair'))),
|
||||
DropdownMenuItem(value: RobotMode.robot, child: Text(AppLocalizations.of(context).translate('route_planning.robot_mode'))),
|
||||
],
|
||||
onChanged: (v) {
|
||||
if (v != null) {
|
||||
@@ -187,9 +190,9 @@ class _BottomOperationPanelState extends State<BottomOperationPanel> {
|
||||
if (widget.initialWorkMode == WorkMode.bow)
|
||||
DropdownButton<AreaMode>(
|
||||
value: _areaMode,
|
||||
items: const [
|
||||
DropdownMenuItem(value: AreaMode.work, child: Text("作业区域")),
|
||||
DropdownMenuItem(value: AreaMode.obstacle, child: Text("障碍区域")),
|
||||
items: [
|
||||
DropdownMenuItem(value: AreaMode.work, child: Text(AppLocalizations.of(context).translate('route_planning.work_area'))),
|
||||
DropdownMenuItem(value: AreaMode.obstacle, child: Text(AppLocalizations.of(context).translate('route_planning.obstacle_area'))),
|
||||
],
|
||||
onChanged: (v) {
|
||||
if (v != null) {
|
||||
@@ -206,15 +209,15 @@ class _BottomOperationPanelState extends State<BottomOperationPanel> {
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
_buildActionButton(Icons.delete_outline, '删除', true, () {
|
||||
_buildActionButton(Icons.delete_outline, AppLocalizations.of(context).translate('route_planning.delete'), true, () {
|
||||
debugPrint('点击了删除按钮,清空所有打点');
|
||||
widget.onDeleteTap?.call();
|
||||
}),
|
||||
_buildActionButton(Icons.undo_outlined, '撤回', widget.canUndo ?? true, () {
|
||||
_buildActionButton(Icons.undo_outlined, AppLocalizations.of(context).translate('route_planning.undo'), widget.canUndo ?? true, () {
|
||||
debugPrint('点击了撤回按钮,删除最后一个打点');
|
||||
widget.onUndoTap?.call();
|
||||
}),
|
||||
_buildActionButton(Icons.check_outlined, '完成', true, () {
|
||||
_buildActionButton(Icons.check_outlined, AppLocalizations.of(context).translate('route_planning.complete'), true, () {
|
||||
widget.onComplete.call();
|
||||
}),
|
||||
],
|
||||
@@ -226,7 +229,7 @@ class _BottomOperationPanelState extends State<BottomOperationPanel> {
|
||||
|
||||
// 标签内容切换方法(地块/航线)
|
||||
Widget _buildTabContent() {
|
||||
if (_currentTab == '地块') {
|
||||
if (_currentTab == AppLocalizations.of(context).translate('route_planning.land')) {
|
||||
// 地块模式:显示机器人/区域选择+操作按钮
|
||||
return _buildLandContent();
|
||||
} else {
|
||||
@@ -237,7 +240,7 @@ class _BottomOperationPanelState extends State<BottomOperationPanel> {
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Text('作业行距(米)', style: TextStyle(fontSize: 16, color: Colors.black87)),
|
||||
Text(AppLocalizations.of(context).translate('route_planning.work_distance'), style: const TextStyle(fontSize: 16, color: Colors.black87)),
|
||||
Row(
|
||||
children: [
|
||||
// 减号按钮
|
||||
@@ -285,7 +288,7 @@ class _BottomOperationPanelState extends State<BottomOperationPanel> {
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Text('航线方向', style: TextStyle(fontSize: 16, color: Colors.black87)),
|
||||
Text(AppLocalizations.of(context).translate('route_planning.route_direction'), style: const TextStyle(fontSize: 16, color: Colors.black87)),
|
||||
// 设置按钮
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
@@ -298,7 +301,7 @@ class _BottomOperationPanelState extends State<BottomOperationPanel> {
|
||||
foregroundColor: Colors.white,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(6)),
|
||||
),
|
||||
child: const Text('设置'),
|
||||
child: Text(AppLocalizations.of(context).translate('route_planning.settings')),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:fpdart/fpdart.dart' hide State;
|
||||
import 'package:maibu_satabot_v2/core/localization/app_localizations.dart';
|
||||
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';
|
||||
@@ -66,9 +67,10 @@ class _WorkParamsCardState extends State<WorkParamsCard> {
|
||||
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),
|
||||
Text(
|
||||
AppLocalizations.of(context).translate('home.work_params'),
|
||||
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Colors.black),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -82,7 +84,11 @@ class _WorkParamsCardState extends State<WorkParamsCard> {
|
||||
children: [
|
||||
Icon(Icons.info, size: 14, color: Colors.grey[400]),
|
||||
const SizedBox(width: 4),
|
||||
Text('查看详情', style: TextStyle(fontSize: 12, color: Colors.grey[400])),
|
||||
Text(
|
||||
AppLocalizations.of(context).translate('home.view_details'),
|
||||
style: TextStyle(fontSize: 12, color: Colors.grey[400]),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -92,12 +98,12 @@ class _WorkParamsCardState extends State<WorkParamsCard> {
|
||||
IntrinsicHeight(
|
||||
child: Row(
|
||||
children: [
|
||||
_buildParamItem('作业面积', '${workArea?.toStringAsFixed(1) ?? '0'}', '㎡'),
|
||||
_buildParamItem(context, 'home.work_area', '${workArea?.toStringAsFixed(1) ?? '0'}', '㎡'),
|
||||
_buildDivider(),
|
||||
_buildParamItem('作业里程', '${distance?.toStringAsFixed(1) ?? '0'}', 'm'),
|
||||
_buildParamItem(context, 'home.work_distance', '${distance?.toStringAsFixed(1) ?? '0'}', 'm'),
|
||||
_buildDivider(),
|
||||
// 显示实时计算的作业时长
|
||||
_buildParamItem('作业时长', '${_totalWorkMinutes}', 'min'),
|
||||
_buildParamItem(context, 'home.work_duration', '${_totalWorkMinutes}', 'min'),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -198,9 +204,10 @@ class _WorkParamsCardState extends State<WorkParamsCard> {
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Center(child: Container(width: 40, height: 4)),
|
||||
const Text(
|
||||
'历史记录详情',
|
||||
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold, color: Colors.black87),
|
||||
Text(
|
||||
AppLocalizations.of(context).translate('home.history_details'),
|
||||
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold, color: Colors.black87),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
@@ -213,13 +220,13 @@ class _WorkParamsCardState extends State<WorkParamsCard> {
|
||||
const SizedBox(height: 10),
|
||||
Expanded(
|
||||
child: data.isEmpty
|
||||
? const Center(
|
||||
? 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)),
|
||||
const Icon(Icons.description_outlined, size: 60, color: Colors.grey),
|
||||
const SizedBox(height: 16),
|
||||
Text(AppLocalizations.of(context).translate('home.no_history'), style: const TextStyle(fontSize: 16, color: Colors.grey)),
|
||||
],
|
||||
),
|
||||
)
|
||||
@@ -237,12 +244,12 @@ class _WorkParamsCardState extends State<WorkParamsCard> {
|
||||
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} ㎡'),
|
||||
_buildDetailRow(context, Icons.calendar_today, 'home.start_time', item.startTime),
|
||||
_buildDetailRow(context, Icons.calendar_today_outlined, 'home.end_time', item.endTime),
|
||||
_buildDetailRow(context, Icons.square_foot, 'home.working_area', '${item.workArea} ㎡'),
|
||||
if (item.distance != null && item.distance! > 0)
|
||||
_buildDetailRow(Icons.directions_car, '工作距离', '${item.distance!.toStringAsFixed(2)} m'),
|
||||
_buildDetailRow(Icons.timer, '花费时间', '${item.time} min'),
|
||||
_buildDetailRow(context, Icons.directions_car, 'home.working_distance', '${item.distance!.toStringAsFixed(2)} m'),
|
||||
_buildDetailRow(context, Icons.timer, 'home.time_spent', '${item.time} min'),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -257,7 +264,7 @@ class _WorkParamsCardState extends State<WorkParamsCard> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDetailRow(IconData icon, String label, String value) {
|
||||
Widget _buildDetailRow(BuildContext context, IconData icon, String labelKey, String value) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: Row(
|
||||
@@ -265,7 +272,7 @@ class _WorkParamsCardState extends State<WorkParamsCard> {
|
||||
Icon(icon, size: 16, color: Colors.blue),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'$label: ',
|
||||
AppLocalizations.of(context).translate(labelKey) + ': ',
|
||||
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: Colors.black87),
|
||||
),
|
||||
Expanded(
|
||||
@@ -276,7 +283,7 @@ class _WorkParamsCardState extends State<WorkParamsCard> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildParamItem(String label, String value, String unit) {
|
||||
Widget _buildParamItem(BuildContext context, String labelKey, String value, String unit) {
|
||||
return Expanded(
|
||||
child: Column(
|
||||
children: [
|
||||
@@ -284,16 +291,16 @@ class _WorkParamsCardState extends State<WorkParamsCard> {
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
label == '作业面积'
|
||||
labelKey == 'home.work_area'
|
||||
? Icons.aspect_ratio
|
||||
: label == '作业里程'
|
||||
: labelKey == 'home.work_distance'
|
||||
? 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)),
|
||||
Text(AppLocalizations.of(context).translate(labelKey), style: const TextStyle(fontSize: 12, color: Colors.black38)),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:maibu_satabot_v2/core/app/app_user_cubit.dart';
|
||||
import 'package:maibu_satabot_v2/core/localization/app_localizations.dart';
|
||||
import 'package:maibu_satabot_v2/core/router/route_paths.dart';
|
||||
import 'package:maibu_satabot_v2/features/devices/domain/entities/device_entity.dart';
|
||||
import 'package:maibu_satabot_v2/features/devices/presentation/bloc/devices_cubit.dart';
|
||||
@@ -80,13 +81,13 @@ class _MachineDetailsPageState extends State<MachineDetailsPage> {
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Text("修改设备名称", style: TextStyle(fontSize: 17, fontWeight: FontWeight.w600)),
|
||||
Text(AppLocalizations.of(context).translate('machine_details.edit_name_title'), style: const TextStyle(fontSize: 17, fontWeight: FontWeight.w600)),
|
||||
const SizedBox(height: 16),
|
||||
TextField(
|
||||
controller: _nameController,
|
||||
autofocus: true,
|
||||
maxLength: 20,
|
||||
decoration: const InputDecoration(hintText: "请输入设备名称", border: OutlineInputBorder(), counterText: ""),
|
||||
decoration: InputDecoration(hintText: AppLocalizations.of(ctx).translate('machine_details.edit_name_hint'), border: const OutlineInputBorder(), counterText: ""),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Row(
|
||||
@@ -97,7 +98,7 @@ class _MachineDetailsPageState extends State<MachineDetailsPage> {
|
||||
_isUpdatingName = false;
|
||||
Navigator.pop(ctx);
|
||||
},
|
||||
child: const Text("取消"),
|
||||
child: Text(AppLocalizations.of(context).translate('common.cancel')),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
@@ -109,7 +110,7 @@ class _MachineDetailsPageState extends State<MachineDetailsPage> {
|
||||
final text = _nameController.text.trim();
|
||||
if (text.isEmpty) {
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("名称不能为空")));
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(AppLocalizations.of(context).translate('machine_details.name_empty_error'))));
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -122,7 +123,7 @@ class _MachineDetailsPageState extends State<MachineDetailsPage> {
|
||||
},
|
||||
child: _isUpdatingName
|
||||
? const SizedBox(width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white))
|
||||
: const Text("确认修改"),
|
||||
: Text(AppLocalizations.of(context).translate('common.confirm')),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -146,7 +147,7 @@ class _MachineDetailsPageState extends State<MachineDetailsPage> {
|
||||
return Scaffold(
|
||||
backgroundColor: const Color(0xFFF5F5F5),
|
||||
appBar: AppBar(
|
||||
title: const Text('设备详情'),
|
||||
title: Text(AppLocalizations.of(context).translate('machine_details.title')),
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back_ios),
|
||||
onPressed: () {
|
||||
@@ -161,7 +162,7 @@ class _MachineDetailsPageState extends State<MachineDetailsPage> {
|
||||
},
|
||||
),
|
||||
),
|
||||
body: const Center(child: Text("未获取到设备信息")),
|
||||
body: Center(child: Text(AppLocalizations.of(context).translate('machine_details.device_info_error'))),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -179,7 +180,7 @@ class _MachineDetailsPageState extends State<MachineDetailsPage> {
|
||||
backgroundColor: const Color(0xFFF5F5F5),
|
||||
appBar: AppBar(
|
||||
centerTitle: true,
|
||||
title: const Text('设备详情', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
|
||||
title: Text(AppLocalizations.of(context).translate('machine_details.title'), style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back_ios),
|
||||
onPressed: () {
|
||||
@@ -224,13 +225,15 @@ class _MachineDetailsPageState extends State<MachineDetailsPage> {
|
||||
final blocState = snapshot.data;
|
||||
debugPrint('🔍 [MachineDetails] StreamBuilder 收到状态: ${blocState?.runtimeType}');
|
||||
|
||||
String controlMode = '未知';
|
||||
String controlMode = AppLocalizations.of(context).translate('common.unknown');
|
||||
String voltage = '--';
|
||||
String battery = '--';
|
||||
|
||||
if (blocState is DeviceStatusUpdated) {
|
||||
debugPrint('✅ [MachineDetails] 有实时数据 - 电压:${blocState.status.voltage}, 电量:${blocState.status.battery}');
|
||||
controlMode = blocState.status.controlMode == '3' ? '远程模式' : '本地模式';
|
||||
controlMode = blocState.status.controlMode == '3'
|
||||
? AppLocalizations.of(context).translate('machine_details.remote_mode')
|
||||
: AppLocalizations.of(context).translate('machine_details.local_mode');
|
||||
voltage = '${blocState.status.voltage}V';
|
||||
battery = '${blocState.status.battery}%';
|
||||
} else {
|
||||
@@ -247,15 +250,15 @@ class _MachineDetailsPageState extends State<MachineDetailsPage> {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(bottom: 12),
|
||||
child: Text('实时状态', style: TextStyle(fontSize: 14, color: Color(0xFF999999))),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
child: Text(AppLocalizations.of(context).translate('machine_details.real_time_status'), style: const TextStyle(fontSize: 14, color: Color(0xFF999999))),
|
||||
),
|
||||
_buildStatusRow('控制模式', controlMode),
|
||||
_buildStatusRow(AppLocalizations.of(context).translate('machine_details.control_mode'), controlMode),
|
||||
const Divider(height: 24, color: Color(0xFFF0F0F0)),
|
||||
_buildStatusRow('电池电压', voltage),
|
||||
_buildStatusRow(AppLocalizations.of(context).translate('machine_details.battery_voltage'), voltage),
|
||||
const Divider(height: 24, color: Color(0xFFF0F0F0)),
|
||||
_buildStatusRow('剩余电量', battery),
|
||||
_buildStatusRow(AppLocalizations.of(context).translate('machine_details.remaining_battery'), battery),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -300,7 +303,7 @@ class _MachineDetailsPageState extends State<MachineDetailsPage> {
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Text(
|
||||
device.isOnline ? '在线' : '离线',
|
||||
device.isOnline ? AppLocalizations.of(context).translate('home.device_online') : AppLocalizations.of(context).translate('home.device_offline'),
|
||||
style: TextStyle(fontSize: 14, color: device.isOnline ? const Color(0xFF00C853) : const Color(0xFF999999)),
|
||||
),
|
||||
),
|
||||
@@ -311,7 +314,7 @@ class _MachineDetailsPageState extends State<MachineDetailsPage> {
|
||||
|
||||
/// 基本信息卡片(保持不变)
|
||||
Widget _buildBasicInfoCard(BuildContext context, DeviceEntity device) {
|
||||
final deviceId = device.deviceName ?? '未知ID';
|
||||
final deviceId = device.deviceName ?? AppLocalizations.of(context).translate('common.unknown');
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
@@ -322,14 +325,14 @@ class _MachineDetailsPageState extends State<MachineDetailsPage> {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(bottom: 16),
|
||||
child: Text('基本信息', style: TextStyle(fontSize: 14, color: Color(0xFF999999))),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 16),
|
||||
child: Text(AppLocalizations.of(context).translate('machine_details.basic_info'), style: const TextStyle(fontSize: 14, color: Color(0xFF999999))),
|
||||
),
|
||||
_buildInfoRow(
|
||||
context: context,
|
||||
icon: Icons.info_outline,
|
||||
label: '设备名称',
|
||||
label: AppLocalizations.of(context).translate('machine_details.device_name'),
|
||||
trailing: GestureDetector(
|
||||
onTap: _isUpdatingName ? null : _showEditNameSheet,
|
||||
child: Row(
|
||||
@@ -346,7 +349,7 @@ class _MachineDetailsPageState extends State<MachineDetailsPage> {
|
||||
_buildInfoRow(
|
||||
context: context,
|
||||
icon: Icons.shield_outlined,
|
||||
label: '设备ID',
|
||||
label: AppLocalizations.of(context).translate('machine_details.device_id'),
|
||||
trailing: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
@@ -354,7 +357,7 @@ class _MachineDetailsPageState extends State<MachineDetailsPage> {
|
||||
SizedBox(
|
||||
width: MediaQuery.of(context).size.width - 180,
|
||||
child: GestureDetector(
|
||||
onLongPress: () => _copyToClipboard(deviceId, '设备ID已复制'),
|
||||
onLongPress: () => _copyToClipboard(deviceId, AppLocalizations.of(context).translate('machine_details.copied')),
|
||||
child: Text(
|
||||
deviceId,
|
||||
textAlign: TextAlign.right,
|
||||
@@ -366,7 +369,7 @@ class _MachineDetailsPageState extends State<MachineDetailsPage> {
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
GestureDetector(
|
||||
onTap: () => _copyToClipboard(deviceId, '设备ID已复制到剪贴板'),
|
||||
onTap: () => _copyToClipboard(deviceId, AppLocalizations.of(context).translate('machine_details.device_id') + AppLocalizations.of(context).translate('machine_details.copied')),
|
||||
child: const Icon(Icons.copy, size: 16, color: Color(0xFF999999)),
|
||||
),
|
||||
],
|
||||
@@ -389,13 +392,13 @@ class _MachineDetailsPageState extends State<MachineDetailsPage> {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(bottom: 16),
|
||||
child: Text('资源中心', style: TextStyle(fontSize: 14, color: Color(0xFF999999))),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 16),
|
||||
child: Text(AppLocalizations.of(context).translate('machine_details.resource_center'), style: const TextStyle(fontSize: 14, color: Color(0xFF999999))),
|
||||
),
|
||||
_buildResourceItem('使用说明', Icons.description_outlined),
|
||||
_buildResourceItem(AppLocalizations.of(context).translate('machine_details.user_manual'), Icons.description_outlined),
|
||||
const Divider(height: 32, color: Color(0xFFF0F0F0)),
|
||||
_buildResourceItem('产品手册', Icons.book_outlined),
|
||||
_buildResourceItem(AppLocalizations.of(context).translate('machine_details.product_manual'), Icons.book_outlined),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -418,7 +421,8 @@ class _MachineDetailsPageState extends State<MachineDetailsPage> {
|
||||
Widget _buildResourceItem(String title, IconData icon) {
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
if (title == "使用说明") {
|
||||
final loc = AppLocalizations.of(context);
|
||||
if (title == loc.translate('machine_details.user_manual')) {
|
||||
context.push(RoutePaths.usage);
|
||||
} else {
|
||||
context.push(RoutePaths.productDesc);
|
||||
@@ -451,13 +455,13 @@ class _MachineDetailsPageState extends State<MachineDetailsPage> {
|
||||
textStyle: const TextStyle(height: 1.2),
|
||||
),
|
||||
onPressed: _isUpdatingName ? null : () => _showUnbindConfirmDialog(),
|
||||
child: const Row(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Icon(Icons.unarchive_outlined, size: 18),
|
||||
SizedBox(width: 8),
|
||||
Text('解绑设备', style: TextStyle(fontSize: 16, height: 1.0), overflow: TextOverflow.visible),
|
||||
const Icon(Icons.unarchive_outlined, size: 18),
|
||||
const SizedBox(width: 8),
|
||||
Text(AppLocalizations.of(context).translate('machine_details.unbind'), style: const TextStyle(fontSize: 16, height: 1.0), overflow: TextOverflow.visible),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -471,15 +475,15 @@ class _MachineDetailsPageState extends State<MachineDetailsPage> {
|
||||
builder: (dialogContext) {
|
||||
_dialogContext = dialogContext;
|
||||
return AlertDialog(
|
||||
title: const Text('确认解绑'),
|
||||
content: Text('确定要解绑【$_deviceName】吗?解绑后将无法管理该设备'),
|
||||
title: Text(AppLocalizations.of(dialogContext).translate('machine_details.unbind_confirm_title')),
|
||||
content: Text(AppLocalizations.of(dialogContext).translate('machine_details.unbind_confirm_content').replaceAll('%s', _deviceName)),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
actions: [
|
||||
TextButton(onPressed: () => Navigator.pop(dialogContext), child: const Text('取消')),
|
||||
TextButton(onPressed: () => Navigator.pop(dialogContext), child: Text(AppLocalizations.of(dialogContext).translate('common.cancel'))),
|
||||
TextButton(
|
||||
style: TextButton.styleFrom(foregroundColor: const Color(0xFFFF3B30)),
|
||||
onPressed: _executeUnbind,
|
||||
child: const Text('确认'),
|
||||
child: Text(AppLocalizations.of(dialogContext).translate('common.confirm')),
|
||||
),
|
||||
],
|
||||
);
|
||||
@@ -490,7 +494,7 @@ class _MachineDetailsPageState extends State<MachineDetailsPage> {
|
||||
Future<void> _executeUnbind() async {
|
||||
if (_deviceId.isEmpty || _deviceName.isEmpty) {
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("设备信息异常,无法解绑"), backgroundColor: Colors.red));
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(AppLocalizations.of(context).translate('machine_details.device_info_error')), backgroundColor: Colors.red));
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -508,7 +512,7 @@ class _MachineDetailsPageState extends State<MachineDetailsPage> {
|
||||
barrierDismissible: false,
|
||||
builder: (ctx) {
|
||||
_loadingDialogContext = ctx;
|
||||
return const AlertDialog(content: Row(children: [CircularProgressIndicator(strokeWidth: 2), SizedBox(width: 16), Text("正在解绑设备...")]));
|
||||
return AlertDialog(content: Row(children: [const CircularProgressIndicator(strokeWidth: 2), const SizedBox(width: 16), Text(AppLocalizations.of(ctx).translate('machine_details.unbinding_device'))]));
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -523,7 +527,7 @@ class _MachineDetailsPageState extends State<MachineDetailsPage> {
|
||||
}
|
||||
} catch (e) {
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("解绑异常:${e.toString()}"), backgroundColor: Colors.red));
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(AppLocalizations.of(context).translate('machine_details.unbind_exception').replaceAll('%s', e.toString())), backgroundColor: Colors.red));
|
||||
// 关闭加载弹窗
|
||||
if (_loadingDialogContext != null && Navigator.canPop(_loadingDialogContext!)) {
|
||||
Navigator.pop(_loadingDialogContext!);
|
||||
@@ -538,7 +542,7 @@ class _MachineDetailsPageState extends State<MachineDetailsPage> {
|
||||
// 基础校验
|
||||
if (_deviceId.isEmpty || newDeviceName.isEmpty) {
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("设备信息异常,无法修改名称"), backgroundColor: Colors.red));
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(AppLocalizations.of(context).translate('machine_details.device_info_error')), backgroundColor: Colors.red));
|
||||
}
|
||||
setState(() => _isUpdatingName = false);
|
||||
return;
|
||||
@@ -552,7 +556,7 @@ class _MachineDetailsPageState extends State<MachineDetailsPage> {
|
||||
barrierDismissible: false,
|
||||
builder: (ctx) {
|
||||
_updateLoadingContext = ctx;
|
||||
return const AlertDialog(content: Row(children: [CircularProgressIndicator(strokeWidth: 2), SizedBox(width: 16), Text("正在修改名称...")]));
|
||||
return AlertDialog(content: Row(children: [const CircularProgressIndicator(strokeWidth: 2), const SizedBox(width: 16), Text(AppLocalizations.of(ctx).translate('machine_details.updating_name'))]));
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -575,7 +579,7 @@ class _MachineDetailsPageState extends State<MachineDetailsPage> {
|
||||
}
|
||||
} catch (e) {
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("修改异常:${e.toString()}"), backgroundColor: Colors.red));
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(AppLocalizations.of(context).translate('machine_details.update_exception').replaceAll('%s', e.toString())), backgroundColor: Colors.red));
|
||||
// 关闭修改名称加载弹窗
|
||||
if (_updateLoadingContext != null && Navigator.canPop(_updateLoadingContext!)) {
|
||||
Navigator.pop(_updateLoadingContext!);
|
||||
@@ -605,14 +609,14 @@ class _MachineDetailsPageState extends State<MachineDetailsPage> {
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text("设备「$_deviceName」解绑成功"), backgroundColor: Colors.green, duration: const Duration(seconds: 2)));
|
||||
).showSnackBar(SnackBar(content: Text(AppLocalizations.of(context).translate('machine_details.unbind_success').replaceAll('%s', _deviceName)), backgroundColor: Colors.green, duration: const Duration(seconds: 2)));
|
||||
|
||||
final username = context.read<AppUserCubit>().state.user?.username ?? "";
|
||||
context.read<DevicesCubit>().fetchAllDevices(username);
|
||||
}
|
||||
} else if (state.errorMessage?.isNotEmpty == true && !state.isLoading) {
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("解绑失败:${state.errorMessage ?? '未知错误'}"), backgroundColor: Colors.red));
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(AppLocalizations.of(context).translate('machine_details.unbind_failed').replaceAll('%s', state.errorMessage ?? AppLocalizations.of(context).translate('common.unknown'))), backgroundColor: Colors.red));
|
||||
}
|
||||
}
|
||||
context.go(RoutePaths.home);
|
||||
@@ -646,7 +650,7 @@ class _MachineDetailsPageState extends State<MachineDetailsPage> {
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text("设备名称已修改为:$_deviceName"), backgroundColor: Colors.green, duration: const Duration(seconds: 2)));
|
||||
).showSnackBar(SnackBar(content: Text(AppLocalizations.of(context).translate('machine_details.name_updated_success').replaceAll('%s', _deviceName)), backgroundColor: Colors.green, duration: const Duration(seconds: 2)));
|
||||
|
||||
// 关闭修改名称的底部抽屉(如果还在)
|
||||
if (_editNameSheetContext != null && Navigator.canPop(_editNameSheetContext!)) {
|
||||
@@ -660,7 +664,7 @@ class _MachineDetailsPageState extends State<MachineDetailsPage> {
|
||||
} else if (state.errorMessage?.isNotEmpty == true) {
|
||||
setState(() => _isUpdatingName = false);
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("修改失败:${state.errorMessage ?? '未知错误'}"), backgroundColor: Colors.red));
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(AppLocalizations.of(context).translate('machine_details.update_failed').replaceAll('%s', state.errorMessage ?? AppLocalizations.of(context).translate('common.unknown'))), backgroundColor: Colors.red));
|
||||
// 强制重置Cubit的loading状态
|
||||
context.read<DevicesCubit>().emit(state.copyWith(isLoading: false, operationType: DeviceOperationType.none));
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ import 'package:get_it/get_it.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:maibu_satabot_v2/components/BannerCarousel.dart';
|
||||
import 'package:maibu_satabot_v2/core/di/injection.dart';
|
||||
import 'package:maibu_satabot_v2/core/localization/app_localizations.dart';
|
||||
import 'package:maibu_satabot_v2/core/localization/locale_cubit.dart';
|
||||
import 'package:maibu_satabot_v2/features/my/presentation/bloc/my_cubit.dart';
|
||||
import 'package:maibu_satabot_v2/features/my/repository/my_repository.dart';
|
||||
import 'package:maibu_satabot_v2/features/my/usecases/updatename_usecase.dart';
|
||||
@@ -80,10 +82,10 @@ class MyPage extends StatelessWidget {
|
||||
|
||||
Widget _buildFunctionList(BuildContext context) {
|
||||
final items = [
|
||||
{'icon': Icons.play_arrow, 'title': '操作视频', 'url': 'https://www.satabot.com/Support/index.html'},
|
||||
{'icon': Icons.car_rental, 'title': '租赁服务', 'url': 'https://www.satabot.com/Leasing.html'},
|
||||
{'icon': Icons.help_outline, 'title': '帮助与支持', 'url': 'https://www.satabot.com/support/index.html'},
|
||||
{'icon': Icons.policy, 'title': '售后政策', 'url': 'https://www.satabot.com/Aftersales-Service.html'},
|
||||
{'icon': Icons.play_arrow, 'title_key': 'my.operation_video', 'url': 'https://www.satabot.com/Support/index.html'},
|
||||
{'icon': Icons.car_rental, 'title_key': 'my.rental_service', 'url': 'https://www.satabot.com/Leasing.html'},
|
||||
{'icon': Icons.help_outline, 'title_key': 'my.help_support', 'url': 'https://www.satabot.com/support/index.html'},
|
||||
{'icon': Icons.policy, 'title_key': 'my.after_sales_policy', 'url': 'https://www.satabot.com/Aftersales-Service.html'},
|
||||
];
|
||||
|
||||
return Container(
|
||||
@@ -103,7 +105,7 @@ class MyPage extends StatelessWidget {
|
||||
final item = items[index];
|
||||
return ListTile(
|
||||
leading: Icon(item['icon'] as IconData),
|
||||
title: Text(item['title'] as String),
|
||||
title: Text(AppLocalizations.of(context).translate(item['title_key'] as String)),
|
||||
trailing: Icon(Icons.arrow_forward),
|
||||
onTap: () {
|
||||
Navigator.push(context, MaterialPageRoute(builder: (context) => WebViewPage(url: item['url'] as String)));
|
||||
@@ -113,10 +115,50 @@ class MyPage extends StatelessWidget {
|
||||
),
|
||||
// 分割线
|
||||
Divider(height: 1, color: Colors.grey.withOpacity(0.3)),
|
||||
|
||||
// 🔥 语言切换
|
||||
BlocBuilder<LocaleCubit, Locale>(
|
||||
builder: (context, locale) {
|
||||
return ListTile(
|
||||
leading: const Icon(Icons.language, color: Colors.black),
|
||||
title: Text(AppLocalizations.of(context).translate('my.language'), style: TextStyle(color: Colors.black)),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () => context.read<LocaleCubit>().setLocale(const Locale('zh', 'CN')),
|
||||
child: Text(
|
||||
AppLocalizations.of(context).translate('my.chinese'),
|
||||
style: TextStyle(
|
||||
color: locale.languageCode == 'zh' ? Colors.blue : Colors.grey,
|
||||
fontWeight: locale.languageCode == 'zh' ? FontWeight.bold : FontWeight.normal,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
TextButton(
|
||||
onPressed: () => context.read<LocaleCubit>().setLocale(const Locale('en', 'US')),
|
||||
child: Text(
|
||||
AppLocalizations.of(context).translate('my.english'),
|
||||
style: TextStyle(
|
||||
color: locale.languageCode == 'en' ? Colors.blue : Colors.grey,
|
||||
fontWeight: locale.languageCode == 'en' ? FontWeight.bold : FontWeight.normal,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
// 分割线
|
||||
Divider(height: 1, color: Colors.grey.withOpacity(0.3)),
|
||||
|
||||
// 退出登录按钮
|
||||
ListTile(
|
||||
leading: const Icon(Icons.logout, color: Colors.black),
|
||||
title: Text('退出登录', style: TextStyle(color: Colors.black)),
|
||||
title: Text(AppLocalizations.of(context).translate('login.logout'), style: TextStyle(color: Colors.black)),
|
||||
trailing: Icon(Icons.exit_to_app, color: Colors.black),
|
||||
onTap: () async {
|
||||
await GetIt.I<UserStorage>().deleteUser();
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:maibu_satabot_v2/core/consts/tcp_consts.dart';
|
||||
import 'package:maibu_satabot_v2/core/localization/app_localizations.dart';
|
||||
|
||||
import '../../../../core/app/app_user_cubit.dart';
|
||||
import '../../../../core/router/route_paths.dart';
|
||||
@@ -203,9 +204,9 @@ class _RemoteControlPageState extends State<RemoteControlPage> {
|
||||
children: [
|
||||
const Icon(Icons.signal_wifi_off, color: Colors.white, size: 60),
|
||||
const SizedBox(height: 20),
|
||||
const Text("设备已断开连接", style: TextStyle(color: Colors.white, fontSize: 18)),
|
||||
Text(AppLocalizations.of(context).translate('remote_control.device_disconnected'), style: const TextStyle(color: Colors.white, fontSize: 18)),
|
||||
const SizedBox(height: 20),
|
||||
ElevatedButton(onPressed: () => context.pop(), child: const Text("返回")),
|
||||
ElevatedButton(onPressed: () => context.pop(), child: Text(AppLocalizations.of(context).translate('remote_control.back'))),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -217,11 +218,11 @@ class _RemoteControlPageState extends State<RemoteControlPage> {
|
||||
return Container(
|
||||
color: Colors.black54,
|
||||
child: AlertDialog(
|
||||
title: const Text("权限变更"),
|
||||
title: Text(AppLocalizations.of(context).translate('remote_control.permission_request_title')),
|
||||
// content: Text("${state.permissionPlatform}端正请求控制权,同意释放吗?"),
|
||||
content: Text("web端正请求控制权,同意释放吗?"),
|
||||
content: Text(AppLocalizations.of(context).translate('remote_control.permission_request_content')),
|
||||
actions: [
|
||||
TextButton(onPressed: () => context.read<RemoteControlCubit>().respondPermission(false, deviceId), child: const Text("拒绝")),
|
||||
TextButton(onPressed: () => context.read<RemoteControlCubit>().respondPermission(false, deviceId), child: Text(AppLocalizations.of(context).translate('remote_control.refuse'))),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
context.read<RemoteControlCubit>().respondPermission(true, deviceId);
|
||||
@@ -232,7 +233,7 @@ class _RemoteControlPageState extends State<RemoteControlPage> {
|
||||
// }
|
||||
// });
|
||||
},
|
||||
child: const Text("同意"),
|
||||
child: Text(AppLocalizations.of(context).translate('remote_control.agree')),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'package:cc_ui_kit/cc_ui_kit.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:maibu_satabot_v2/core/localization/app_localizations.dart';
|
||||
|
||||
import '../bloc/remote_control_cubit.dart';
|
||||
import 'emergency_stop_button.dart';
|
||||
@@ -41,7 +42,7 @@ class CenterControlArea extends StatelessWidget {
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
_buildSliderBox(expandedWidth, "底盘", true,context),
|
||||
_buildSliderBox(expandedWidth, AppLocalizations.of(context).translate('remote_control.chassis'), true,context),
|
||||
SizedBox(height: totalWidth * 0.01),
|
||||
],
|
||||
),
|
||||
@@ -57,9 +58,9 @@ class CenterControlArea extends StatelessWidget {
|
||||
|
||||
SizedBox(height: totalWidth * 0.05),
|
||||
|
||||
const Text(
|
||||
'双击空白处切换视角',
|
||||
style: TextStyle(fontSize: 8, color: Colors.grey),
|
||||
Text(
|
||||
AppLocalizations.of(context).translate('remote_control.switch_view_hint'),
|
||||
style: const TextStyle(fontSize: 8, color: Colors.grey),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -71,7 +72,7 @@ class CenterControlArea extends StatelessWidget {
|
||||
width: expandedWidth,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [_buildSliderBox(expandedWidth, "割刀", false,context)],
|
||||
children: [_buildSliderBox(expandedWidth, AppLocalizations.of(context).translate('remote_control.mower'), false,context)],
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -93,7 +94,9 @@ class CenterControlArea extends StatelessWidget {
|
||||
svgStart: "assets/svgs/remote_up.svg",
|
||||
svgCenter: switch (label) {
|
||||
"割刀" => "assets/svgs/remote_scissors.svg",
|
||||
"Mower" => "assets/svgs/remote_scissors.svg",
|
||||
"底盘" => "assets/svgs/remote_layers.svg",
|
||||
"Chassis" => "assets/svgs/remote_layers.svg",
|
||||
_ => "assets/svgs/remote_layers.svg", // 下划线表示默认值
|
||||
},
|
||||
svgEnd: "assets/svgs/remote_down.svg",
|
||||
@@ -111,12 +114,15 @@ class CenterControlArea extends StatelessWidget {
|
||||
void _handleSliderAction(String label, String action, BuildContext context) {
|
||||
debugPrint('🎯 [Slider 业务] label: $label, action: $action');
|
||||
|
||||
final loc = AppLocalizations.of(context);
|
||||
// 🔥 根据 label 区分不同的设备
|
||||
switch (label) {
|
||||
case "底盘":
|
||||
case "Chassis":
|
||||
_handleChassisAction(action, context);
|
||||
break;
|
||||
case "割刀":
|
||||
case "Mower":
|
||||
_handleMowerAction(action, context);
|
||||
break;
|
||||
default:
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:vibration/vibration.dart';
|
||||
import 'package:maibu_satabot_v2/core/localization/app_localizations.dart';
|
||||
|
||||
import '../bloc/remote_control_cubit.dart';
|
||||
|
||||
@@ -158,7 +159,9 @@ class _EmergencyStopButtonState extends State<EmergencyStopButton>
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
isEmergencyActive ? "急停中" : "急停",
|
||||
isEmergencyActive
|
||||
? AppLocalizations.of(context).translate('remote_control.emergency_stopping')
|
||||
: AppLocalizations.of(context).translate('remote_control.emergency_stop'),
|
||||
style: TextStyle(
|
||||
color: Colors.white.withValues(alpha: 0.5),
|
||||
fontSize: 14,
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:cc_ui_kit/cc_ui_kit.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:vibration/vibration.dart';
|
||||
import 'package:maibu_satabot_v2/core/localization/app_localizations.dart';
|
||||
|
||||
import '../bloc/remote_control_cubit.dart';
|
||||
|
||||
@@ -63,7 +64,7 @@ class _LeftJoystickAreaState extends State<LeftJoystickArea> {
|
||||
),
|
||||
const SizedBox(height: 18),
|
||||
Text(
|
||||
"前后控制",
|
||||
AppLocalizations.of(context).translate('remote_control.forward_backward'),
|
||||
style: TextStyle(
|
||||
color: Colors.white.withOpacity(0.5),
|
||||
fontSize: 12,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:vibration/vibration.dart';
|
||||
import 'package:maibu_satabot_v2/core/localization/app_localizations.dart';
|
||||
|
||||
import '../bloc/remote_control_cubit.dart';
|
||||
|
||||
@@ -58,7 +59,7 @@ class _RightJoystickAreaState extends State<RightJoystickArea> {
|
||||
),
|
||||
const SizedBox(height: 18),
|
||||
Text(
|
||||
"左右控制",
|
||||
AppLocalizations.of(context).translate('remote_control.left_right'),
|
||||
style: TextStyle(
|
||||
color: Colors.white.withOpacity(0.5),
|
||||
fontSize: 12,
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:maibu_satabot_v2/core/localization/app_localizations.dart';
|
||||
import 'package:maibu_satabot_v2/features/remote_control/presentation/bloc/remote_control_state.dart';
|
||||
import 'package:maibu_satabot_v2/features/remote_control/presentation/widgets/status_chip.dart';
|
||||
import 'package:cc_ui_kit/cc_ui_kit.dart';
|
||||
@@ -51,7 +52,9 @@ class TopStatusBar extends StatelessWidget {
|
||||
|
||||
// 2. 控制状态 (对应 StatusChipLeft)
|
||||
StatusChip(
|
||||
text: remoteState.hasPermission ? "正在控制" : "未在控制",
|
||||
text: remoteState.hasPermission
|
||||
? AppLocalizations.of(context).translate('remote_control.controlling')
|
||||
: AppLocalizations.of(context).translate('remote_control.not_controlling'),
|
||||
color: remoteState.hasPermission
|
||||
? const Color(0xFF1DB954)
|
||||
: Colors.red,
|
||||
@@ -81,7 +84,7 @@ class TopStatusBar extends StatelessWidget {
|
||||
// const SizedBox(width: 8),
|
||||
// 5. 火技能按钮 (对应 SmallFunctionButton)
|
||||
//_buildIconButton("assets/svgs/fire.svg", () {}),
|
||||
_buildSliderBox("火技能按钮", false,context),
|
||||
_buildSliderBox(AppLocalizations.of(context).translate('remote_control.fire_skill'), false,context),
|
||||
|
||||
const Spacer(),
|
||||
_buildExpandIconButton(
|
||||
@@ -127,7 +130,7 @@ class TopStatusBar extends StatelessWidget {
|
||||
},
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
_buildControlModeChip(remoteState.runningStatusModel.controlMode),
|
||||
_buildControlModeChip(remoteState.runningStatusModel.controlMode, context),
|
||||
const SizedBox(width: 8),
|
||||
_buildVoltageChip(remoteState.runningStatusModel.voltage),
|
||||
const SizedBox(width: 8),
|
||||
@@ -245,10 +248,10 @@ class TopStatusBar extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildControlModeChip(String mode) {
|
||||
Widget _buildControlModeChip(String mode, BuildContext context) {
|
||||
String displayText;
|
||||
if(mode == ''){
|
||||
displayText = '无模式';
|
||||
displayText = AppLocalizations.of(context).translate('remote_control.mode_none');
|
||||
}else{
|
||||
displayText = mode;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:maibu_satabot_v2/core/app/app_user_cubit.dart';
|
||||
@@ -12,6 +13,8 @@ import 'package:maibu_satabot_v2/features/auth/presentation/bloc/auth_cubit.dart
|
||||
import 'package:maibu_satabot_v2/features/devices/presentation/bloc/devices_cubit.dart';
|
||||
|
||||
import 'core/di/injection.dart';
|
||||
import 'core/localization/app_localizations.dart';
|
||||
import 'core/localization/locale_cubit.dart';
|
||||
import 'features/auth/presentation/bloc/auth_state.dart';
|
||||
import 'features/auth/presentation/bloc/login_cubit.dart';
|
||||
import 'features/devices/presentation/bloc/device_status_bloc.dart';
|
||||
@@ -49,6 +52,8 @@ class MyApp extends StatelessWidget {
|
||||
// 在 runApp 之前调用 appStarted,确保 GoRouter 初始化时能获取到正确的初始状态
|
||||
sl<AuthCubit>().appStarted();
|
||||
final deviceStatusBloc = sl<DeviceStatusBloc>();
|
||||
final localeCubit = sl<LocaleCubit>();
|
||||
|
||||
return MultiBlocProvider(
|
||||
providers: [
|
||||
// 核心修正:在这里提供 AuthCubit
|
||||
@@ -70,15 +75,34 @@ class MyApp extends StatelessWidget {
|
||||
BlocProvider<DeviceStatusBloc>.value(
|
||||
value: deviceStatusBloc,
|
||||
),
|
||||
// 🔥 语言管理 Cubit
|
||||
BlocProvider<LocaleCubit>.value(value: localeCubit),
|
||||
// 其他 Cubit...
|
||||
],
|
||||
child: MaterialApp.router(title: 'Maibu Satabot',
|
||||
theme: AppTheme.lightTheme, routerConfig: sl<GoRouter>(),
|
||||
builder: (context, child) {
|
||||
return _LifecycleListener(child: child);
|
||||
},),
|
||||
|
||||
|
||||
child: BlocBuilder<LocaleCubit, Locale>(
|
||||
bloc: localeCubit,
|
||||
builder: (context, locale) {
|
||||
return MaterialApp.router(
|
||||
title: 'Maibu Satabot',
|
||||
locale: locale,
|
||||
supportedLocales: const [
|
||||
Locale('zh', 'CN'),
|
||||
Locale('en', 'US'),
|
||||
],
|
||||
localizationsDelegates: const [
|
||||
AppLocalizations.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
theme: AppTheme.lightTheme,
|
||||
routerConfig: sl<GoRouter>(),
|
||||
builder: (context, child) {
|
||||
return _LifecycleListener(child: child);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -405,6 +405,11 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "6.0.0"
|
||||
flutter_localizations:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_map:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
||||
@@ -39,6 +39,10 @@ dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
|
||||
# ===== 国际化支持 =====
|
||||
flutter_localizations:
|
||||
sdk: flutter
|
||||
|
||||
# The following adds the Cupertino Icons font to your application.
|
||||
# Use with the CupertinoIcons class for iOS style icons.
|
||||
cupertino_icons: ^1.0.8
|
||||
@@ -168,6 +172,7 @@ flutter:
|
||||
- assets/images/
|
||||
- assets/www/webrtc/
|
||||
- assets/svgs/
|
||||
- assets/languages/
|
||||
#- assets/tiles/ # 声明所有大疆地图资源(通配子目录)
|
||||
#- assets/tiles/metadata.json # 可选:场站配置文件
|
||||
|
||||
|
||||
Reference in New Issue
Block a user