基本完成APP的所有页面中的语言切换支持的开发和设计

This commit is contained in:
2026-04-24 15:24:43 +08:00
parent ba4519125e
commit 2e241cb9f5
26 changed files with 649 additions and 416 deletions

View File

@@ -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()));

View File

@@ -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')))],
);
},
);

View File

@@ -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,
),

View File

@@ -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),

View File

@@ -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)
],
);
}

View File

@@ -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,

View File

@@ -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)),
],
),
);
},
);
}
}

View File

@@ -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),

View File

@@ -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,

View File

@@ -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'),
),
],
),

View File

@@ -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)),
),
],
),

View File

@@ -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("确定")),
],
),
],
),
),

View File

@@ -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,

View File

@@ -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')),
),
],
),

View File

@@ -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),

View File

@@ -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));
}

View File

@@ -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();

View File

@@ -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')),
),
],
),

View File

@@ -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:

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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;
}

View File

@@ -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);
},
);
},
),
);
}
}

View File

@@ -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:

View File

@@ -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 # 可选:场站配置文件